Skip to main content

cookie_monster/cookie/
prefix.rs

1use std::borrow::Cow;
2
3use super::{Cookie, CookieBuilder};
4
5pub(crate) const HOST_PREFIX: &str = "__Host-";
6pub(crate) const SECURE_PREFIX: &str = "__Secure-";
7
8/// A recognized cookie name prefix as defined by
9/// [RFC 6265bis §4.1.3](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-4.1.3).
10///
11/// This is an internal detail: the flavour is stored on a [`Cookie`], set only by
12/// [`Cookie::host`] / [`Cookie::secure`] and by parsing, and re-applied on serialization.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub(crate) enum CookiePrefix {
15    Host,
16    Secure,
17}
18
19impl CookiePrefix {
20    /// The literal prefix string that is prepended to the name on the wire.
21    pub(crate) const fn as_str(self) -> &'static str {
22        match self {
23            CookiePrefix::Host => HOST_PREFIX,
24            CookiePrefix::Secure => SECURE_PREFIX,
25        }
26    }
27}
28
29/// Splits a recognized prefix off the front of a parsed cookie name. Matching is
30/// case-sensitive per the spec, so `__host-` is not treated as a prefix. The returned name
31/// is the logical (unprefixed) name.
32pub(crate) fn split_prefix(name: Cow<'_, str>) -> (Option<CookiePrefix>, Cow<'_, str>) {
33    let (prefix, len) = if name.starts_with(HOST_PREFIX) {
34        (CookiePrefix::Host, HOST_PREFIX.len())
35    } else if name.starts_with(SECURE_PREFIX) {
36        (CookiePrefix::Secure, SECURE_PREFIX.len())
37    } else {
38        return (None, name);
39    };
40
41    let stripped = match name {
42        Cow::Borrowed(borrowed) => Cow::Borrowed(&borrowed[len..]),
43        Cow::Owned(mut owned) => {
44            owned.drain(..len);
45            Cow::Owned(owned)
46        }
47    };
48
49    (Some(prefix), stripped)
50}
51
52impl Cookie {
53    /// Builds a `__Host-` prefixed cookie.
54    ///
55    /// The `Secure` attribute is set and the `Path` attribute is set to `/`, and the `__Host-`
56    /// prefix is applied to the name on serialization. These are the attributes the prefix
57    /// requires per
58    /// [RFC 6265bis §4.1.3](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-4.1.3),
59    /// but they are only defaults: nothing stops you from changing the `Path`, adding a
60    /// `Domain` or clearing `Secure` afterwards to build a non-standard cookie.
61    ///
62    /// The `name` you pass is the logical name; the prefix is stored separately and is not
63    /// part of [`Cookie::name`].
64    ///
65    /// # Example
66    /// ```rust
67    /// use cookie_monster::Cookie;
68    ///
69    /// let cookie = Cookie::host("id", "abc").build();
70    ///
71    /// assert_eq!(cookie.name(), "id");
72    /// assert!(cookie.is_secure());
73    /// assert_eq!(cookie.path(), Some("/"));
74    /// assert_eq!(cookie.serialize().as_deref(), Ok("__Host-id=abc; Path=/; Secure"));
75    /// ```
76    pub fn host<N, V>(name: N, value: V) -> CookieBuilder
77    where
78        N: Into<Cow<'static, str>>,
79        V: Into<Cow<'static, str>>,
80    {
81        CookieBuilder::new(name, value)
82            .secure()
83            .path("/")
84            .with_prefix(CookiePrefix::Host)
85    }
86
87    /// Builds a `__Secure-` prefixed cookie.
88    ///
89    /// The `Secure` attribute is set, and the `__Secure-` prefix is applied to the name on
90    /// serialization. `Secure` is what the prefix requires per
91    /// [RFC 6265bis §4.1.3](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-4.1.3),
92    /// but it is only a default: you may clear it afterwards to build a non-standard cookie.
93    ///
94    /// The `name` you pass is the logical name; the prefix is stored separately and is not
95    /// part of [`Cookie::name`].
96    ///
97    /// # Example
98    /// ```rust
99    /// use cookie_monster::Cookie;
100    ///
101    /// let cookie = Cookie::secure("id", "abc").build();
102    ///
103    /// assert_eq!(cookie.name(), "id");
104    /// assert!(cookie.is_secure());
105    /// assert_eq!(cookie.serialize().as_deref(), Ok("__Secure-id=abc; Secure"));
106    /// ```
107    pub fn secure<N, V>(name: N, value: V) -> CookieBuilder
108    where
109        N: Into<Cow<'static, str>>,
110        V: Into<Cow<'static, str>>,
111    {
112        CookieBuilder::new(name, value)
113            .secure()
114            .with_prefix(CookiePrefix::Secure)
115    }
116}