use std::fmt;
use parking_lot::Mutex;
use crate::Config;
use crate::http::private::cookie;
#[doc(inline)]
pub use self::cookie::{Cookie, SameSite, Iter};
pub struct CookieJar<'a> {
jar: cookie::CookieJar,
ops: Mutex<Vec<Op>>,
config: &'a Config,
}
impl<'a> Clone for CookieJar<'a> {
fn clone(&self) -> Self {
CookieJar {
jar: self.jar.clone(),
ops: Mutex::new(self.ops.lock().clone()),
config: self.config,
}
}
}
#[derive(Clone)]
enum Op {
Add(Cookie<'static>, bool),
Remove(Cookie<'static>, bool),
}
impl Op {
fn cookie(&self) -> &Cookie<'static> {
match self {
Op::Add(c, _) | Op::Remove(c, _) => c
}
}
}
impl<'a> CookieJar<'a> {
#[inline(always)]
pub(crate) fn new(config: &'a Config) -> Self {
CookieJar::from(cookie::CookieJar::new(), config)
}
pub(crate) fn from(jar: cookie::CookieJar, config: &'a Config) -> Self {
CookieJar { jar, config, ops: Mutex::new(Vec::new()) }
}
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
self.jar.get(name)
}
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn get_private(&self, name: &str) -> Option<Cookie<'static>> {
self.jar.private(&self.config.secret_key.key).get(name)
}
pub fn get_pending(&self, name: &str) -> Option<Cookie<'static>> {
let ops = self.ops.lock();
for op in ops.iter().rev().filter(|op| op.cookie().name() == name) {
match op {
Op::Add(c, _) => return Some(c.clone()),
Op::Remove(_, _) => return None,
}
}
drop(ops);
#[cfg(feature = "secrets")] {
self.get_private(name).or_else(|| self.get(name).cloned())
}
#[cfg(not(feature = "secrets"))] {
self.get(name).cloned()
}
}
pub fn add<C: Into<Cookie<'static>>>(&self, cookie: C) {
let mut cookie = cookie.into();
Self::set_defaults(self.config, &mut cookie);
self.ops.lock().push(Op::Add(cookie, false));
}
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn add_private<C: Into<Cookie<'static>>>(&self, cookie: C) {
let mut cookie = cookie.into();
Self::set_private_defaults(self.config, &mut cookie);
self.ops.lock().push(Op::Add(cookie, true));
}
pub fn remove<C: Into<Cookie<'static>>>(&self, cookie: C) {
let mut cookie = cookie.into();
Self::set_removal_defaults(&mut cookie);
self.ops.lock().push(Op::Remove(cookie, false));
}
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn remove_private<C: Into<Cookie<'static>>>(&self, cookie: C) {
let mut cookie = cookie.into();
Self::set_removal_defaults(&mut cookie);
self.ops.lock().push(Op::Remove(cookie, true));
}
pub fn iter(&self) -> impl Iterator<Item=&Cookie<'static>> {
self.jar.iter()
}
#[inline(always)]
pub(crate) fn reset_delta(&self) {
self.ops.lock().clear();
}
pub(crate) fn take_delta_jar(&self) -> cookie::CookieJar {
let ops = std::mem::take(&mut *self.ops.lock());
let mut jar = cookie::CookieJar::new();
for op in ops {
match op {
Op::Add(c, false) => jar.add(c),
#[cfg(feature = "secrets")]
Op::Add(c, true) => {
jar.private_mut(&self.config.secret_key.key).add(c);
}
Op::Remove(mut c, _) => {
if self.jar.get(c.name()).is_some() {
c.make_removal();
jar.add(c);
} else {
jar.remove(c);
}
}
#[allow(unreachable_patterns)]
_ => unreachable!()
}
}
jar
}
#[inline(always)]
pub(crate) fn add_original(&mut self, cookie: Cookie<'static>) {
self.jar.add_original(cookie)
}
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
#[inline(always)]
pub(crate) fn add_original_private(&mut self, cookie: Cookie<'static>) {
self.jar.private_mut(&self.config.secret_key.key).add_original(cookie);
}
fn set_defaults(config: &Config, cookie: &mut Cookie<'static>) {
if cookie.path().is_none() {
cookie.set_path("/");
}
if cookie.same_site().is_none() {
cookie.set_same_site(SameSite::Strict);
}
if cookie.secure().is_none() && config.tls_enabled() {
cookie.set_secure(true);
}
}
fn set_removal_defaults(cookie: &mut Cookie<'static>) {
if cookie.path().is_none() {
cookie.set_path("/");
}
if cookie.same_site().is_none() {
cookie.set_same_site(SameSite::Lax);
}
}
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
fn set_private_defaults(config: &Config, cookie: &mut Cookie<'static>) {
if cookie.path().is_none() {
cookie.set_path("/");
}
if cookie.same_site().is_none() {
cookie.set_same_site(SameSite::Strict);
}
if cookie.http_only().is_none() {
cookie.set_http_only(true);
}
if cookie.expires().is_none() {
cookie.set_expires(time::OffsetDateTime::now_utc() + time::Duration::weeks(1));
}
if cookie.secure().is_none() && config.tls_enabled() {
cookie.set_secure(true);
}
}
}
impl fmt::Debug for CookieJar<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pending: Vec<_> = self.ops.lock()
.iter()
.map(|c| c.cookie())
.cloned()
.collect();
f.debug_struct("CookieJar")
.field("original", &self.jar)
.field("pending", &pending)
.finish()
}
}