Skip to main content

cookie_rs/
cookie.rs

1use std::borrow::{Borrow, Cow};
2use std::fmt;
3use std::time::Duration;
4
5pub use self::builder::CookieBuilder;
6use crate::StringPrison;
7
8#[cfg(feature = "percent-encoding")]
9const COOKIE_VALUE_ENCODE_SET: percent_encoding::AsciiSet = percent_encoding::CONTROLS
10    .add(b' ')
11    .add(b'"')
12    .add(b'%')
13    .add(b',')
14    .add(b';')
15    .add(b'\\');
16
17pub mod builder;
18pub mod parse;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum SameSite {
22    Strict,
23    Lax,
24    None,
25}
26
27/// Represents an HTTP cookie, including attributes such as domain, path, and expiration.
28#[derive(Debug, Clone)]
29pub struct Cookie<'a> {
30    prison: Option<StringPrison<'a>>,
31    name: Cow<'a, str>,
32    value: Cow<'a, str>,
33    domain: Option<Cow<'a, str>>,
34    expires: Option<Cow<'a, str>>,
35    http_only: Option<bool>,
36    max_age: Option<Duration>,
37    partitioned: Option<bool>,
38    path: Option<Cow<'a, str>>,
39    same_site: Option<SameSite>,
40    secure: Option<bool>,
41}
42
43impl<'a> Cookie<'a> {
44    /// Creates a new `Cookie` with the specified name and value.
45    ///
46    /// # Arguments
47    /// - `name`: The name of the cookie.
48    /// - `value`: The value of the cookie.
49    ///
50    /// # Example
51    /// ```
52    /// use cookie_rs::prelude::*;
53    ///
54    /// let cookie = Cookie::new("session", "abc123");
55    /// assert_eq!(cookie.name(), "session");
56    /// assert_eq!(cookie.value(), "abc123");
57    /// ```
58    pub fn new<N, V>(name: N, value: V) -> Self
59    where
60        N: Into<Cow<'a, str>>,
61        V: Into<Cow<'a, str>>,
62    {
63        Self {
64            name: name.into(),
65            value: value.into(),
66            ..Default::default()
67        }
68    }
69
70    /// Creates a `CookieBuilder` for constructing a `Cookie` with additional attributes.
71    ///
72    /// # Arguments
73    /// - `name`: The name of the cookie.
74    /// - `value`: The value of the cookie.
75    ///
76    /// # Example
77    /// ```
78    /// use cookie_rs::prelude::*;
79    ///
80    /// let cookie = Cookie::builder("session", "abc123")
81    ///     .domain("example.com")
82    ///     .secure(true)
83    ///     .build();
84    /// ```
85    pub fn builder<N, V>(name: N, value: V) -> CookieBuilder<'a>
86    where
87        N: Into<Cow<'a, str>>,
88        V: Into<Cow<'a, str>>,
89    {
90        CookieBuilder::new(name, value)
91    }
92
93    /// Sets the domain for the cookie.
94    ///
95    /// # Arguments
96    /// - `domain`: The domain attribute of the cookie.
97    ///
98    /// # Example
99    /// ```
100    /// use cookie_rs::prelude::*;
101    ///
102    /// let mut cookie = Cookie::new("session", "abc123");
103    /// cookie.set_domain("example.com");
104    /// assert_eq!(cookie.domain(), Some("example.com"));
105    /// ```
106    pub fn set_domain<V: Into<Cow<'a, str>>>(&mut self, domain: V) {
107        self.domain = Some(domain.into())
108    }
109
110    /// Sets the expiration date for the cookie.
111    ///
112    /// # Arguments
113    /// - `expires`: The expiration date of the cookie.
114    ///
115    /// # Example
116    /// ```
117    /// use cookie_rs::prelude::*;
118    ///
119    /// let mut cookie = Cookie::new("session", "abc123");
120    /// cookie.set_expires("Wed, 21 Oct 2025 07:28:00 GMT");
121    /// assert_eq!(cookie.expires(), Some("Wed, 21 Oct 2025 07:28:00 GMT"));
122    /// ```
123    pub fn set_expires<V: Into<Cow<'a, str>>>(&mut self, expires: V) {
124        self.expires = Some(expires.into());
125    }
126
127    /// Sets the `HttpOnly` attribute for the cookie.
128    ///
129    /// # Arguments
130    /// - `http_only`: Whether the cookie is HttpOnly.
131    ///
132    /// # Example
133    /// ```
134    /// use cookie_rs::prelude::*;
135    ///
136    /// let mut cookie = Cookie::new("session", "abc123");
137    /// cookie.set_http_only(true);
138    /// assert_eq!(cookie.http_only(), Some(true));
139    /// ```
140    pub fn set_http_only(&mut self, http_only: bool) {
141        self.http_only = Some(http_only);
142    }
143
144    /// Sets the maximum age for the cookie.
145    ///
146    /// # Arguments
147    /// - `max_age`: The maximum age of the cookie as a `Duration`.
148    ///
149    /// # Example
150    /// ```
151    /// use std::time::Duration;
152    /// use cookie_rs::prelude::*;
153    ///
154    /// let mut cookie = Cookie::new("session", "abc123");
155    /// cookie.set_max_age(Duration::from_secs(3600));
156    /// assert_eq!(cookie.max_age(), Some(Duration::from_secs(3600)));
157    /// ```
158    pub fn set_max_age<V: Into<Duration>>(&mut self, max_age: V) {
159        self.max_age = Some(max_age.into());
160    }
161
162    /// Sets the partitioned attribute for the cookie.
163    ///
164    /// # Arguments
165    /// - `partitioned`: Whether the cookie is partitioned.
166    ///
167    /// # Example
168    /// ```
169    /// use cookie_rs::prelude::*;
170    ///
171    /// let mut cookie = Cookie::new("session", "abc123");
172    /// cookie.set_partitioned(true);
173    /// assert_eq!(cookie.partitioned(), Some(true));
174    /// ```
175    pub fn set_partitioned(&mut self, partitioned: bool) {
176        self.partitioned = Some(partitioned);
177    }
178
179    /// Sets the path attribute for the cookie.
180    ///
181    /// # Arguments
182    /// - `path`: The path attribute of the cookie.
183    ///
184    /// # Example
185    /// ```
186    /// use cookie_rs::prelude::*;
187    ///
188    /// let mut cookie = Cookie::new("session", "abc123");
189    /// cookie.set_path("/");
190    /// assert_eq!(cookie.path(), Some("/"));
191    /// ```
192    pub fn set_path<V: Into<Cow<'a, str>>>(&mut self, path: V) {
193        self.path = Some(path.into());
194    }
195
196    /// Sets the `SameSite` attribute for the cookie.
197    ///
198    /// # Arguments
199    /// - `same_site`: The `SameSite` attribute for the cookie.
200    ///
201    /// # Example
202    /// ```
203    /// use cookie_rs::prelude::*;
204    ///
205    /// let mut cookie = Cookie::new("session", "abc123");
206    /// cookie.set_same_site(SameSite::Lax);
207    /// assert_eq!(cookie.same_site(), Some(SameSite::Lax));
208    /// ```
209    pub fn set_same_site(&mut self, same_site: SameSite) {
210        self.same_site = Some(same_site);
211    }
212
213    /// Sets the `Secure` attribute for the cookie.
214    ///
215    /// # Arguments
216    /// - `secure`: Whether the cookie is secure.
217    ///
218    /// # Example
219    /// ```
220    /// use cookie_rs::prelude::*;
221    ///
222    /// let mut cookie = Cookie::new("session", "abc123");
223    /// cookie.set_secure(true);
224    /// assert_eq!(cookie.secure(), Some(true));
225    /// ```
226    pub fn set_secure(&mut self, secure: bool) {
227        self.secure = Some(secure);
228    }
229
230    /// Sets the domain for the cookie.
231    ///
232    /// # Arguments
233    /// - `domain`: The domain attribute of the cookie.
234    ///
235    /// # Example
236    /// ```
237    /// use cookie_rs::prelude::*;
238    ///
239    /// let cookie = Cookie::new("session", "abc123").with_domain("example.com");
240    ///
241    /// assert_eq!(cookie.domain(), Some("example.com"));
242    /// ```
243    pub fn with_domain<V: Into<Cow<'a, str>>>(mut self, domain: V) -> Self {
244        self.set_domain(domain);
245
246        self
247    }
248
249    /// Sets the expiration date for the cookie.
250    ///
251    /// # Arguments
252    /// - `expires`: The expiration date of the cookie.
253    ///
254    /// # Example
255    /// ```
256    /// use cookie_rs::prelude::*;
257    ///
258    /// let cookie = Cookie::new("session", "abc123").with_expires("Wed, 21 Oct 2025 07:28:00 GMT");
259    ///
260    /// assert_eq!(cookie.expires(), Some("Wed, 21 Oct 2025 07:28:00 GMT"));
261    /// ```
262    pub fn with_expires<V: Into<Cow<'a, str>>>(mut self, expires: V) -> Self {
263        self.set_expires(expires);
264
265        self
266    }
267
268    /// Sets the `HttpOnly` attribute for the cookie.
269    ///
270    /// # Arguments
271    /// - `http_only`: Whether the cookie is HttpOnly.
272    ///
273    /// # Example
274    /// ```
275    /// use cookie_rs::prelude::*;
276    ///
277    /// let cookie = Cookie::new("session", "abc123").with_http_only(true);
278    ///
279    /// assert_eq!(cookie.http_only(), Some(true));
280    /// ```
281    pub fn with_http_only(mut self, http_only: bool) -> Self {
282        self.set_http_only(http_only);
283
284        self
285    }
286
287    /// Sets the maximum age for the cookie.
288    ///
289    /// # Arguments
290    /// - `max_age`: The maximum age of the cookie as a `Duration`.
291    ///
292    /// # Example
293    /// ```
294    /// use std::time::Duration;
295    /// use cookie_rs::prelude::*;
296    ///
297    /// let cookie = Cookie::new("session", "abc123").with_max_age(Duration::from_secs(3600));
298    ///
299    /// assert_eq!(cookie.max_age(), Some(Duration::from_secs(3600)));
300    /// ```
301    pub fn with_max_age<V: Into<Duration>>(mut self, max_age: V) -> Self {
302        self.set_max_age(max_age);
303
304        self
305    }
306
307    /// Sets the partitioned attribute for the cookie.
308    ///
309    /// # Arguments
310    /// - `partitioned`: Whether the cookie is partitioned.
311    ///
312    /// # Example
313    /// ```
314    /// use cookie_rs::prelude::*;
315    ///
316    /// let cookie = Cookie::new("session", "abc123").with_partitioned(true);
317    ///
318    /// assert_eq!(cookie.partitioned(), Some(true));
319    /// ```
320    pub fn with_partitioned(mut self, partitioned: bool) -> Self {
321        self.set_partitioned(partitioned);
322
323        self
324    }
325
326    /// Sets the `Secure` attribute for the cookie.
327    ///
328    /// # Arguments
329    /// - `secure`: Whether the cookie is secure.
330    ///
331    /// # Example
332    /// ```
333    /// use cookie_rs::prelude::*;
334    ///
335    /// let cookie = Cookie::new("session", "abc123").with_secure(true);
336    ///
337    /// assert_eq!(cookie.secure(), Some(true));
338    /// ```
339    pub fn with_secure(mut self, secure: bool) -> Self {
340        self.set_secure(secure);
341
342        self
343    }
344
345    /// Sets the path attribute for the cookie.
346    ///
347    /// # Arguments
348    /// - `path`: The path attribute of the cookie.
349    ///
350    /// # Example
351    /// ```
352    /// use cookie_rs::prelude::*;
353    ///
354    /// let cookie = Cookie::new("session", "abc123").with_path("/");
355    ///
356    /// assert_eq!(cookie.path(), Some("/"));
357    /// ```
358    pub fn with_path<V: Into<Cow<'a, str>>>(mut self, path: V) -> Self {
359        self.set_path(path);
360
361        self
362    }
363
364    /// Sets the `SameSite` attribute for the cookie.
365    ///
366    /// # Arguments
367    /// - `same_site`: The `SameSite` attribute for the cookie.
368    ///
369    /// # Example
370    /// ```
371    /// use cookie_rs::prelude::*;
372    ///
373    /// let cookie = Cookie::new("session", "abc123").with_same_site(SameSite::Lax);
374    ///
375    /// assert_eq!(cookie.same_site(), Some(SameSite::Lax));
376    /// ```
377    pub fn with_same_site(mut self, same_site: SameSite) -> Self {
378        self.set_same_site(same_site);
379
380        self
381    }
382
383    /// Returns the name of the cookie.
384    ///
385    /// # Example
386    /// ```
387    /// use cookie_rs::prelude::*;
388    ///
389    /// let cookie = Cookie::new("session", "abc123");
390    /// assert_eq!(cookie.name(), "session");
391    /// ```
392    pub fn name(&self) -> &str {
393        self.name.as_ref()
394    }
395
396    /// Returns the value of the cookie.
397    ///
398    /// # Example
399    /// ```
400    /// use cookie_rs::prelude::*;
401    ///
402    /// let cookie = Cookie::new("session", "abc123");
403    /// assert_eq!(cookie.value(), "abc123");
404    /// ```
405    pub fn value(&self) -> &str {
406        self.value.as_ref()
407    }
408
409    /// Returns the domain of the cookie, if set.
410    ///
411    /// # Example
412    /// ```
413    /// use cookie_rs::prelude::*;
414    ///
415    /// let mut cookie = Cookie::new("session", "abc123");
416    /// cookie.set_domain("example.com");
417    /// assert_eq!(cookie.domain(), Some("example.com"));
418    /// ```
419    pub fn domain(&self) -> Option<&str> {
420        self.domain.as_deref()
421    }
422
423    /// Returns the expiration date of the cookie, if set.
424    ///
425    /// # Example
426    /// ```
427    /// use cookie_rs::prelude::*;
428    ///
429    /// let mut cookie = Cookie::new("session", "abc123");
430    /// cookie.set_expires("Wed, 21 Oct 2025 07:28:00 GMT");
431    /// assert_eq!(cookie.expires(), Some("Wed, 21 Oct 2025 07:28:00 GMT"));
432    /// ```
433    pub fn expires(&self) -> Option<&str> {
434        self.expires.as_deref()
435    }
436
437    /// Returns whether the cookie has the `HttpOnly` attribute set.
438    ///
439    /// # Example
440    /// ```
441    /// use cookie_rs::prelude::*;
442    ///
443    /// let mut cookie = Cookie::new("session", "abc123");
444    /// cookie.set_http_only(true);
445    /// assert_eq!(cookie.http_only(), Some(true));
446    /// ```
447    pub fn http_only(&self) -> Option<bool> {
448        self.http_only
449    }
450
451    /// Returns the maximum age of the cookie, if set.
452    ///
453    /// # Example
454    /// ```
455    /// use std::time::Duration;
456    /// use cookie_rs::prelude::*;
457    ///
458    /// let mut cookie = Cookie::new("session", "abc123");
459    /// cookie.set_max_age(Duration::from_secs(3600));
460    /// assert_eq!(cookie.max_age(), Some(Duration::from_secs(3600)));
461    /// ```
462    pub fn max_age(&self) -> Option<Duration> {
463        self.max_age
464    }
465
466    /// Returns whether the cookie is partitioned.
467    ///
468    /// # Example
469    /// ```
470    /// use cookie_rs::prelude::*;
471    ///
472    /// let mut cookie = Cookie::new("session", "abc123");
473    /// cookie.set_partitioned(true);
474    /// assert_eq!(cookie.partitioned(), Some(true));
475    /// ```
476    pub fn partitioned(&self) -> Option<bool> {
477        self.partitioned
478    }
479
480    /// Returns the path of the cookie, if set.
481    ///
482    /// # Example
483    /// ```
484    /// use cookie_rs::prelude::*;
485    ///
486    /// let mut cookie = Cookie::new("session", "abc123");
487    /// cookie.set_path("/");
488    /// assert_eq!(cookie.path(), Some("/"));
489    /// ```
490    pub fn path(&self) -> Option<&str> {
491        self.path.as_deref()
492    }
493
494    /// Returns the `SameSite` attribute of the cookie, if set.
495    ///
496    /// # Example
497    /// ```
498    /// use cookie_rs::prelude::*;
499    ///
500    /// let mut cookie = Cookie::new("session", "abc123");
501    /// cookie.set_same_site(SameSite::Lax);
502    /// assert_eq!(cookie.same_site(), Some(SameSite::Lax));
503    /// ```
504    pub fn same_site(&self) -> Option<SameSite> {
505        self.same_site
506    }
507
508    /// Returns whether the cookie has the `Secure` attribute set.
509    ///
510    /// # Example
511    /// ```
512    /// use cookie_rs::prelude::*;
513    ///
514    /// let mut cookie = Cookie::new("session", "abc123");
515    /// cookie.set_secure(true);
516    /// assert_eq!(cookie.secure(), Some(true));
517    /// ```
518    pub fn secure(&self) -> Option<bool> {
519        self.secure
520    }
521
522    /// Converts the cookie into an owned version with a `'static` lifetime.
523    ///
524    /// # Example
525    /// ```
526    /// use cookie_rs::prelude::*;
527    ///
528    /// let input = String::from("session=abc123; Path=/");
529    /// let cookie: Cookie<'static> = Cookie::parse(input).unwrap().into_owned();
530    ///
531    /// assert_eq!(cookie.name(), "session");
532    /// assert_eq!(cookie.path(), Some("/"));
533    /// ```
534    pub fn into_owned(self) -> Cookie<'static> {
535        Cookie {
536            prison: None,
537            name: Cow::Owned(self.name.into_owned()),
538            value: Cow::Owned(self.value.into_owned()),
539            domain: self.domain.map(|v| Cow::Owned(v.into_owned())),
540            expires: self.expires.map(|v| Cow::Owned(v.into_owned())),
541            http_only: self.http_only,
542            max_age: self.max_age,
543            partitioned: self.partitioned,
544            path: self.path.map(|v| Cow::Owned(v.into_owned())),
545            same_site: self.same_site,
546            secure: self.secure,
547        }
548    }
549}
550
551impl PartialEq for Cookie<'_> {
552    fn eq(&self, other: &Self) -> bool {
553        match (self.domain.as_ref(), other.domain.as_ref()) {
554            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => (),
555            (None, None) => (),
556            _ => return false,
557        }
558
559        match (self.path.as_ref(), other.path.as_ref()) {
560            (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => (),
561            (None, None) => (),
562            _ => return false,
563        }
564
565        self.name == other.name
566            && self.value == other.value
567            && self.expires == other.expires
568            && self.http_only == other.http_only
569            && self.max_age == other.max_age
570            && self.partitioned == other.partitioned
571            && self.same_site == other.same_site
572            && self.secure == other.secure
573    }
574}
575
576impl Eq for Cookie<'_> {}
577
578impl PartialOrd for Cookie<'_> {
579    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
580        Some(self.cmp(other))
581    }
582}
583
584impl Ord for Cookie<'_> {
585    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
586        self.name.cmp(&other.name)
587    }
588}
589
590impl Borrow<str> for Cookie<'_> {
591    fn borrow(&self) -> &str {
592        self.name()
593    }
594}
595
596impl<'a> From<&'a str> for Cookie<'a> {
597    fn from(value: &'a str) -> Self {
598        Cookie::new(value, "")
599    }
600}
601
602impl<'a> From<(&'a str, &'a str)> for Cookie<'a> {
603    fn from(value: (&'a str, &'a str)) -> Self {
604        Cookie::new(value.0, value.1)
605    }
606}
607
608impl std::str::FromStr for Cookie<'_> {
609    type Err = parse::ParseError;
610
611    fn from_str(s: &str) -> Result<Self, Self::Err> {
612        Cookie::parse(s.to_owned())
613    }
614}
615
616impl fmt::Display for Cookie<'_> {
617    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618        #[cfg(not(feature = "percent-encoding"))]
619        write!(f, "{}={}", self.name, self.value)?;
620        #[cfg(feature = "percent-encoding")]
621        write!(
622            f,
623            "{}={}",
624            self.name,
625            percent_encoding::utf8_percent_encode(&self.value, &COOKIE_VALUE_ENCODE_SET)
626        )?;
627
628        if let Some(domain) = self.domain.as_ref() {
629            write!(f, "; Domain={domain}")?;
630        }
631
632        if let Some(expires) = self.expires.as_ref() {
633            write!(f, "; Expires={expires}")?;
634        }
635
636        if self.http_only.is_some_and(|v| v) {
637            write!(f, "; HttpOnly")?;
638        }
639
640        if let Some(max_age) = self.max_age.as_ref() {
641            write!(f, "; Max-Age={}", max_age.as_secs())?;
642        }
643
644        if self.partitioned.is_some_and(|v| v) {
645            write!(f, "; Partitioned")?;
646        }
647
648        if let Some(path) = self.path.as_ref() {
649            write!(f, "; Path={path}")?;
650        }
651
652        if let Some(same_site) = self.same_site {
653            write!(f, "; SameSite={same_site}")?;
654        }
655
656        if self.secure.is_some_and(|v| v) {
657            write!(f, "; Secure")?;
658        }
659
660        Ok(())
661    }
662}
663
664impl fmt::Display for SameSite {
665    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
666        match self {
667            SameSite::Strict => write!(f, "Strict"),
668            SameSite::Lax => write!(f, "Lax"),
669            SameSite::None => write!(f, "None"),
670        }
671    }
672}
673
674impl Default for Cookie<'_> {
675    fn default() -> Self {
676        Self {
677            prison: None,
678            name: Cow::Borrowed(""),
679            value: Cow::Borrowed(""),
680            domain: None,
681            expires: None,
682            http_only: None,
683            max_age: None,
684            partitioned: None,
685            path: None,
686            same_site: None,
687            secure: None,
688        }
689    }
690}