cookie/lib.rs
1//! HTTP cookie parsing and cookie jar management.
2//!
3//! This crates provides the [`Cookie`] type, representing an HTTP cookie, and
4//! the [`CookieJar`] type, which manages a collection of cookies for session
5//! management, recording changes as they are made, and optional automatic
6//! cookie encryption and signing.
7//!
8//! # Usage
9//!
10//! Add the following to the `[dependencies]` section of your `Cargo.toml`:
11//!
12//! ```toml
13//! cookie = "0.18"
14//! ```
15//!
16//! # Features
17//!
18//! This crate exposes several features, all of which are disabled by default:
19//!
20//! * **`percent-encode`**
21//!
22//! Enables _percent encoding and decoding_ of names and values in cookies.
23//!
24//! When this feature is enabled, the [`Cookie::encoded()`] and
25//! [`Cookie::parse_encoded()`] methods are available. The `encoded` method
26//! returns a wrapper around a `Cookie` whose `Display` implementation
27//! percent-encodes the name and value of the cookie. The `parse_encoded`
28//! method percent-decodes the name and value of a `Cookie` during parsing.
29//!
30//! * **`signed`**
31//!
32//! Enables _signed_ cookies via [`CookieJar::signed()`].
33//!
34//! When this feature is enabled, the [`CookieJar::signed()`] method,
35//! [`SignedJar`] type, and [`Key`] type are available. The jar acts as "child
36//! jar"; operations on the jar automatically sign and verify cookies as they
37//! are added and retrieved from the parent jar.
38//!
39//! * **`private`**
40//!
41//! Enables _private_ (authenticated, encrypted) cookies via
42//! [`CookieJar::private()`].
43//!
44//! When this feature is enabled, the [`CookieJar::private()`] method,
45//! [`PrivateJar`] type, and [`Key`] type are available. The jar acts as "child
46//! jar"; operations on the jar automatically encrypt and decrypt/authenticate
47//! cookies as they are added and retrieved from the parent jar.
48//!
49//! * **`key-expansion`**
50//!
51//! Enables _key expansion_ or _key derivation_ via [`Key::derive_from()`].
52//!
53//! When this feature is enabled, and either `signed` or `private` are _also_
54//! enabled, the [`Key::derive_from()`] method is available. The method can be
55//! used to derive a `Key` structure appropriate for use with signed and
56//! private jars from cryptographically valid key material that is shorter in
57//! length than the full key.
58//!
59//! * **`secure`**
60//!
61//! A meta-feature that simultaneously enables `signed`, `private`, and
62//! `key-expansion`.
63//!
64//! You can enable features via `Cargo.toml`:
65//!
66//! ```toml
67//! [dependencies.cookie]
68//! features = ["secure", "percent-encode"]
69//! ```
70
71#![cfg_attr(all(nightly, doc), feature(doc_cfg))]
72
73#![deny(missing_docs)]
74
75pub use time;
76
77mod builder;
78mod parse;
79mod jar;
80mod delta;
81mod same_site;
82mod expiration;
83
84/// Implementation of [HTTP RFC6265 draft] cookie prefixes.
85///
86/// [HTTP RFC6265 draft]:
87/// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#name-cookie-name-prefixes
88pub mod prefix;
89
90#[cfg(any(feature = "private", feature = "signed"))] #[macro_use] mod secure;
91#[cfg(any(feature = "private", feature = "signed"))] pub use secure::*;
92
93use std::borrow::Cow;
94use std::fmt;
95use std::str::FromStr;
96
97#[allow(unused_imports, deprecated)]
98use std::ascii::AsciiExt;
99
100use time::{Duration, OffsetDateTime, UtcOffset, macros::datetime};
101
102use crate::parse::parse_cookie;
103pub use crate::parse::ParseError;
104pub use crate::builder::CookieBuilder;
105pub use crate::jar::{CookieJar, Delta, Iter};
106pub use crate::same_site::*;
107pub use crate::expiration::*;
108
109#[derive(Debug, Clone)]
110enum CookieStr<'c> {
111 /// An string derived from indexes (start, end).
112 Indexed(usize, usize),
113 /// A string derived from a concrete string.
114 Concrete(Cow<'c, str>),
115}
116
117impl<'c> CookieStr<'c> {
118 /// Creates an indexed `CookieStr` that holds the start and end indices of
119 /// `needle` inside of `haystack`, if `needle` is a substring of `haystack`.
120 /// Otherwise returns `None`.
121 ///
122 /// The `needle` can later be retrieved via `to_str()`.
123 fn indexed(needle: &str, haystack: &str) -> Option<CookieStr<'static>> {
124 let haystack_start = haystack.as_ptr() as usize;
125 let needle_start = needle.as_ptr() as usize;
126
127 if needle_start < haystack_start {
128 return None;
129 }
130
131 if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
132 return None;
133 }
134
135 let start = needle_start - haystack_start;
136 let end = start + needle.len();
137 Some(CookieStr::Indexed(start, end))
138 }
139
140 /// Retrieves the string `self` corresponds to. If `self` is derived from
141 /// indices, the corresponding subslice of `string` is returned. Otherwise,
142 /// the concrete string is returned.
143 ///
144 /// # Panics
145 ///
146 /// Panics if `self` is an indexed string and `string` is None.
147 fn to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str {
148 match *self {
149 CookieStr::Indexed(i, j) => {
150 let s = string.expect("`Some` base string must exist when \
151 converting indexed str to str! (This is a module invariant.)");
152 &s[i..j]
153 },
154 CookieStr::Concrete(ref cstr) => &*cstr,
155 }
156 }
157
158 #[allow(clippy::ptr_arg)]
159 fn to_raw_str<'s, 'b: 's>(&'s self, string: &'s Cow<'b, str>) -> Option<&'b str> {
160 match *self {
161 CookieStr::Indexed(i, j) => {
162 match *string {
163 Cow::Borrowed(s) => Some(&s[i..j]),
164 Cow::Owned(_) => None,
165 }
166 },
167 CookieStr::Concrete(_) => None,
168 }
169 }
170
171 fn into_owned(self) -> CookieStr<'static> {
172 use crate::CookieStr::*;
173
174 match self {
175 Indexed(a, b) => Indexed(a, b),
176 Concrete(Cow::Owned(c)) => Concrete(Cow::Owned(c)),
177 Concrete(Cow::Borrowed(c)) => Concrete(Cow::Owned(c.into())),
178 }
179 }
180}
181
182/// Representation of an HTTP cookie.
183///
184/// ## Constructing a `Cookie`
185///
186/// To construct a cookie with only a name/value, use [`Cookie::new()`]:
187///
188/// ```rust
189/// use cookie::Cookie;
190///
191/// let cookie = Cookie::new("name", "value");
192/// assert_eq!(cookie.to_string(), "name=value");
193/// ```
194///
195/// ## Building a `Cookie`
196///
197/// To construct more elaborate cookies, use [`Cookie::build()`] and
198/// [`CookieBuilder`] methods. `Cookie::build()` accepts any type that
199/// implements `T: Into<Cookie>`. See [`Cookie::build()`] for details.
200///
201/// ```rust
202/// use cookie::Cookie;
203///
204/// let cookie = Cookie::build(("name", "value"))
205/// .domain("www.rust-lang.org")
206/// .path("/")
207/// .secure(true)
208/// .http_only(true);
209///
210/// # let mut jar = cookie::CookieJar::new();
211/// jar.add(cookie);
212/// jar.remove(Cookie::build("name").path("/"));
213/// ```
214#[derive(Debug, Clone)]
215pub struct Cookie<'c> {
216 /// Storage for the cookie string. Only used if this structure was derived
217 /// from a string that was subsequently parsed.
218 cookie_string: Option<Cow<'c, str>>,
219 /// The cookie's name.
220 name: CookieStr<'c>,
221 /// The cookie's value.
222 value: CookieStr<'c>,
223 /// The cookie's expiration, if any.
224 expires: Option<Expiration>,
225 /// The cookie's maximum age, if any.
226 max_age: Option<Duration>,
227 /// The cookie's domain, if any.
228 domain: Option<CookieStr<'c>>,
229 /// The cookie's path domain, if any.
230 path: Option<CookieStr<'c>>,
231 /// Whether this cookie was marked Secure.
232 secure: Option<bool>,
233 /// Whether this cookie was marked HttpOnly.
234 http_only: Option<bool>,
235 /// The draft `SameSite` attribute.
236 same_site: Option<SameSite>,
237 /// The draft `Partitioned` attribute.
238 partitioned: Option<bool>,
239}
240
241impl<'c> Cookie<'c> {
242 /// Creates a new `Cookie` with the given name and value.
243 ///
244 /// # Example
245 ///
246 /// ```rust
247 /// use cookie::Cookie;
248 ///
249 /// let cookie = Cookie::new("name", "value");
250 /// assert_eq!(cookie.name_value(), ("name", "value"));
251 ///
252 /// // This is equivalent to `from` with a `(name, value)` tuple:
253 /// let cookie = Cookie::from(("name", "value"));
254 /// assert_eq!(cookie.name_value(), ("name", "value"));
255 /// ```
256 pub fn new<N, V>(name: N, value: V) -> Self
257 where N: Into<Cow<'c, str>>,
258 V: Into<Cow<'c, str>>
259 {
260 Cookie {
261 cookie_string: None,
262 name: CookieStr::Concrete(name.into()),
263 value: CookieStr::Concrete(value.into()),
264 expires: None,
265 max_age: None,
266 domain: None,
267 path: None,
268 secure: None,
269 http_only: None,
270 same_site: None,
271 partitioned: None,
272 }
273 }
274
275 /// Creates a new `Cookie` with the given name and an empty value.
276 ///
277 /// # Example
278 ///
279 /// ```rust
280 /// use cookie::Cookie;
281 ///
282 /// let cookie = Cookie::named("name");
283 /// assert_eq!(cookie.name(), "name");
284 /// assert!(cookie.value().is_empty());
285 ///
286 /// // This is equivalent to `from` with `"name`:
287 /// let cookie = Cookie::from("name");
288 /// assert_eq!(cookie.name(), "name");
289 /// assert!(cookie.value().is_empty());
290 /// ```
291 #[deprecated(since = "0.18.0", note = "use `Cookie::build(name)` or `Cookie::from(name)`")]
292 pub fn named<N>(name: N) -> Cookie<'c>
293 where N: Into<Cow<'c, str>>
294 {
295 Cookie::new(name, "")
296 }
297
298 /// Creates a new [`CookieBuilder`] starting from a `base` cookie.
299 ///
300 /// Any type that implements `T: Into<Cookie>` can be used as a `base`:
301 ///
302 /// | `Into<Cookie>` Type | Example | Equivalent To |
303 /// |----------------------------------|------------------------|----------------------------|
304 /// | `(K, V)`, `K, V: Into<Cow<str>>` | `("name", "value")` | `Cookie::new(name, value)` |
305 /// | `&str`, `String`, `Cow<str>` | `"name"` | `Cookie::new(name, "")` |
306 /// | [`CookieBuilder`] | `Cookie::build("foo")` | [`CookieBuilder::build()`] |
307 ///
308 /// # Example
309 ///
310 /// ```
311 /// use cookie::Cookie;
312 ///
313 /// // Use `(K, V)` as the base, setting a name and value.
314 /// let b1 = Cookie::build(("name", "value")).path("/");
315 /// assert_eq!(b1.inner().name_value(), ("name", "value"));
316 /// assert_eq!(b1.inner().path(), Some("/"));
317 ///
318 /// // Use `&str` as the base, setting a name and empty value.
319 /// let b2 = Cookie::build(("name"));
320 /// assert_eq!(b2.inner().name_value(), ("name", ""));
321 ///
322 /// // Use `CookieBuilder` as the base, inheriting all properties.
323 /// let b3 = Cookie::build(b1);
324 /// assert_eq!(b3.inner().name_value(), ("name", "value"));
325 /// assert_eq!(b3.inner().path(), Some("/"));
326 /// ```
327 pub fn build<C: Into<Cookie<'c>>>(base: C) -> CookieBuilder<'c> {
328 CookieBuilder::from(base.into())
329 }
330
331 /// Parses a `Cookie` from the given HTTP cookie header value string. Does
332 /// not perform any percent-decoding.
333 ///
334 /// # Example
335 ///
336 /// ```
337 /// use cookie::Cookie;
338 ///
339 /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap();
340 /// assert_eq!(c.name_value(), ("foo", "bar%20baz"));
341 /// assert_eq!(c.http_only(), Some(true));
342 /// assert_eq!(c.secure(), None);
343 /// ```
344 pub fn parse<S>(s: S) -> Result<Cookie<'c>, ParseError>
345 where S: Into<Cow<'c, str>>
346 {
347 parse_cookie(s.into(), false)
348 }
349
350 /// Parses a `Cookie` from the given HTTP cookie header value string where
351 /// the name and value fields are percent-encoded. Percent-decodes the
352 /// name/value fields.
353 ///
354 /// # Example
355 ///
356 /// ```
357 /// use cookie::Cookie;
358 ///
359 /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap();
360 /// assert_eq!(c.name_value(), ("foo", "bar baz"));
361 /// assert_eq!(c.http_only(), Some(true));
362 /// assert_eq!(c.secure(), None);
363 /// ```
364 #[cfg(feature = "percent-encode")]
365 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
366 pub fn parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError>
367 where S: Into<Cow<'c, str>>
368 {
369 parse_cookie(s.into(), true)
370 }
371
372 /// Parses the HTTP `Cookie` header, a series of cookie names and value
373 /// separated by `;`, returning an iterator over the parse results. Each
374 /// item returned by the iterator is a `Result<Cookie, ParseError>` of
375 /// parsing one name/value pair. Empty cookie values (i.e, in `a=1;;b=2`)
376 /// and any excess surrounding whitespace are ignored.
377 ///
378 /// Unlike [`Cookie::split_parse_encoded()`], this method _does **not**_
379 /// percent-decode keys and values.
380 ///
381 /// # Example
382 ///
383 /// ```rust
384 /// use cookie::Cookie;
385 ///
386 /// let string = "name=value; other=key%20value";
387 /// # let values: Vec<_> = Cookie::split_parse(string).collect();
388 /// # assert_eq!(values.len(), 2);
389 /// # assert_eq!(values[0].as_ref().unwrap().name(), "name");
390 /// # assert_eq!(values[1].as_ref().unwrap().name(), "other");
391 /// for cookie in Cookie::split_parse(string) {
392 /// let cookie = cookie.unwrap();
393 /// match cookie.name() {
394 /// "name" => assert_eq!(cookie.value(), "value"),
395 /// "other" => assert_eq!(cookie.value(), "key%20value"),
396 /// _ => unreachable!()
397 /// }
398 /// }
399 /// ```
400 #[inline(always)]
401 pub fn split_parse<S>(string: S) -> SplitCookies<'c>
402 where S: Into<Cow<'c, str>>
403 {
404 SplitCookies {
405 string: string.into(),
406 last: 0,
407 decode: false,
408 }
409 }
410
411 /// Parses the HTTP `Cookie` header, a series of cookie names and value
412 /// separated by `;`, returning an iterator over the parse results. Each
413 /// item returned by the iterator is a `Result<Cookie, ParseError>` of
414 /// parsing one name/value pair. Empty cookie values (i.e, in `a=1;;b=2`)
415 /// and any excess surrounding whitespace are ignored.
416 ///
417 /// Unlike [`Cookie::split_parse()`], this method _does_ percent-decode keys
418 /// and values.
419 ///
420 /// # Example
421 ///
422 /// ```rust
423 /// use cookie::Cookie;
424 ///
425 /// let string = "name=value; other=key%20value";
426 /// # let v: Vec<_> = Cookie::split_parse_encoded(string).collect();
427 /// # assert_eq!(v.len(), 2);
428 /// # assert_eq!(v[0].as_ref().unwrap().name_value(), ("name", "value"));
429 /// # assert_eq!(v[1].as_ref().unwrap().name_value(), ("other", "key value"));
430 /// for cookie in Cookie::split_parse_encoded(string) {
431 /// let cookie = cookie.unwrap();
432 /// match cookie.name() {
433 /// "name" => assert_eq!(cookie.value(), "value"),
434 /// "other" => assert_eq!(cookie.value(), "key value"),
435 /// _ => unreachable!()
436 /// }
437 /// }
438 /// ```
439 #[cfg(feature = "percent-encode")]
440 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
441 #[inline(always)]
442 pub fn split_parse_encoded<S>(string: S) -> SplitCookies<'c>
443 where S: Into<Cow<'c, str>>
444 {
445 SplitCookies {
446 string: string.into(),
447 last: 0,
448 decode: true,
449 }
450 }
451
452 /// Converts `self` into a `Cookie` with a static lifetime with as few
453 /// allocations as possible.
454 ///
455 /// # Example
456 ///
457 /// ```
458 /// use cookie::Cookie;
459 ///
460 /// let c = Cookie::new("a", "b");
461 /// let owned_cookie = c.into_owned();
462 /// assert_eq!(owned_cookie.name_value(), ("a", "b"));
463 /// ```
464 pub fn into_owned(self) -> Cookie<'static> {
465 Cookie {
466 cookie_string: self.cookie_string.map(|s| s.into_owned().into()),
467 name: self.name.into_owned(),
468 value: self.value.into_owned(),
469 expires: self.expires,
470 max_age: self.max_age,
471 domain: self.domain.map(|s| s.into_owned()),
472 path: self.path.map(|s| s.into_owned()),
473 secure: self.secure,
474 http_only: self.http_only,
475 same_site: self.same_site,
476 partitioned: self.partitioned,
477 }
478 }
479
480 /// Returns the name of `self`.
481 ///
482 /// # Example
483 ///
484 /// ```
485 /// use cookie::Cookie;
486 ///
487 /// let c = Cookie::new("name", "value");
488 /// assert_eq!(c.name(), "name");
489 /// ```
490 #[inline]
491 pub fn name(&self) -> &str {
492 self.name.to_str(self.cookie_string.as_ref())
493 }
494
495 /// Returns the value of `self`.
496 ///
497 /// Does not strip surrounding quotes. See [`Cookie::value_trimmed()`] for a
498 /// version that does.
499 ///
500 /// # Example
501 ///
502 /// ```
503 /// use cookie::Cookie;
504 ///
505 /// let c = Cookie::new("name", "value");
506 /// assert_eq!(c.value(), "value");
507 ///
508 /// let c = Cookie::new("name", "\"value\"");
509 /// assert_eq!(c.value(), "\"value\"");
510 /// ```
511 #[inline]
512 pub fn value(&self) -> &str {
513 self.value.to_str(self.cookie_string.as_ref())
514 }
515
516 /// Returns the value of `self` with surrounding double-quotes trimmed.
517 ///
518 /// This is _not_ the value of the cookie (_that_ is [`Cookie::value()`]).
519 /// Instead, this is the value with a surrounding pair of double-quotes, if
520 /// any, trimmed away. Quotes are only trimmed when they form a pair and
521 /// never otherwise. The trimmed value is never used for other operations,
522 /// such as equality checking, on `self`.
523 ///
524 /// # Example
525 ///
526 /// ```
527 /// use cookie::Cookie;
528 /// let c0 = Cookie::new("name", "value");
529 /// assert_eq!(c0.value_trimmed(), "value");
530 ///
531 /// let c = Cookie::new("name", "\"value\"");
532 /// assert_eq!(c.value_trimmed(), "value");
533 /// assert!(c != c0);
534 ///
535 /// let c = Cookie::new("name", "\"value");
536 /// assert_eq!(c.value(), "\"value");
537 /// assert_eq!(c.value_trimmed(), "\"value");
538 /// assert!(c != c0);
539 ///
540 /// let c = Cookie::new("name", "\"value\"\"");
541 /// assert_eq!(c.value(), "\"value\"\"");
542 /// assert_eq!(c.value_trimmed(), "value\"");
543 /// assert!(c != c0);
544 /// ```
545 #[inline]
546 pub fn value_trimmed(&self) -> &str {
547 #[inline(always)]
548 fn trim_quotes(s: &str) -> &str {
549 if s.len() < 2 {
550 return s;
551 }
552
553 let bytes = s.as_bytes();
554 match (bytes.first(), bytes.last()) {
555 (Some(b'"'), Some(b'"')) => &s[1..(s.len() - 1)],
556 _ => s
557 }
558 }
559
560 trim_quotes(self.value())
561 }
562
563 /// Returns the name and value of `self` as a tuple of `(name, value)`.
564 ///
565 /// # Example
566 ///
567 /// ```
568 /// use cookie::Cookie;
569 ///
570 /// let c = Cookie::new("name", "value");
571 /// assert_eq!(c.name_value(), ("name", "value"));
572 /// ```
573 #[inline]
574 pub fn name_value(&self) -> (&str, &str) {
575 (self.name(), self.value())
576 }
577
578 /// Returns the name and [trimmed value](Cookie::value_trimmed()) of `self`
579 /// as a tuple of `(name, trimmed_value)`.
580 ///
581 /// # Example
582 ///
583 /// ```
584 /// use cookie::Cookie;
585 ///
586 /// let c = Cookie::new("name", "\"value\"");
587 /// assert_eq!(c.name_value_trimmed(), ("name", "value"));
588 /// ```
589 #[inline]
590 pub fn name_value_trimmed(&self) -> (&str, &str) {
591 (self.name(), self.value_trimmed())
592 }
593
594 /// Returns whether this cookie was marked `HttpOnly` or not. Returns
595 /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
596 /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`,
597 /// and `None` otherwise.
598 ///
599 /// # Example
600 ///
601 /// ```
602 /// use cookie::Cookie;
603 ///
604 /// let c = Cookie::parse("name=value; httponly").unwrap();
605 /// assert_eq!(c.http_only(), Some(true));
606 ///
607 /// let mut c = Cookie::new("name", "value");
608 /// assert_eq!(c.http_only(), None);
609 ///
610 /// let mut c = Cookie::new("name", "value");
611 /// assert_eq!(c.http_only(), None);
612 ///
613 /// // An explicitly set "false" value.
614 /// c.set_http_only(false);
615 /// assert_eq!(c.http_only(), Some(false));
616 ///
617 /// // An explicitly set "true" value.
618 /// c.set_http_only(true);
619 /// assert_eq!(c.http_only(), Some(true));
620 /// ```
621 #[inline]
622 pub fn http_only(&self) -> Option<bool> {
623 self.http_only
624 }
625
626 /// Returns whether this cookie was marked `Secure` or not. Returns
627 /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
628 /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and
629 /// `None` otherwise.
630 ///
631 /// # Example
632 ///
633 /// ```
634 /// use cookie::Cookie;
635 ///
636 /// let c = Cookie::parse("name=value; Secure").unwrap();
637 /// assert_eq!(c.secure(), Some(true));
638 ///
639 /// let mut c = Cookie::parse("name=value").unwrap();
640 /// assert_eq!(c.secure(), None);
641 ///
642 /// let mut c = Cookie::new("name", "value");
643 /// assert_eq!(c.secure(), None);
644 ///
645 /// // An explicitly set "false" value.
646 /// c.set_secure(false);
647 /// assert_eq!(c.secure(), Some(false));
648 ///
649 /// // An explicitly set "true" value.
650 /// c.set_secure(true);
651 /// assert_eq!(c.secure(), Some(true));
652 /// ```
653 #[inline]
654 pub fn secure(&self) -> Option<bool> {
655 self.secure
656 }
657
658 /// Returns the `SameSite` attribute of this cookie if one was specified.
659 ///
660 /// # Example
661 ///
662 /// ```
663 /// use cookie::{Cookie, SameSite};
664 ///
665 /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap();
666 /// assert_eq!(c.same_site(), Some(SameSite::Lax));
667 /// ```
668 #[inline]
669 pub fn same_site(&self) -> Option<SameSite> {
670 self.same_site
671 }
672
673 /// Returns whether this cookie was marked `Partitioned` or not. Returns
674 /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
675 /// `Partitioned`, `Some(false)` when `partitioned` was manually set to `false`,
676 /// and `None` otherwise.
677 ///
678 /// **Note:** This cookie attribute is an [HTTP draft]! Its meaning and
679 /// definition are not standardized and therefore subject to change.
680 ///
681 /// [HTTP draft]: https://www.ietf.org/id/draft-cutler-httpbis-partitioned-cookies-01.html
682 ///
683 /// # Example
684 ///
685 /// ```
686 /// use cookie::Cookie;
687 ///
688 /// let c = Cookie::parse("name=value; Partitioned").unwrap();
689 /// assert_eq!(c.partitioned(), Some(true));
690 ///
691 /// let mut c = Cookie::parse("name=value").unwrap();
692 /// assert_eq!(c.partitioned(), None);
693 ///
694 /// let mut c = Cookie::new("name", "value");
695 /// assert_eq!(c.partitioned(), None);
696 ///
697 /// // An explicitly set "false" value.
698 /// c.set_partitioned(false);
699 /// assert_eq!(c.partitioned(), Some(false));
700 ///
701 /// // An explicitly set "true" value.
702 /// c.set_partitioned(true);
703 /// assert_eq!(c.partitioned(), Some(true));
704 /// ```
705 #[inline]
706 pub fn partitioned(&self) -> Option<bool> {
707 self.partitioned
708 }
709
710 /// Returns the specified max-age of the cookie if one was specified.
711 ///
712 /// # Example
713 ///
714 /// ```
715 /// use cookie::Cookie;
716 ///
717 /// let c = Cookie::parse("name=value").unwrap();
718 /// assert_eq!(c.max_age(), None);
719 ///
720 /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap();
721 /// assert_eq!(c.max_age().map(|age| age.whole_hours()), Some(1));
722 /// ```
723 #[inline]
724 pub fn max_age(&self) -> Option<Duration> {
725 self.max_age
726 }
727
728 /// Returns the `Path` of the cookie if one was specified.
729 ///
730 /// # Example
731 ///
732 /// ```
733 /// use cookie::Cookie;
734 ///
735 /// let c = Cookie::parse("name=value").unwrap();
736 /// assert_eq!(c.path(), None);
737 ///
738 /// let c = Cookie::parse("name=value; Path=/").unwrap();
739 /// assert_eq!(c.path(), Some("/"));
740 ///
741 /// let c = Cookie::parse("name=value; path=/sub").unwrap();
742 /// assert_eq!(c.path(), Some("/sub"));
743 /// ```
744 #[inline]
745 pub fn path(&self) -> Option<&str> {
746 match self.path {
747 Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
748 None => None,
749 }
750 }
751
752 /// Returns the `Domain` of the cookie if one was specified.
753 ///
754 /// This does not consider whether the `Domain` is valid; validation is left
755 /// to higher-level libraries, as needed. However, if the `Domain` starts
756 /// with a leading `.`, the leading `.` is stripped.
757 ///
758 /// # Example
759 ///
760 /// ```
761 /// use cookie::Cookie;
762 ///
763 /// let c = Cookie::parse("name=value").unwrap();
764 /// assert_eq!(c.domain(), None);
765 ///
766 /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap();
767 /// assert_eq!(c.domain(), Some("crates.io"));
768 ///
769 /// let c = Cookie::parse("name=value; Domain=.crates.io").unwrap();
770 /// assert_eq!(c.domain(), Some("crates.io"));
771 ///
772 /// // Note that `..crates.io` is not a valid domain.
773 /// let c = Cookie::parse("name=value; Domain=..crates.io").unwrap();
774 /// assert_eq!(c.domain(), Some(".crates.io"));
775 /// ```
776 #[inline]
777 pub fn domain(&self) -> Option<&str> {
778 match self.domain {
779 Some(ref c) => {
780 let domain = c.to_str(self.cookie_string.as_ref());
781 domain.strip_prefix(".").or(Some(domain))
782 },
783 None => None,
784 }
785 }
786
787 /// Returns the [`Expiration`] of the cookie if one was specified.
788 ///
789 /// # Example
790 ///
791 /// ```
792 /// use cookie::{Cookie, Expiration};
793 ///
794 /// let c = Cookie::parse("name=value").unwrap();
795 /// assert_eq!(c.expires(), None);
796 ///
797 /// // Here, `cookie.expires_datetime()` returns `None`.
798 /// let c = Cookie::build(("name", "value")).expires(None).build();
799 /// assert_eq!(c.expires(), Some(Expiration::Session));
800 ///
801 /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
802 /// let cookie_str = format!("name=value; Expires={}", expire_time);
803 /// let c = Cookie::parse(cookie_str).unwrap();
804 /// assert_eq!(c.expires().and_then(|e| e.datetime()).map(|t| t.year()), Some(2017));
805 /// ```
806 #[inline]
807 pub fn expires(&self) -> Option<Expiration> {
808 self.expires
809 }
810
811 /// Returns the expiration date-time of the cookie if one was specified.
812 ///
813 /// # Example
814 ///
815 /// ```
816 /// use cookie::Cookie;
817 ///
818 /// let c = Cookie::parse("name=value").unwrap();
819 /// assert_eq!(c.expires_datetime(), None);
820 ///
821 /// // Here, `cookie.expires()` returns `Some`.
822 /// let c = Cookie::build(("name", "value")).expires(None).build();
823 /// assert_eq!(c.expires_datetime(), None);
824 ///
825 /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
826 /// let cookie_str = format!("name=value; Expires={}", expire_time);
827 /// let c = Cookie::parse(cookie_str).unwrap();
828 /// assert_eq!(c.expires_datetime().map(|t| t.year()), Some(2017));
829 /// ```
830 #[inline]
831 pub fn expires_datetime(&self) -> Option<OffsetDateTime> {
832 self.expires.and_then(|e| e.datetime())
833 }
834
835 /// Sets the name of `self` to `name`.
836 ///
837 /// # Example
838 ///
839 /// ```
840 /// use cookie::Cookie;
841 ///
842 /// let mut c = Cookie::new("name", "value");
843 /// assert_eq!(c.name(), "name");
844 ///
845 /// c.set_name("foo");
846 /// assert_eq!(c.name(), "foo");
847 /// ```
848 pub fn set_name<N: Into<Cow<'c, str>>>(&mut self, name: N) {
849 self.name = CookieStr::Concrete(name.into())
850 }
851
852 /// Sets the value of `self` to `value`.
853 ///
854 /// # Example
855 ///
856 /// ```
857 /// use cookie::Cookie;
858 ///
859 /// let mut c = Cookie::new("name", "value");
860 /// assert_eq!(c.value(), "value");
861 ///
862 /// c.set_value("bar");
863 /// assert_eq!(c.value(), "bar");
864 /// ```
865 pub fn set_value<V: Into<Cow<'c, str>>>(&mut self, value: V) {
866 self.value = CookieStr::Concrete(value.into())
867 }
868
869 /// Sets the value of `http_only` in `self` to `value`. If `value` is
870 /// `None`, the field is unset.
871 ///
872 /// # Example
873 ///
874 /// ```
875 /// use cookie::Cookie;
876 ///
877 /// let mut c = Cookie::new("name", "value");
878 /// assert_eq!(c.http_only(), None);
879 ///
880 /// c.set_http_only(true);
881 /// assert_eq!(c.http_only(), Some(true));
882 ///
883 /// c.set_http_only(false);
884 /// assert_eq!(c.http_only(), Some(false));
885 ///
886 /// c.set_http_only(None);
887 /// assert_eq!(c.http_only(), None);
888 /// ```
889 #[inline]
890 pub fn set_http_only<T: Into<Option<bool>>>(&mut self, value: T) {
891 self.http_only = value.into();
892 }
893
894 /// Sets the value of `secure` in `self` to `value`. If `value` is `None`,
895 /// the field is unset.
896 ///
897 /// # Example
898 ///
899 /// ```
900 /// use cookie::Cookie;
901 ///
902 /// let mut c = Cookie::new("name", "value");
903 /// assert_eq!(c.secure(), None);
904 ///
905 /// c.set_secure(true);
906 /// assert_eq!(c.secure(), Some(true));
907 ///
908 /// c.set_secure(false);
909 /// assert_eq!(c.secure(), Some(false));
910 ///
911 /// c.set_secure(None);
912 /// assert_eq!(c.secure(), None);
913 /// ```
914 #[inline]
915 pub fn set_secure<T: Into<Option<bool>>>(&mut self, value: T) {
916 self.secure = value.into();
917 }
918
919 /// Sets the value of `same_site` in `self` to `value`. If `value` is
920 /// `None`, the field is unset. If `value` is `SameSite::None`, the "Secure"
921 /// flag will be set when the cookie is written out unless `secure` is
922 /// explicitly set to `false` via [`Cookie::set_secure()`] or the equivalent
923 /// builder method.
924 ///
925 /// [HTTP draft]: https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
926 ///
927 /// # Example
928 ///
929 /// ```
930 /// use cookie::{Cookie, SameSite};
931 ///
932 /// let mut c = Cookie::new("name", "value");
933 /// assert_eq!(c.same_site(), None);
934 ///
935 /// c.set_same_site(SameSite::None);
936 /// assert_eq!(c.same_site(), Some(SameSite::None));
937 /// assert_eq!(c.to_string(), "name=value; SameSite=None; Secure");
938 ///
939 /// c.set_secure(false);
940 /// assert_eq!(c.to_string(), "name=value; SameSite=None");
941 ///
942 /// let mut c = Cookie::new("name", "value");
943 /// assert_eq!(c.same_site(), None);
944 ///
945 /// c.set_same_site(SameSite::Strict);
946 /// assert_eq!(c.same_site(), Some(SameSite::Strict));
947 /// assert_eq!(c.to_string(), "name=value; SameSite=Strict");
948 ///
949 /// c.set_same_site(None);
950 /// assert_eq!(c.same_site(), None);
951 /// assert_eq!(c.to_string(), "name=value");
952 /// ```
953 #[inline]
954 pub fn set_same_site<T: Into<Option<SameSite>>>(&mut self, value: T) {
955 self.same_site = value.into();
956 }
957
958 /// Sets the value of `partitioned` in `self` to `value`. If `value` is
959 /// `None`, the field is unset.
960 ///
961 /// **Note:** _Partitioned_ cookies require the `Secure` attribute to be
962 /// set. As such, `Partitioned` cookies are always rendered with the
963 /// `Secure` attribute, irrespective of the `Secure` attribute's setting.
964 ///
965 /// **Note:** This cookie attribute is an [HTTP draft]! Its meaning and
966 /// definition are not standardized and therefore subject to change.
967 ///
968 /// [HTTP draft]: https://www.ietf.org/id/draft-cutler-httpbis-partitioned-cookies-01.html
969 ///
970 /// # Example
971 ///
972 /// ```
973 /// use cookie::Cookie;
974 ///
975 /// let mut c = Cookie::new("name", "value");
976 /// assert_eq!(c.partitioned(), None);
977 ///
978 /// c.set_partitioned(true);
979 /// assert_eq!(c.partitioned(), Some(true));
980 /// assert!(c.to_string().contains("Secure"));
981 ///
982 /// c.set_partitioned(false);
983 /// assert_eq!(c.partitioned(), Some(false));
984 /// assert!(!c.to_string().contains("Secure"));
985 ///
986 /// c.set_partitioned(None);
987 /// assert_eq!(c.partitioned(), None);
988 /// assert!(!c.to_string().contains("Secure"));
989 /// ```
990 #[inline]
991 pub fn set_partitioned<T: Into<Option<bool>>>(&mut self, value: T) {
992 self.partitioned = value.into();
993 }
994
995 /// Sets the value of `max_age` in `self` to `value`. If `value` is `None`,
996 /// the field is unset.
997 ///
998 /// # Example
999 ///
1000 /// ```rust
1001 /// # extern crate cookie;
1002 /// use cookie::Cookie;
1003 /// use cookie::time::Duration;
1004 ///
1005 /// # fn main() {
1006 /// let mut c = Cookie::new("name", "value");
1007 /// assert_eq!(c.max_age(), None);
1008 ///
1009 /// c.set_max_age(Duration::hours(10));
1010 /// assert_eq!(c.max_age(), Some(Duration::hours(10)));
1011 ///
1012 /// c.set_max_age(None);
1013 /// assert!(c.max_age().is_none());
1014 /// # }
1015 /// ```
1016 #[inline]
1017 pub fn set_max_age<D: Into<Option<Duration>>>(&mut self, value: D) {
1018 self.max_age = value.into();
1019 }
1020
1021 /// Sets the `path` of `self` to `path`.
1022 ///
1023 /// # Example
1024 ///
1025 /// ```rust
1026 /// use cookie::Cookie;
1027 ///
1028 /// let mut c = Cookie::new("name", "value");
1029 /// assert_eq!(c.path(), None);
1030 ///
1031 /// c.set_path("/");
1032 /// assert_eq!(c.path(), Some("/"));
1033 /// ```
1034 pub fn set_path<P: Into<Cow<'c, str>>>(&mut self, path: P) {
1035 self.path = Some(CookieStr::Concrete(path.into()));
1036 }
1037
1038 /// Unsets the `path` of `self`.
1039 ///
1040 /// # Example
1041 ///
1042 /// ```
1043 /// use cookie::Cookie;
1044 ///
1045 /// let mut c = Cookie::new("name", "value");
1046 /// assert_eq!(c.path(), None);
1047 ///
1048 /// c.set_path("/");
1049 /// assert_eq!(c.path(), Some("/"));
1050 ///
1051 /// c.unset_path();
1052 /// assert_eq!(c.path(), None);
1053 /// ```
1054 pub fn unset_path(&mut self) {
1055 self.path = None;
1056 }
1057
1058 /// Sets the `domain` of `self` to `domain`.
1059 ///
1060 /// # Example
1061 ///
1062 /// ```
1063 /// use cookie::Cookie;
1064 ///
1065 /// let mut c = Cookie::new("name", "value");
1066 /// assert_eq!(c.domain(), None);
1067 ///
1068 /// c.set_domain("rust-lang.org");
1069 /// assert_eq!(c.domain(), Some("rust-lang.org"));
1070 /// ```
1071 pub fn set_domain<D: Into<Cow<'c, str>>>(&mut self, domain: D) {
1072 self.domain = Some(CookieStr::Concrete(domain.into()));
1073 }
1074
1075 /// Unsets the `domain` of `self`.
1076 ///
1077 /// # Example
1078 ///
1079 /// ```
1080 /// use cookie::Cookie;
1081 ///
1082 /// let mut c = Cookie::new("name", "value");
1083 /// assert_eq!(c.domain(), None);
1084 ///
1085 /// c.set_domain("rust-lang.org");
1086 /// assert_eq!(c.domain(), Some("rust-lang.org"));
1087 ///
1088 /// c.unset_domain();
1089 /// assert_eq!(c.domain(), None);
1090 /// ```
1091 pub fn unset_domain(&mut self) {
1092 self.domain = None;
1093 }
1094
1095 /// Sets the expires field of `self` to `time`. If `time` is `None`, an
1096 /// expiration of [`Session`](Expiration::Session) is set.
1097 ///
1098 /// # Example
1099 ///
1100 /// ```
1101 /// # extern crate cookie;
1102 /// use cookie::{Cookie, Expiration};
1103 /// use cookie::time::{Duration, OffsetDateTime};
1104 ///
1105 /// let mut c = Cookie::new("name", "value");
1106 /// assert_eq!(c.expires(), None);
1107 ///
1108 /// let mut now = OffsetDateTime::now_utc();
1109 /// now += Duration::weeks(52);
1110 ///
1111 /// c.set_expires(now);
1112 /// assert!(c.expires().is_some());
1113 ///
1114 /// c.set_expires(None);
1115 /// assert_eq!(c.expires(), Some(Expiration::Session));
1116 /// ```
1117 pub fn set_expires<T: Into<Expiration>>(&mut self, time: T) {
1118 static MAX_DATETIME: OffsetDateTime = datetime!(9999-12-31 23:59:59.999_999 UTC);
1119
1120 // RFC 6265 requires dates not to exceed 9999 years.
1121 self.expires = Some(time.into()
1122 .map(|time| std::cmp::min(time, MAX_DATETIME)));
1123 }
1124
1125 /// Unsets the `expires` of `self`.
1126 ///
1127 /// # Example
1128 ///
1129 /// ```
1130 /// use cookie::{Cookie, Expiration};
1131 ///
1132 /// let mut c = Cookie::new("name", "value");
1133 /// assert_eq!(c.expires(), None);
1134 ///
1135 /// c.set_expires(None);
1136 /// assert_eq!(c.expires(), Some(Expiration::Session));
1137 ///
1138 /// c.unset_expires();
1139 /// assert_eq!(c.expires(), None);
1140 /// ```
1141 pub fn unset_expires(&mut self) {
1142 self.expires = None;
1143 }
1144
1145 /// Makes `self` a "permanent" cookie by extending its expiration and max
1146 /// age 20 years into the future.
1147 ///
1148 /// # Example
1149 ///
1150 /// ```rust
1151 /// # extern crate cookie;
1152 /// use cookie::Cookie;
1153 /// use cookie::time::Duration;
1154 ///
1155 /// # fn main() {
1156 /// let mut c = Cookie::new("foo", "bar");
1157 /// assert!(c.expires().is_none());
1158 /// assert!(c.max_age().is_none());
1159 ///
1160 /// c.make_permanent();
1161 /// assert!(c.expires().is_some());
1162 /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
1163 /// # }
1164 /// ```
1165 pub fn make_permanent(&mut self) {
1166 let twenty_years = Duration::days(365 * 20);
1167 self.set_max_age(twenty_years);
1168 self.set_expires(OffsetDateTime::now_utc() + twenty_years);
1169 }
1170
1171 /// Make `self` a "removal" cookie by clearing its value, setting a max-age
1172 /// of `0`, and setting an expiration date far in the past.
1173 ///
1174 /// # Example
1175 ///
1176 /// ```rust
1177 /// # extern crate cookie;
1178 /// use cookie::Cookie;
1179 /// use cookie::time::Duration;
1180 ///
1181 /// # fn main() {
1182 /// let mut c = Cookie::new("foo", "bar");
1183 /// c.make_permanent();
1184 /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
1185 /// assert_eq!(c.value(), "bar");
1186 ///
1187 /// c.make_removal();
1188 /// assert_eq!(c.value(), "");
1189 /// assert_eq!(c.max_age(), Some(Duration::ZERO));
1190 /// # }
1191 /// ```
1192 pub fn make_removal(&mut self) {
1193 self.set_value("");
1194 self.set_max_age(Duration::seconds(0));
1195 self.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
1196 }
1197
1198 fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result {
1199 if let Some(true) = self.http_only() {
1200 write!(f, "; HttpOnly")?;
1201 }
1202
1203 if let Some(same_site) = self.same_site() {
1204 write!(f, "; SameSite={}", same_site)?;
1205 }
1206
1207 if let Some(true) = self.partitioned() {
1208 write!(f, "; Partitioned")?;
1209 }
1210
1211 if self.secure() == Some(true)
1212 || self.partitioned() == Some(true)
1213 || self.secure().is_none() && self.same_site() == Some(SameSite::None)
1214 {
1215 write!(f, "; Secure")?;
1216 }
1217
1218 if let Some(path) = self.path() {
1219 write!(f, "; Path={}", path)?;
1220 }
1221
1222 if let Some(domain) = self.domain() {
1223 write!(f, "; Domain={}", domain)?;
1224 }
1225
1226 if let Some(max_age) = self.max_age() {
1227 write!(f, "; Max-Age={}", max_age.whole_seconds())?;
1228 }
1229
1230 if let Some(time) = self.expires_datetime() {
1231 let time = time.to_offset(UtcOffset::UTC);
1232 write!(f, "; Expires={}", time.format(&crate::parse::FMT1).map_err(|_| fmt::Error)?)?;
1233 }
1234
1235 Ok(())
1236 }
1237
1238 /// Returns the name of `self` as a string slice of the raw string `self`
1239 /// was originally parsed from. If `self` was not originally parsed from a
1240 /// raw string, returns `None`.
1241 ///
1242 /// This method differs from [`Cookie::name()`] in that it returns a string
1243 /// with the same lifetime as the originally parsed string. This lifetime
1244 /// may outlive `self`. If a longer lifetime is not required, or you're
1245 /// unsure if you need a longer lifetime, use [`Cookie::name()`].
1246 ///
1247 /// # Example
1248 ///
1249 /// ```
1250 /// use cookie::Cookie;
1251 ///
1252 /// let cookie_string = format!("{}={}", "foo", "bar");
1253 ///
1254 /// // `c` will be dropped at the end of the scope, but `name` will live on
1255 /// let name = {
1256 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
1257 /// c.name_raw()
1258 /// };
1259 ///
1260 /// assert_eq!(name, Some("foo"));
1261 /// ```
1262 #[inline]
1263 pub fn name_raw(&self) -> Option<&'c str> {
1264 self.cookie_string.as_ref()
1265 .and_then(|s| self.name.to_raw_str(s))
1266 }
1267
1268 /// Returns the value of `self` as a string slice of the raw string `self`
1269 /// was originally parsed from. If `self` was not originally parsed from a
1270 /// raw string, returns `None`.
1271 ///
1272 /// This method differs from [`Cookie::value()`] in that it returns a
1273 /// string with the same lifetime as the originally parsed string. This
1274 /// lifetime may outlive `self`. If a longer lifetime is not required, or
1275 /// you're unsure if you need a longer lifetime, use [`Cookie::value()`].
1276 ///
1277 /// # Example
1278 ///
1279 /// ```
1280 /// use cookie::Cookie;
1281 ///
1282 /// let cookie_string = format!("{}={}", "foo", "bar");
1283 ///
1284 /// // `c` will be dropped at the end of the scope, but `value` will live on
1285 /// let value = {
1286 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
1287 /// c.value_raw()
1288 /// };
1289 ///
1290 /// assert_eq!(value, Some("bar"));
1291 /// ```
1292 #[inline]
1293 pub fn value_raw(&self) -> Option<&'c str> {
1294 self.cookie_string.as_ref()
1295 .and_then(|s| self.value.to_raw_str(s))
1296 }
1297
1298 /// Returns the `Path` of `self` as a string slice of the raw string `self`
1299 /// was originally parsed from. If `self` was not originally parsed from a
1300 /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has
1301 /// changed since parsing, returns `None`.
1302 ///
1303 /// This method differs from [`Cookie::path()`] in that it returns a
1304 /// string with the same lifetime as the originally parsed string. This
1305 /// lifetime may outlive `self`. If a longer lifetime is not required, or
1306 /// you're unsure if you need a longer lifetime, use [`Cookie::path()`].
1307 ///
1308 /// # Example
1309 ///
1310 /// ```
1311 /// use cookie::Cookie;
1312 ///
1313 /// let cookie_string = format!("{}={}; Path=/", "foo", "bar");
1314 ///
1315 /// // `c` will be dropped at the end of the scope, but `path` will live on
1316 /// let path = {
1317 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
1318 /// c.path_raw()
1319 /// };
1320 ///
1321 /// assert_eq!(path, Some("/"));
1322 /// ```
1323 #[inline]
1324 pub fn path_raw(&self) -> Option<&'c str> {
1325 match (self.path.as_ref(), self.cookie_string.as_ref()) {
1326 (Some(path), Some(string)) => path.to_raw_str(string),
1327 _ => None,
1328 }
1329 }
1330
1331 /// Returns the `Domain` of `self` as a string slice of the raw string
1332 /// `self` was originally parsed from. If `self` was not originally parsed
1333 /// from a raw string, or if `self` doesn't contain a `Domain`, or if the
1334 /// `Domain` has changed since parsing, returns `None`.
1335 ///
1336 /// Like [`Cookie::domain()`], this does not consider whether `Domain` is
1337 /// valid; validation is left to higher-level libraries, as needed. However,
1338 /// if `Domain` starts with a leading `.`, the leading `.` is stripped.
1339 ///
1340 /// This method differs from [`Cookie::domain()`] in that it returns a
1341 /// string with the same lifetime as the originally parsed string. This
1342 /// lifetime may outlive `self` struct. If a longer lifetime is not
1343 /// required, or you're unsure if you need a longer lifetime, use
1344 /// [`Cookie::domain()`].
1345 ///
1346 /// # Example
1347 ///
1348 /// ```
1349 /// use cookie::Cookie;
1350 ///
1351 /// let cookie_string = format!("{}={}; Domain=.crates.io", "foo", "bar");
1352 ///
1353 /// //`c` will be dropped at the end of the scope, but `domain` will live on
1354 /// let domain = {
1355 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
1356 /// c.domain_raw()
1357 /// };
1358 ///
1359 /// assert_eq!(domain, Some("crates.io"));
1360 /// ```
1361 #[inline]
1362 pub fn domain_raw(&self) -> Option<&'c str> {
1363 match (self.domain.as_ref(), self.cookie_string.as_ref()) {
1364 (Some(domain), Some(string)) => match domain.to_raw_str(string) {
1365 Some(s) => s.strip_prefix(".").or(Some(s)),
1366 None => None,
1367 }
1368 _ => None,
1369 }
1370 }
1371
1372 /// Wraps `self` in an encoded [`Display`]: a cost-free wrapper around
1373 /// `Cookie` whose [`fmt::Display`] implementation percent-encodes the name
1374 /// and value of the wrapped `Cookie`.
1375 ///
1376 /// The returned structure can be chained with [`Display::stripped()`] to
1377 /// display only the name and value.
1378 ///
1379 /// # Example
1380 ///
1381 /// ```rust
1382 /// use cookie::Cookie;
1383 ///
1384 /// let mut c = Cookie::build(("my name", "this; value?")).secure(true).build();
1385 /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F; Secure");
1386 /// assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%3F");
1387 /// ```
1388 #[cfg(feature = "percent-encode")]
1389 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
1390 #[inline(always)]
1391 pub fn encoded<'a>(&'a self) -> Display<'a, 'c> {
1392 Display::new_encoded(self)
1393 }
1394
1395 /// Wraps `self` in a stripped `Display`]: a cost-free wrapper around
1396 /// `Cookie` whose [`fmt::Display`] implementation prints only the `name`
1397 /// and `value` of the wrapped `Cookie`.
1398 ///
1399 /// The returned structure can be chained with [`Display::encoded()`] to
1400 /// encode the name and value.
1401 ///
1402 /// # Example
1403 ///
1404 /// ```rust
1405 /// use cookie::Cookie;
1406 ///
1407 /// let mut c = Cookie::build(("key?", "value")).secure(true).path("/").build();
1408 /// assert_eq!(&c.stripped().to_string(), "key?=value");
1409 #[cfg_attr(feature = "percent-encode", doc = r##"
1410// Note: `encoded()` is only available when `percent-encode` is enabled.
1411assert_eq!(&c.stripped().encoded().to_string(), "key%3F=value");
1412 #"##)]
1413 /// ```
1414 #[inline(always)]
1415 pub fn stripped<'a>(&'a self) -> Display<'a, 'c> {
1416 Display::new_stripped(self)
1417 }
1418}
1419
1420/// An iterator over cookie parse `Result`s: `Result<Cookie, ParseError>`.
1421///
1422/// Returned by [`Cookie::split_parse()`] and [`Cookie::split_parse_encoded()`].
1423pub struct SplitCookies<'c> {
1424 // The source string, which we split and parse.
1425 string: Cow<'c, str>,
1426 // The index where we last split off.
1427 last: usize,
1428 // Whether we should percent-decode when parsing.
1429 decode: bool,
1430}
1431
1432impl<'c> Iterator for SplitCookies<'c> {
1433 type Item = Result<Cookie<'c>, ParseError>;
1434
1435 fn next(&mut self) -> Option<Self::Item> {
1436 while self.last < self.string.len() {
1437 let i = self.last;
1438 let j = self.string[i..]
1439 .find(';')
1440 .map(|k| i + k)
1441 .unwrap_or(self.string.len());
1442
1443 self.last = j + 1;
1444 if self.string[i..j].chars().all(|c| c.is_whitespace()) {
1445 continue;
1446 }
1447
1448 return Some(match self.string {
1449 Cow::Borrowed(s) => parse_cookie(s[i..j].trim(), self.decode),
1450 Cow::Owned(ref s) => parse_cookie(s[i..j].trim().to_owned(), self.decode),
1451 })
1452 }
1453
1454 None
1455 }
1456}
1457
1458#[cfg(feature = "percent-encode")]
1459mod encoding {
1460 use percent_encoding::{AsciiSet, CONTROLS};
1461
1462 /// https://url.spec.whatwg.org/#fragment-percent-encode-set
1463 const FRAGMENT: &AsciiSet = &CONTROLS
1464 .add(b' ')
1465 .add(b'"')
1466 .add(b'<')
1467 .add(b'>')
1468 .add(b'`');
1469
1470 /// https://url.spec.whatwg.org/#path-percent-encode-set
1471 const PATH: &AsciiSet = &FRAGMENT
1472 .add(b'#')
1473 .add(b'?')
1474 .add(b'{')
1475 .add(b'}');
1476
1477 /// https://url.spec.whatwg.org/#userinfo-percent-encode-set
1478 const USERINFO: &AsciiSet = &PATH
1479 .add(b'/')
1480 .add(b':')
1481 .add(b';')
1482 .add(b'=')
1483 .add(b'@')
1484 .add(b'[')
1485 .add(b'\\')
1486 .add(b']')
1487 .add(b'^')
1488 .add(b'|')
1489 .add(b'%');
1490
1491 /// https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1 + '(', ')'
1492 const COOKIE: &AsciiSet = &USERINFO
1493 .add(b'(')
1494 .add(b')')
1495 .add(b',');
1496
1497 /// Percent-encode a cookie name or value with the proper encoding set.
1498 pub fn encode(string: &str) -> impl std::fmt::Display + '_ {
1499 percent_encoding::percent_encode(string.as_bytes(), COOKIE)
1500 }
1501}
1502
1503/// Wrapper around `Cookie` whose `Display` implementation either
1504/// percent-encodes the cookie's name and value, skips displaying the cookie's
1505/// parameters (only displaying it's name and value), or both.
1506///
1507/// A value of this type can be obtained via [`Cookie::encoded()`] and
1508/// [`Cookie::stripped()`], or an arbitrary chaining of the two methods. This
1509/// type should only be used for its `Display` implementation.
1510///
1511/// # Example
1512///
1513/// ```rust
1514/// use cookie::Cookie;
1515///
1516/// let c = Cookie::build(("my name", "this; value%?")).secure(true).build();
1517/// assert_eq!(&c.stripped().to_string(), "my name=this; value%?");
1518#[cfg_attr(feature = "percent-encode", doc = r##"
1519// Note: `encoded()` is only available when `percent-encode` is enabled.
1520assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%25%3F; Secure");
1521assert_eq!(&c.stripped().encoded().to_string(), "my%20name=this%3B%20value%25%3F");
1522assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%25%3F");
1523"##)]
1524/// ```
1525pub struct Display<'a, 'c: 'a> {
1526 cookie: &'a Cookie<'c>,
1527 #[cfg(feature = "percent-encode")]
1528 encode: bool,
1529 strip: bool,
1530}
1531
1532impl<'a, 'c: 'a> fmt::Display for Display<'a, 'c> {
1533 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1534 #[cfg(feature = "percent-encode")] {
1535 if self.encode {
1536 let name = encoding::encode(self.cookie.name());
1537 let value = encoding::encode(self.cookie.value());
1538 write!(f, "{}={}", name, value)?;
1539 } else {
1540 write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1541 }
1542 }
1543
1544 #[cfg(not(feature = "percent-encode"))] {
1545 write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1546 }
1547
1548 match self.strip {
1549 true => Ok(()),
1550 false => self.cookie.fmt_parameters(f)
1551 }
1552 }
1553}
1554
1555impl<'a, 'c> Display<'a, 'c> {
1556 #[cfg(feature = "percent-encode")]
1557 fn new_encoded(cookie: &'a Cookie<'c>) -> Self {
1558 Display { cookie, strip: false, encode: true }
1559 }
1560
1561 fn new_stripped(cookie: &'a Cookie<'c>) -> Self {
1562 Display { cookie, strip: true, #[cfg(feature = "percent-encode")] encode: false }
1563 }
1564
1565 /// Percent-encode the name and value pair.
1566 #[inline]
1567 #[cfg(feature = "percent-encode")]
1568 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
1569 pub fn encoded(mut self) -> Self {
1570 self.encode = true;
1571 self
1572 }
1573
1574 /// Only display the name and value.
1575 #[inline]
1576 pub fn stripped(mut self) -> Self {
1577 self.strip = true;
1578 self
1579 }
1580}
1581
1582impl<'c> fmt::Display for Cookie<'c> {
1583 /// Formats the cookie `self` as a `Set-Cookie` header value.
1584 ///
1585 /// Does _not_ percent-encode any values. To percent-encode, use
1586 /// [`Cookie::encoded()`].
1587 ///
1588 /// # Example
1589 ///
1590 /// ```rust
1591 /// use cookie::Cookie;
1592 ///
1593 /// let mut cookie = Cookie::build(("foo", "bar")).path("/");
1594 /// assert_eq!(cookie.to_string(), "foo=bar; Path=/");
1595 /// ```
1596 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1597 write!(f, "{}={}", self.name(), self.value())?;
1598 self.fmt_parameters(f)
1599 }
1600}
1601
1602impl FromStr for Cookie<'static> {
1603 type Err = ParseError;
1604
1605 fn from_str(s: &str) -> Result<Cookie<'static>, ParseError> {
1606 Cookie::parse(s).map(|c| c.into_owned())
1607 }
1608}
1609
1610impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
1611 fn eq(&self, other: &Cookie<'b>) -> bool {
1612 let so_far_so_good = self.name() == other.name()
1613 && self.value() == other.value()
1614 && self.http_only() == other.http_only()
1615 && self.secure() == other.secure()
1616 && self.partitioned() == other.partitioned()
1617 && self.max_age() == other.max_age()
1618 && self.expires() == other.expires();
1619
1620 if !so_far_so_good {
1621 return false;
1622 }
1623
1624 match (self.path(), other.path()) {
1625 (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1626 (None, None) => {}
1627 _ => return false,
1628 };
1629
1630 match (self.domain(), other.domain()) {
1631 (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1632 (None, None) => {}
1633 _ => return false,
1634 };
1635
1636 true
1637 }
1638}
1639
1640impl<'a> From<&'a str> for Cookie<'a> {
1641 fn from(name: &'a str) -> Self {
1642 Cookie::new(name, "")
1643 }
1644}
1645
1646impl From<String> for Cookie<'static> {
1647 fn from(name: String) -> Self {
1648 Cookie::new(name, "")
1649 }
1650}
1651
1652impl<'a> From<Cow<'a, str>> for Cookie<'a> {
1653 fn from(name: Cow<'a, str>) -> Self {
1654 Cookie::new(name, "")
1655 }
1656}
1657
1658impl<'a, N, V> From<(N, V)> for Cookie<'a>
1659 where N: Into<Cow<'a, str>>,
1660 V: Into<Cow<'a, str>>
1661{
1662 fn from((name, value): (N, V)) -> Self {
1663 Cookie::new(name, value)
1664 }
1665}
1666
1667impl<'a> From<CookieBuilder<'a>> for Cookie<'a> {
1668 fn from(builder: CookieBuilder<'a>) -> Self {
1669 builder.build()
1670 }
1671}
1672
1673impl<'a> AsRef<Cookie<'a>> for Cookie<'a> {
1674 fn as_ref(&self) -> &Cookie<'a> {
1675 self
1676 }
1677}
1678
1679impl<'a> AsMut<Cookie<'a>> for Cookie<'a> {
1680 fn as_mut(&mut self) -> &mut Cookie<'a> {
1681 self
1682 }
1683}
1684
1685#[cfg(test)]
1686mod tests {
1687 use crate::{Cookie, SameSite, parse::parse_date};
1688 use time::{Duration, OffsetDateTime};
1689
1690 #[test]
1691 fn format() {
1692 let cookie = Cookie::new("foo", "bar");
1693 assert_eq!(&cookie.to_string(), "foo=bar");
1694
1695 let cookie = Cookie::build(("foo", "bar")).http_only(true);
1696 assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
1697
1698 let cookie = Cookie::build(("foo", "bar")).max_age(Duration::seconds(10));
1699 assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
1700
1701 let cookie = Cookie::build(("foo", "bar")).secure(true);
1702 assert_eq!(&cookie.to_string(), "foo=bar; Secure");
1703
1704 let cookie = Cookie::build(("foo", "bar")).path("/");
1705 assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1706
1707 let cookie = Cookie::build(("foo", "bar")).domain("www.rust-lang.org");
1708 assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
1709
1710 let cookie = Cookie::build(("foo", "bar")).domain(".rust-lang.org");
1711 assert_eq!(&cookie.to_string(), "foo=bar; Domain=rust-lang.org");
1712
1713 let cookie = Cookie::build(("foo", "bar")).domain("rust-lang.org");
1714 assert_eq!(&cookie.to_string(), "foo=bar; Domain=rust-lang.org");
1715
1716 let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
1717 let expires = parse_date(time_str, &crate::parse::FMT1).unwrap();
1718 let cookie = Cookie::build(("foo", "bar")).expires(expires);
1719 assert_eq!(&cookie.to_string(),
1720 "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT");
1721
1722 let cookie = Cookie::build(("foo", "bar")).same_site(SameSite::Strict);
1723 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict");
1724
1725 let cookie = Cookie::build(("foo", "bar")).same_site(SameSite::Lax);
1726 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax");
1727
1728 let mut cookie = Cookie::build(("foo", "bar")).same_site(SameSite::None).build();
1729 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
1730
1731 cookie.set_partitioned(true);
1732 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Partitioned; Secure");
1733
1734 cookie.set_same_site(None);
1735 assert_eq!(&cookie.to_string(), "foo=bar; Partitioned; Secure");
1736
1737 cookie.set_secure(false);
1738 assert_eq!(&cookie.to_string(), "foo=bar; Partitioned; Secure");
1739
1740 cookie.set_secure(None);
1741 assert_eq!(&cookie.to_string(), "foo=bar; Partitioned; Secure");
1742
1743 cookie.set_partitioned(None);
1744 assert_eq!(&cookie.to_string(), "foo=bar");
1745
1746 let mut c = Cookie::build(("foo", "bar")).same_site(SameSite::None).secure(false).build();
1747 assert_eq!(&c.to_string(), "foo=bar; SameSite=None");
1748 c.set_secure(true);
1749 assert_eq!(&c.to_string(), "foo=bar; SameSite=None; Secure");
1750 }
1751
1752 #[test]
1753 #[ignore]
1754 fn format_date_wraps() {
1755 let expires = OffsetDateTime::UNIX_EPOCH + Duration::MAX;
1756 let cookie = Cookie::build(("foo", "bar")).expires(expires);
1757 assert_eq!(&cookie.to_string(), "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT");
1758
1759 let expires = time::macros::datetime!(9999-01-01 0:00 UTC) + Duration::days(1000);
1760 let cookie = Cookie::build(("foo", "bar")).expires(expires);
1761 assert_eq!(&cookie.to_string(), "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT");
1762 }
1763
1764 #[test]
1765 fn cookie_string_long_lifetimes() {
1766 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1767 let (name, value, path, domain) = {
1768 // Create a cookie passing a slice
1769 let c = Cookie::parse(cookie_string.as_str()).unwrap();
1770 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1771 };
1772
1773 assert_eq!(name, Some("bar"));
1774 assert_eq!(value, Some("baz"));
1775 assert_eq!(path, Some("/subdir"));
1776 assert_eq!(domain, Some("crates.io"));
1777 }
1778
1779 #[test]
1780 fn owned_cookie_string() {
1781 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1782 let (name, value, path, domain) = {
1783 // Create a cookie passing an owned string
1784 let c = Cookie::parse(cookie_string).unwrap();
1785 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1786 };
1787
1788 assert_eq!(name, None);
1789 assert_eq!(value, None);
1790 assert_eq!(path, None);
1791 assert_eq!(domain, None);
1792 }
1793
1794 #[test]
1795 fn owned_cookie_struct() {
1796 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io";
1797 let (name, value, path, domain) = {
1798 // Create an owned cookie
1799 let c = Cookie::parse(cookie_string).unwrap().into_owned();
1800
1801 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1802 };
1803
1804 assert_eq!(name, None);
1805 assert_eq!(value, None);
1806 assert_eq!(path, None);
1807 assert_eq!(domain, None);
1808 }
1809
1810 #[test]
1811 #[cfg(feature = "percent-encode")]
1812 fn format_encoded() {
1813 let cookie = Cookie::new("foo !%?=", "bar;;, a");
1814 let cookie_str = cookie.encoded().to_string();
1815 assert_eq!(&cookie_str, "foo%20!%25%3F%3D=bar%3B%3B%2C%20a");
1816
1817 let cookie = Cookie::parse_encoded(cookie_str).unwrap();
1818 assert_eq!(cookie.name_value(), ("foo !%?=", "bar;;, a"));
1819 }
1820
1821 #[test]
1822 fn split_parse() {
1823 let cases = [
1824 ("", vec![]),
1825 (";;", vec![]),
1826 ("name=value", vec![("name", "value")]),
1827 ("a=%20", vec![("a", "%20")]),
1828 ("a=d#$%^&*()_", vec![("a", "d#$%^&*()_")]),
1829 (" name=value ", vec![("name", "value")]),
1830 ("name=value ", vec![("name", "value")]),
1831 ("name=value;;other=key", vec![("name", "value"), ("other", "key")]),
1832 ("name=value; ;other=key", vec![("name", "value"), ("other", "key")]),
1833 ("name=value ; ;other=key", vec![("name", "value"), ("other", "key")]),
1834 ("name=value ; ; other=key", vec![("name", "value"), ("other", "key")]),
1835 ("name=value ; ; other=key ", vec![("name", "value"), ("other", "key")]),
1836 ("name=value ; ; other=key;; ", vec![("name", "value"), ("other", "key")]),
1837 (";name=value ; ; other=key ", vec![("name", "value"), ("other", "key")]),
1838 (";a=1 ; ; b=2 ", vec![("a", "1"), ("b", "2")]),
1839 (";a=1 ; ; b= ", vec![("a", "1"), ("b", "")]),
1840 (";a=1 ; ; =v ; c=", vec![("a", "1"), ("c", "")]),
1841 (" ; a=1 ; ; =v ; ;;c=", vec![("a", "1"), ("c", "")]),
1842 (" ; a=1 ; ; =v ; ;;c=== ", vec![("a", "1"), ("c", "==")]),
1843 ];
1844
1845 for (string, expected) in cases {
1846 let actual: Vec<_> = Cookie::split_parse(string)
1847 .filter_map(|parse| parse.ok())
1848 .map(|c| (c.name_raw().unwrap(), c.value_raw().unwrap()))
1849 .collect();
1850
1851 assert_eq!(expected, actual);
1852 }
1853 }
1854
1855 #[test]
1856 #[cfg(feature = "percent-encode")]
1857 fn split_parse_encoded() {
1858 let cases = [
1859 ("", vec![]),
1860 (";;", vec![]),
1861 ("name=val%20ue", vec![("name", "val ue")]),
1862 ("foo%20!%25%3F%3D=bar%3B%3B%2C%20a", vec![("foo !%?=", "bar;;, a")]),
1863 (
1864 "name=val%20ue ; ; foo%20!%25%3F%3D=bar%3B%3B%2C%20a",
1865 vec![("name", "val ue"), ("foo !%?=", "bar;;, a")]
1866 ),
1867 ];
1868
1869 for (string, expected) in cases {
1870 let cookies: Vec<_> = Cookie::split_parse_encoded(string)
1871 .filter_map(|parse| parse.ok())
1872 .collect();
1873
1874 let actual: Vec<_> = cookies.iter()
1875 .map(|c| c.name_value())
1876 .collect();
1877
1878 assert_eq!(expected, actual);
1879 }
1880 }
1881}