use std::time::SystemTime;
use crate::header::Header;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum SameSite {
#[default]
Default,
Lax,
Strict,
None,
}
#[derive(Debug, Clone, Default)]
pub struct Cookie {
pub name: String,
pub value: String,
pub path: String,
pub domain: String,
pub expires: Option<SystemTime>,
pub max_age: i32,
pub secure: bool,
pub http_only: bool,
pub same_site: SameSite,
}
impl Cookie {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
..Default::default()
}
}
pub fn to_set_cookie_header(&self) -> String {
let mut s = format!("{}={}", self.name, self.value);
if !self.path.is_empty() { s.push_str(&format!("; Path={}", self.path)); }
if !self.domain.is_empty() { s.push_str(&format!("; Domain={}", self.domain)); }
if self.max_age > 0 { s.push_str(&format!("; Max-Age={}", self.max_age)); }
if self.secure { s.push_str("; Secure"); }
if self.http_only { s.push_str("; HttpOnly"); }
match self.same_site {
SameSite::Lax => s.push_str("; SameSite=Lax"),
SameSite::Strict => s.push_str("; SameSite=Strict"),
SameSite::None => s.push_str("; SameSite=None"),
SameSite::Default => {}
}
s
}
}
pub fn parse_request_cookies(h: &Header) -> Vec<Cookie> {
let mut cookies = Vec::new();
for val in h.values("Cookie") {
for pair in val.split(';') {
let pair = pair.trim();
if let Some(eq) = pair.find('=') {
let name = pair[..eq].trim().to_owned();
let value = pair[eq + 1..].trim().to_owned();
if !name.is_empty() {
cookies.push(Cookie::new(name, value));
}
}
}
}
cookies
}
pub trait CookieJar: Send + Sync {
fn cookies(&self, url: &url::Url) -> Vec<Cookie>;
fn set_cookies(&self, url: &url::Url, cookies: &[Cookie]);
}
use std::sync::Mutex;
use std::collections::HashMap;
pub struct MemoryCookieJar {
store: Mutex<HashMap<String, Vec<Cookie>>>,
}
impl MemoryCookieJar {
pub fn new() -> Self {
Self { store: Mutex::new(HashMap::new()) }
}
}
impl Default for MemoryCookieJar {
fn default() -> Self { Self::new() }
}
impl CookieJar for MemoryCookieJar {
fn cookies(&self, url: &url::Url) -> Vec<Cookie> {
let key = url.host_str().unwrap_or("").to_owned();
self.store.lock().unwrap().get(&key).cloned().unwrap_or_default()
}
fn set_cookies(&self, url: &url::Url, cookies: &[Cookie]) {
let key = url.host_str().unwrap_or("").to_owned();
self.store.lock().unwrap()
.entry(key)
.or_default()
.extend_from_slice(cookies);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_set_cookie_header_basic() {
let c = Cookie::new("session", "abc123");
let s = c.to_set_cookie_header();
assert_eq!(s, "session=abc123");
}
#[test]
fn to_set_cookie_header_full() {
let c = Cookie {
name: "id".into(),
value: "42".into(),
path: "/".into(),
secure: true,
http_only: true,
same_site: SameSite::Lax,
..Default::default()
};
let s = c.to_set_cookie_header();
assert!(s.contains("id=42"));
assert!(s.contains("Path=/"));
assert!(s.contains("Secure"));
assert!(s.contains("HttpOnly"));
assert!(s.contains("SameSite=Lax"));
}
#[test]
fn parse_request_cookies_basic() {
let mut h = Header::new();
h.set("Cookie", "a=1; b=2");
let cookies = parse_request_cookies(&h);
assert_eq!(cookies.len(), 2);
assert_eq!(cookies[0].name, "a");
assert_eq!(cookies[1].value, "2");
}
}