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}