Closed jakajancar closed 1 year ago
Note that this RFC is the current active draft. I agree it would be nice to support this. I don't think a new constructor is quite what we want, however. Maybe we want a Prefix
enum that works a bit like SameSite
. I'd be open to reviewing such a design.
I would suggest starting purely with the design (i.e, a set of interface changes and desired semantics) and then we can talk about implementation.
I’m not actually sure what a good API is. You probably also want name()
to still equal the actual name of the cookie, incl. the prefix, so having prefix as completely separate (vs. just a convenience constructor/setter) is not a good idea.
Thinking about it more, I think the API we want is that of a prefixed child jar. This means we get the following API:
let mut jar: CookieJar; // some cookie jar
jar.prefixed(Host).add(Cookie::new("foo", "bar")); // prefix the name, set the required values, add to jar
jar.prefixed(Host).get("foo") // => cookie with ("foo", "bar")
jar.get("foo") // => cookie with ("__HOST-foo", "bar")
Here's an implementation (that I haven't tested or checked through thoroughly, at all):
use std::marker::PhantomData;
use std::borrow::{Borrow, BorrowMut, Cow};
use crate::{CookieJar, Cookie};
mod private {
pub trait Sealed {}
impl Sealed for super::Host {}
impl Sealed for super::Secure {}
}
pub struct Host;
impl Prefix for Host {
const PREFIX: &'static str = "__HOST-";
fn polish(mut cookie: Cookie<'_>) -> Cookie<'_> {
cookie.set_secure(true);
cookie.set_path("/");
cookie.unset_domain();
cookie
}
}
pub struct Secure;
impl Prefix for Secure {
const PREFIX: &'static str = "__SECURE-";
fn polish(mut cookie: Cookie<'_>) -> Cookie<'_> {
cookie.set_secure(true);
cookie
}
}
pub trait Prefix: private::Sealed {
#[doc(hidden)]
const PREFIX: &'static str;
fn polish(cookie: Cookie<'_>) -> Cookie<'_>;
#[doc(hidden)]
fn prefixed_name(name: &str) -> String {
format!("{}{name}", Self::PREFIX)
}
#[doc(hidden)]
fn prefix(mut cookie: Cookie<'_>) -> Cookie<'_> {
use crate::CookieStr;
cookie.name = CookieStr::Concrete(match cookie.name {
CookieStr::Concrete(Cow::Owned(mut string)) => {
string.insert_str(0, Self::PREFIX);
string.into()
}
_ => Self::prefixed_name(cookie.name()).into(),
});
cookie
}
#[doc(hidden)]
fn clip(mut cookie: Cookie<'_>) -> Cookie<'_> {
use std::borrow::Cow::*;
use crate::CookieStr::*;
debug_assert!(cookie.name().starts_with(Self::PREFIX));
if !cookie.name().starts_with(Self::PREFIX) {
return cookie;
}
let len = Self::PREFIX.len();
cookie.name = match cookie.name {
Indexed(i, j) => Indexed(i + len, j),
Concrete(Borrowed(v)) => Concrete(Borrowed(&v[len..])),
Concrete(Owned(v)) => Concrete(Owned(v[len..].to_string())),
};
cookie
}
#[doc(hidden)]
fn refine(cookie: Cookie<'_>) -> Cookie<'_> {
Self::polish(Self::prefix(cookie))
}
}
pub struct PrefixJar<P: Prefix, J> {
parent: J,
_prefix: PhantomData<P>,
}
impl<P: Prefix, J> PrefixJar<P, J> {
pub(crate) fn new(parent: J) -> Self {
Self { parent, _prefix: PhantomData }
}
}
impl<P: Prefix, J: Borrow<CookieJar>> PrefixJar<P, J> {
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
self.parent.borrow()
.get(&P::prefixed_name(name))
.map(|c| P::clip(c.clone()))
}
}
impl<P: Prefix, J: BorrowMut<CookieJar>> PrefixJar<P, J> {
pub fn add<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
self.parent.borrow_mut().add(P::refine(cookie.into()));
}
pub fn add_original<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
self.parent.borrow_mut().add_original(P::refine(cookie.into()));
}
pub fn remove<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
self.parent.borrow_mut().remove(P::refine(cookie.into()));
}
}
That is very clever, I agree. Like a virtual, transparently mapped namespace.
According to the spec:
It would be nice if there was
Cookie::new_host("name", "value")
or similar shortcut which would create a valid host cookie, i.e. prepend__Host-
, set the secure attribute, and set the path to/
.