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}