cookie_monster/cookie/
mod.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Debug},
4    time::Duration,
5};
6
7mod builder;
8mod domain;
9pub(crate) mod expires;
10mod parse;
11mod path;
12pub(crate) mod same_site;
13mod serialize;
14
15#[cfg(feature = "percent-encode")]
16mod encoding;
17
18pub use builder::CookieBuilder;
19use expires::Expires;
20
21use crate::{SameSite, util::TinyStr};
22
23/// An HTTP Cookie.
24#[derive(Default, Clone)]
25pub struct Cookie {
26    // A read only buffer to the raw cookie value.
27    raw_value: Option<String>,
28    name: TinyStr,
29    value: TinyStr,
30    expires: Expires,
31    max_age: Option<u64>,
32    domain: Option<TinyStr>,
33    path: Option<TinyStr>,
34    secure: bool,
35    http_only: bool,
36    partitioned: bool,
37    same_site: Option<SameSite>,
38}
39
40impl Cookie {
41    /// Creates a new cookie with the given name and value.
42    ///
43    /// # Example
44    /// ```rust
45    /// use cookie_monster::Cookie;
46    ///
47    /// let cookie = Cookie::new("hello", "world");
48    ///
49    /// assert_eq!(cookie.name(), "hello");
50    /// assert_eq!(cookie.value(), "world");
51    /// ```
52    pub fn new<N, V>(name: N, value: V) -> Cookie
53    where
54        N: Into<Cow<'static, str>>,
55        V: Into<Cow<'static, str>>,
56    {
57        Self::new_inner(TinyStr::from(name), TinyStr::from(value))
58    }
59
60    /// Creates a cookie that can be used to remove the cookie from the user-agent. This sets the
61    /// Expires attribute in the past and Max-Age to 0 seconds.
62    ///
63    /// **To ensure a cookie is removed from the user-agent, set the `Path` and `Domain` attributes
64    /// with the same values that were used to create the cookie.**
65    ///
66    /// # Note
67    /// You don't have to use this method with [`crate::CookieJar::remove`], the jar automatically set's
68    /// the Expires and Max-Age attributes.
69    ///
70    /// # Example
71    /// ```rust
72    /// use cookie_monster::Cookie;
73    ///
74    /// let cookie = Cookie::remove("session");
75    ///
76    /// assert_eq!(cookie.max_age_secs(), Some(0));
77    /// assert!(!cookie.is_expires_set());
78    /// ```
79    pub fn remove<N>(name: N) -> Cookie
80    where
81        N: Into<Cow<'static, str>>,
82    {
83        Cookie::new(name, "").into_remove()
84    }
85
86    pub(crate) fn into_remove(mut self) -> Self {
87        self.set_expires(Expires::remove());
88        self.set_max_age_secs(0);
89        self.set_value("");
90        self
91    }
92
93    fn new_inner(name: TinyStr, value: TinyStr) -> Cookie {
94        Cookie {
95            name,
96            value,
97            ..Default::default()
98        }
99    }
100
101    /// Build a new cookie. This returns a `CookieBuilder` that can be used to set other attribute
102    /// values.
103    ///
104    /// # Example
105    /// ```rust
106    /// use cookie_monster::Cookie;
107    ///
108    /// let cookie = Cookie::build("foo", "bar")
109    ///     .secure()
110    ///     .http_only()
111    ///     .build();
112    ///
113    /// assert!(cookie.secure());
114    /// assert!(cookie.http_only());
115    /// ```
116    pub fn build<N, V>(name: N, value: V) -> CookieBuilder
117    where
118        N: Into<Cow<'static, str>>,
119        V: Into<Cow<'static, str>>,
120    {
121        CookieBuilder::new(name, value)
122    }
123
124    /// Creates a [`CookieBuilder`] with the given name and an empty value. This can be used when
125    /// removing a cookie from a [`crate::CookieJar`].
126    ///
127    /// # Example
128    /// ```rust
129    /// use cookie_monster::{Cookie, CookieJar};
130    ///
131    /// let mut jar = CookieJar::empty();
132    /// jar.remove(Cookie::named("session").path("/login"));
133    ///
134    /// assert!(jar.get("session").is_none());
135    /// ```
136    pub fn named<N>(name: N) -> CookieBuilder
137    where
138        N: Into<Cow<'static, str>>,
139    {
140        Self::build(name, "")
141    }
142
143    /// Returns the cookie name.
144    #[inline]
145    pub fn name(&self) -> &str {
146        self.name.as_str(self.raw_value.as_deref())
147    }
148
149    /// Set the cookie name.
150    #[inline]
151    pub fn set_name<N: Into<Cow<'static, str>>>(&mut self, name: N) {
152        self.name = TinyStr::from(name)
153    }
154
155    /// Get the cookie value. This does not trim `"` characters.
156    #[inline]
157    pub fn value(&self) -> &str {
158        self.value.as_str(self.raw_value.as_deref())
159    }
160
161    /// Set the cookie value.
162    #[inline]
163    pub fn set_value<V: Into<Cow<'static, str>>>(&mut self, value: V) {
164        self.value = TinyStr::from(value)
165    }
166
167    /// Set the Expired attribute.
168    #[inline]
169    pub fn set_expires<E: Into<Expires>>(&mut self, expires: E) {
170        self.expires = expires.into();
171    }
172
173    /// Get the Max-Age duration. This returns a `std::time::Duration`, if you'd like a `time`,
174    /// `chrono` or `jiff` specific duration use the `max_age_{time,chrono,jiff}` method.
175    #[inline]
176    pub fn max_age(&self) -> Option<Duration> {
177        self.max_age.map(Duration::from_secs)
178    }
179
180    /// Get the Max-Age as seconds.
181    #[inline]
182    pub fn max_age_secs(&self) -> Option<u64> {
183        self.max_age
184    }
185
186    /// Set the Max-Age attribute.
187    #[inline]
188    pub fn set_max_age(&mut self, max_age: Duration) {
189        self.set_max_age_secs(max_age.as_secs());
190    }
191
192    /// Set the Max-Age value in seconds.
193    #[inline]
194    pub fn set_max_age_secs(&mut self, max_age_secs: u64) {
195        self.max_age = Some(max_age_secs);
196    }
197
198    /// Removes the Max-Age attribute.
199    #[inline]
200    pub fn unset_max_age(&mut self) {
201        self.max_age = None;
202    }
203
204    /// Returns the Domain attribute if it's set.
205    #[inline]
206    pub fn domain(&self) -> Option<&str> {
207        self.domain
208            .as_ref()
209            .map(|s| s.as_str(self.raw_value.as_deref()))
210    }
211
212    pub(crate) fn domain_sanitized(&self) -> Option<&str> {
213        self.domain().map(|d| d.strip_prefix('.').unwrap_or(d))
214    }
215
216    /// Set the Domain attribute.
217    #[inline]
218    pub fn set_domain<D: Into<Cow<'static, str>>>(&mut self, domain: D) {
219        self.domain = Some(TinyStr::from(domain))
220    }
221
222    /// Removes the Domain attribute.
223    #[inline]
224    pub fn unset_domain(&mut self) {
225        self.domain = None
226    }
227
228    /// Returns the Path attribute if it's set.
229    #[inline]
230    pub fn path(&self) -> Option<&str> {
231        self.path
232            .as_ref()
233            .map(|val| val.as_str(self.raw_value.as_deref()))
234    }
235
236    /// Set the Path attribute.
237    #[inline]
238    pub fn set_path<D: Into<Cow<'static, str>>>(&mut self, path: D) {
239        self.path = Some(TinyStr::from(path))
240    }
241
242    /// Removes the path attribute.
243    #[inline]
244    pub fn unset_path(&mut self) {
245        self.path = None
246    }
247
248    /// Returns if the Secure attribute is set.
249    #[inline]
250    pub fn secure(&self) -> bool {
251        self.secure
252    }
253
254    /// Sets the Secure attribute of the cookie.
255    #[inline]
256    pub fn set_secure(&mut self, secure: bool) {
257        self.secure = secure
258    }
259
260    /// Returns if the HttpOnly attribute is set.
261    #[inline]
262    pub fn http_only(&self) -> bool {
263        self.http_only
264    }
265
266    /// Sets the HttpOnly attribute of the cookie.
267    #[inline]
268    pub fn set_http_only(&mut self, http_only: bool) {
269        self.http_only = http_only
270    }
271
272    /// Returns if the Partitioned attribute is set.
273    #[inline]
274    pub fn partitioned(&self) -> bool {
275        self.partitioned
276    }
277
278    /// Set the Partitioned flag, enabling the Partitioned attribute also enables the Secure Attribute.
279    #[inline]
280    pub fn set_partitioned(&mut self, partitioned: bool) {
281        self.partitioned = partitioned;
282    }
283
284    /// Returns the SameSite attribute if it is set.
285    #[inline]
286    pub fn same_site(&self) -> Option<SameSite> {
287        self.same_site
288    }
289
290    /// Set the SameSite attribute.
291    #[inline]
292    pub fn set_same_site<S: Into<Option<SameSite>>>(&mut self, same_site: S) {
293        self.same_site = same_site.into();
294    }
295}
296
297impl fmt::Display for Cookie {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        write!(f, "{:?}", self)
300    }
301}
302
303impl Debug for Cookie {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        let mut debug = f.debug_struct("Cookie");
306
307        debug
308            .field("name", &self.name())
309            .field("value", &self.value())
310            .field("max_age", &self.max_age())
311            .field("domain", &self.domain())
312            .field("path", &self.path())
313            .field("secure", &self.secure())
314            .field("http_only", &self.http_only())
315            .field("partitioned", &self.partitioned())
316            .field("expires", &self.expires)
317            .finish()
318    }
319}
320
321impl PartialEq<Cookie> for Cookie {
322    fn eq(&self, other: &Cookie) -> bool {
323        if self.name() != other.name()
324            || self.value() != other.value()
325            || self.secure() != other.secure()
326            || self.http_only() != other.http_only()
327            || self.partitioned() != other.partitioned()
328            || self.max_age() != other.max_age()
329            || self.same_site() != other.same_site()
330            || self.expires != other.expires
331        {
332            return false;
333        }
334
335        if !opt_str_eq(self.domain_sanitized(), other.domain_sanitized()) {
336            return false;
337        }
338
339        if !opt_str_eq(self.path(), other.path()) {
340            return false;
341        }
342
343        true
344    }
345}
346
347fn opt_str_eq(left: Option<&str>, right: Option<&str>) -> bool {
348    match (left, right) {
349        (None, None) => true,
350        (Some(l), Some(r)) => l.eq_ignore_ascii_case(r),
351        _ => false,
352    }
353}