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.15"
14//! ```
15//!
16//! Then add the following line to your crate root:
17//!
18//! ```
19//! extern crate cookie;
20//! ```
21//!
22//! # Features
23//!
24//! This crate exposes several features, all of which are disabled by default:
25//!
26//! * **`percent-encode`**
27//!
28//! Enables _percent encoding and decoding_ of names and values in cookies.
29//!
30//! When this feature is enabled, the [`Cookie::encoded()`] and
31//! [`Cookie::parse_encoded()`] methods are available. The `encoded` method
32//! returns a wrapper around a `Cookie` whose `Display` implementation
33//! percent-encodes the name and value of the cookie. The `parse_encoded`
34//! method percent-decodes the name and value of a `Cookie` during parsing.
35//!
36//! * **`signed`**
37//!
38//! Enables _signed_ cookies via [`CookieJar::signed()`].
39//!
40//! When this feature is enabled, the [`CookieJar::signed()`] method,
41//! [`SignedJar`] type, and [`Key`] type are available. The jar acts as "child
42//! jar"; operations on the jar automatically sign and verify cookies as they
43//! are added and retrieved from the parent jar.
44//!
45//! * **`private`**
46//!
47//! Enables _private_ (authenticated, encrypted) cookies via
48//! [`CookieJar::private()`].
49//!
50//! When this feature is enabled, the [`CookieJar::private()`] method,
51//! [`PrivateJar`] type, and [`Key`] type are available. The jar acts as "child
52//! jar"; operations on the jar automatically encrypt and decrypt/authenticate
53//! cookies as they are added and retrieved from the parent jar.
54//!
55//! * **`key-expansion`**
56//!
57//! Enables _key expansion_ or _key derivation_ via [`Key::derive_from()`].
58//!
59//! When this feature is enabled, and either `signed` or `private` are _also_
60//! enabled, the [`Key::derive_from()`] method is available. The method can be
61//! used to derive a `Key` structure appropriate for use with signed and
62//! private jars from cryptographically valid key material that is shorter in
63//! length than the full key.
64//!
65//! * **`secure`**
66//!
67//! A meta-feature that simultaneously enables `signed`, `private`, and
68//! `key-expansion`.
69//!
70//! You can enable features via `Cargo.toml`:
71//!
72//! ```toml
73//! [dependencies.cookie]
74//! features = ["secure", "percent-encode"]
75//! ```
76
77#![cfg_attr(all(nightly, doc), feature(doc_cfg))]
78
79#![doc(html_root_url = "https://docs.rs/cookie/0.15")]
80#![deny(missing_docs)]
81
82#[cfg(feature = "percent-encode")] extern crate percent_encoding;
83extern crate time;
84
85mod builder;
86mod parse;
87mod jar;
88mod delta;
89mod draft;
90mod expiration;
91
92#[cfg(any(feature = "private", feature = "signed"))] #[macro_use] mod secure;
93#[cfg(any(feature = "private", feature = "signed"))] pub use secure::*;
94
95use std::borrow::Cow;
96use std::fmt;
97use std::str::FromStr;
98
99#[allow(unused_imports, deprecated)]
100use std::ascii::AsciiExt;
101
102#[cfg(feature = "percent-encode")]
103use percent_encoding::{AsciiSet, percent_encode as encode};
104use time::{Duration, OffsetDateTime, UtcOffset};
105
106use crate::parse::parse_cookie;
107pub use crate::parse::ParseError;
108pub use crate::builder::CookieBuilder;
109pub use crate::jar::{CookieJar, Delta, Iter};
110pub use crate::draft::*;
111pub use crate::expiration::*;
112
113#[derive(Debug, Clone)]
114enum CookieStr<'c> {
115 /// An string derived from indexes (start, end).
116 Indexed(usize, usize),
117 /// A string derived from a concrete string.
118 Concrete(Cow<'c, str>),
119}
120
121impl<'c> CookieStr<'c> {
122 /// Retrieves the string `self` corresponds to. If `self` is derived from
123 /// indexes, the corresponding subslice of `string` is returned. Otherwise,
124 /// the concrete string is returned.
125 ///
126 /// # Panics
127 ///
128 /// Panics if `self` is an indexed string and `string` is None.
129 fn to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str {
130 match *self {
131 CookieStr::Indexed(i, j) => {
132 let s = string.expect("`Some` base string must exist when \
133 converting indexed str to str! (This is a module invariant.)");
134 &s[i..j]
135 },
136 CookieStr::Concrete(ref cstr) => &*cstr,
137 }
138 }
139
140 #[allow(clippy::ptr_arg)]
141 fn to_raw_str<'s, 'b: 's>(&'s self, string: &'s Cow<'b, str>) -> Option<&'b str> {
142 match *self {
143 CookieStr::Indexed(i, j) => {
144 match *string {
145 Cow::Borrowed(s) => Some(&s[i..j]),
146 Cow::Owned(_) => None,
147 }
148 },
149 CookieStr::Concrete(_) => None,
150 }
151 }
152
153 fn into_owned(self) -> CookieStr<'static> {
154 use crate::CookieStr::*;
155
156 match self {
157 Indexed(a, b) => Indexed(a, b),
158 Concrete(Cow::Owned(c)) => Concrete(Cow::Owned(c)),
159 Concrete(Cow::Borrowed(c)) => Concrete(Cow::Owned(c.into())),
160 }
161 }
162}
163
164/// Representation of an HTTP cookie.
165///
166/// # Constructing a `Cookie`
167///
168/// To construct a cookie with only a name/value, use [`Cookie::new()`]:
169///
170/// ```rust
171/// use cookie::Cookie;
172///
173/// let cookie = Cookie::new("name", "value");
174/// assert_eq!(&cookie.to_string(), "name=value");
175/// ```
176///
177/// To construct more elaborate cookies, use [`Cookie::build()`] and
178/// [`CookieBuilder`] methods:
179///
180/// ```rust
181/// use cookie::Cookie;
182///
183/// let cookie = Cookie::build("name", "value")
184/// .domain("www.rust-lang.org")
185/// .path("/")
186/// .secure(true)
187/// .http_only(true)
188/// .finish();
189/// ```
190#[derive(Debug, Clone)]
191pub struct Cookie<'c> {
192 /// Storage for the cookie string. Only used if this structure was derived
193 /// from a string that was subsequently parsed.
194 cookie_string: Option<Cow<'c, str>>,
195 /// The cookie's name.
196 name: CookieStr<'c>,
197 /// The cookie's value.
198 value: CookieStr<'c>,
199 /// The cookie's expiration, if any.
200 expires: Option<Expiration>,
201 /// The cookie's maximum age, if any.
202 max_age: Option<Duration>,
203 /// The cookie's domain, if any.
204 domain: Option<CookieStr<'c>>,
205 /// The cookie's path domain, if any.
206 path: Option<CookieStr<'c>>,
207 /// Whether this cookie was marked Secure.
208 secure: Option<bool>,
209 /// Whether this cookie was marked HttpOnly.
210 http_only: Option<bool>,
211 /// The draft `SameSite` attribute.
212 same_site: Option<SameSite>,
213}
214
215impl<'c> Cookie<'c> {
216 /// Creates a new `Cookie` with the given name and value.
217 ///
218 /// # Example
219 ///
220 /// ```rust
221 /// use cookie::Cookie;
222 ///
223 /// let cookie = Cookie::new("name", "value");
224 /// assert_eq!(cookie.name_value(), ("name", "value"));
225 /// ```
226 pub fn new<N, V>(name: N, value: V) -> Self
227 where N: Into<Cow<'c, str>>,
228 V: Into<Cow<'c, str>>
229 {
230 Cookie {
231 cookie_string: None,
232 name: CookieStr::Concrete(name.into()),
233 value: CookieStr::Concrete(value.into()),
234 expires: None,
235 max_age: None,
236 domain: None,
237 path: None,
238 secure: None,
239 http_only: None,
240 same_site: None,
241 }
242 }
243
244 /// Creates a new `Cookie` with the given name and an empty value.
245 ///
246 /// # Example
247 ///
248 /// ```rust
249 /// use cookie::Cookie;
250 ///
251 /// let cookie = Cookie::named("name");
252 /// assert_eq!(cookie.name(), "name");
253 /// assert!(cookie.value().is_empty());
254 /// ```
255 pub fn named<N>(name: N) -> Cookie<'c>
256 where N: Into<Cow<'c, str>>
257 {
258 Cookie::new(name, "")
259 }
260
261 /// Creates a new `CookieBuilder` instance from the given key and value
262 /// strings.
263 ///
264 /// # Example
265 ///
266 /// ```
267 /// use cookie::Cookie;
268 ///
269 /// let c = Cookie::build("foo", "bar").finish();
270 /// assert_eq!(c.name_value(), ("foo", "bar"));
271 /// ```
272 pub fn build<N, V>(name: N, value: V) -> CookieBuilder<'c>
273 where N: Into<Cow<'c, str>>,
274 V: Into<Cow<'c, str>>
275 {
276 CookieBuilder::new(name, value)
277 }
278
279 /// Parses a `Cookie` from the given HTTP cookie header value string. Does
280 /// not perform any percent-decoding.
281 ///
282 /// # Example
283 ///
284 /// ```
285 /// use cookie::Cookie;
286 ///
287 /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap();
288 /// assert_eq!(c.name_value(), ("foo", "bar%20baz"));
289 /// assert_eq!(c.http_only(), Some(true));
290 /// ```
291 pub fn parse<S>(s: S) -> Result<Cookie<'c>, ParseError>
292 where S: Into<Cow<'c, str>>
293 {
294 parse_cookie(s, false)
295 }
296
297 /// Parses a `Cookie` from the given HTTP cookie header value string where
298 /// the name and value fields are percent-encoded. Percent-decodes the
299 /// name/value fields.
300 ///
301 /// # Example
302 ///
303 /// ```
304 /// use cookie::Cookie;
305 ///
306 /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap();
307 /// assert_eq!(c.name_value(), ("foo", "bar baz"));
308 /// assert_eq!(c.http_only(), Some(true));
309 /// ```
310 #[cfg(feature = "percent-encode")]
311 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
312 pub fn parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError>
313 where S: Into<Cow<'c, str>>
314 {
315 parse_cookie(s, true)
316 }
317
318 /// Converts `self` into a `Cookie` with a static lifetime with as few
319 /// allocations as possible.
320 ///
321 /// # Example
322 ///
323 /// ```
324 /// use cookie::Cookie;
325 ///
326 /// let c = Cookie::new("a", "b");
327 /// let owned_cookie = c.into_owned();
328 /// assert_eq!(owned_cookie.name_value(), ("a", "b"));
329 /// ```
330 pub fn into_owned(self) -> Cookie<'static> {
331 Cookie {
332 cookie_string: self.cookie_string.map(|s| s.into_owned().into()),
333 name: self.name.into_owned(),
334 value: self.value.into_owned(),
335 expires: self.expires,
336 max_age: self.max_age,
337 domain: self.domain.map(|s| s.into_owned()),
338 path: self.path.map(|s| s.into_owned()),
339 secure: self.secure,
340 http_only: self.http_only,
341 same_site: self.same_site,
342 }
343 }
344
345 /// Returns the name of `self`.
346 ///
347 /// # Example
348 ///
349 /// ```
350 /// use cookie::Cookie;
351 ///
352 /// let c = Cookie::new("name", "value");
353 /// assert_eq!(c.name(), "name");
354 /// ```
355 #[inline]
356 pub fn name(&self) -> &str {
357 self.name.to_str(self.cookie_string.as_ref())
358 }
359
360 /// Returns the value of `self`.
361 ///
362 /// # Example
363 ///
364 /// ```
365 /// use cookie::Cookie;
366 ///
367 /// let c = Cookie::new("name", "value");
368 /// assert_eq!(c.value(), "value");
369 /// ```
370 #[inline]
371 pub fn value(&self) -> &str {
372 self.value.to_str(self.cookie_string.as_ref())
373 }
374
375 /// Returns the name and value of `self` as a tuple of `(name, value)`.
376 ///
377 /// # Example
378 ///
379 /// ```
380 /// use cookie::Cookie;
381 ///
382 /// let c = Cookie::new("name", "value");
383 /// assert_eq!(c.name_value(), ("name", "value"));
384 /// ```
385 #[inline]
386 pub fn name_value(&self) -> (&str, &str) {
387 (self.name(), self.value())
388 }
389
390 /// Returns whether this cookie was marked `HttpOnly` or not. Returns
391 /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
392 /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`,
393 /// and `None` otherwise.
394 ///
395 /// # Example
396 ///
397 /// ```
398 /// use cookie::Cookie;
399 ///
400 /// let c = Cookie::parse("name=value; httponly").unwrap();
401 /// assert_eq!(c.http_only(), Some(true));
402 ///
403 /// let mut c = Cookie::new("name", "value");
404 /// assert_eq!(c.http_only(), None);
405 ///
406 /// let mut c = Cookie::new("name", "value");
407 /// assert_eq!(c.http_only(), None);
408 ///
409 /// // An explicitly set "false" value.
410 /// c.set_http_only(false);
411 /// assert_eq!(c.http_only(), Some(false));
412 ///
413 /// // An explicitly set "true" value.
414 /// c.set_http_only(true);
415 /// assert_eq!(c.http_only(), Some(true));
416 /// ```
417 #[inline]
418 pub fn http_only(&self) -> Option<bool> {
419 self.http_only
420 }
421
422 /// Returns whether this cookie was marked `Secure` or not. Returns
423 /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
424 /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and
425 /// `None` otherwise.
426 ///
427 /// # Example
428 ///
429 /// ```
430 /// use cookie::Cookie;
431 ///
432 /// let c = Cookie::parse("name=value; Secure").unwrap();
433 /// assert_eq!(c.secure(), Some(true));
434 ///
435 /// let mut c = Cookie::parse("name=value").unwrap();
436 /// assert_eq!(c.secure(), None);
437 ///
438 /// let mut c = Cookie::new("name", "value");
439 /// assert_eq!(c.secure(), None);
440 ///
441 /// // An explicitly set "false" value.
442 /// c.set_secure(false);
443 /// assert_eq!(c.secure(), Some(false));
444 ///
445 /// // An explicitly set "true" value.
446 /// c.set_secure(true);
447 /// assert_eq!(c.secure(), Some(true));
448 /// ```
449 #[inline]
450 pub fn secure(&self) -> Option<bool> {
451 self.secure
452 }
453
454 /// Returns the `SameSite` attribute of this cookie if one was specified.
455 ///
456 /// # Example
457 ///
458 /// ```
459 /// use cookie::{Cookie, SameSite};
460 ///
461 /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap();
462 /// assert_eq!(c.same_site(), Some(SameSite::Lax));
463 /// ```
464 #[inline]
465 pub fn same_site(&self) -> Option<SameSite> {
466 self.same_site
467 }
468
469 /// Returns the specified max-age of the cookie if one was specified.
470 ///
471 /// # Example
472 ///
473 /// ```
474 /// use cookie::Cookie;
475 ///
476 /// let c = Cookie::parse("name=value").unwrap();
477 /// assert_eq!(c.max_age(), None);
478 ///
479 /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap();
480 /// assert_eq!(c.max_age().map(|age| age.whole_hours()), Some(1));
481 /// ```
482 #[inline]
483 pub fn max_age(&self) -> Option<Duration> {
484 self.max_age
485 }
486
487 /// Returns the `Path` of the cookie if one was specified.
488 ///
489 /// # Example
490 ///
491 /// ```
492 /// use cookie::Cookie;
493 ///
494 /// let c = Cookie::parse("name=value").unwrap();
495 /// assert_eq!(c.path(), None);
496 ///
497 /// let c = Cookie::parse("name=value; Path=/").unwrap();
498 /// assert_eq!(c.path(), Some("/"));
499 ///
500 /// let c = Cookie::parse("name=value; path=/sub").unwrap();
501 /// assert_eq!(c.path(), Some("/sub"));
502 /// ```
503 #[inline]
504 pub fn path(&self) -> Option<&str> {
505 match self.path {
506 Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
507 None => None,
508 }
509 }
510
511 /// Returns the `Domain` of the cookie if one was specified.
512 ///
513 /// # Example
514 ///
515 /// ```
516 /// use cookie::Cookie;
517 ///
518 /// let c = Cookie::parse("name=value").unwrap();
519 /// assert_eq!(c.domain(), None);
520 ///
521 /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap();
522 /// assert_eq!(c.domain(), Some("crates.io"));
523 /// ```
524 #[inline]
525 pub fn domain(&self) -> Option<&str> {
526 match self.domain {
527 Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
528 None => None,
529 }
530 }
531
532 /// Returns the [`Expiration`] of the cookie if one was specified.
533 ///
534 /// # Example
535 ///
536 /// ```
537 /// use cookie::{Cookie, Expiration};
538 ///
539 /// let c = Cookie::parse("name=value").unwrap();
540 /// assert_eq!(c.expires(), None);
541 ///
542 /// // Here, `cookie.expires_datetime()` returns `None`.
543 /// let c = Cookie::build("name", "value").expires(None).finish();
544 /// assert_eq!(c.expires(), Some(Expiration::Session));
545 ///
546 /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
547 /// let cookie_str = format!("name=value; Expires={}", expire_time);
548 /// let c = Cookie::parse(cookie_str).unwrap();
549 /// assert_eq!(c.expires().and_then(|e| e.datetime()).map(|t| t.year()), Some(2017));
550 /// ```
551 #[inline]
552 pub fn expires(&self) -> Option<Expiration> {
553 self.expires
554 }
555
556 /// Returns the expiration date-time of the cookie if one was specified.
557 ///
558 /// # Example
559 ///
560 /// ```
561 /// use cookie::Cookie;
562 ///
563 /// let c = Cookie::parse("name=value").unwrap();
564 /// assert_eq!(c.expires_datetime(), None);
565 ///
566 /// // Here, `cookie.expires()` returns `Some`.
567 /// let c = Cookie::build("name", "value").expires(None).finish();
568 /// assert_eq!(c.expires_datetime(), None);
569 ///
570 /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
571 /// let cookie_str = format!("name=value; Expires={}", expire_time);
572 /// let c = Cookie::parse(cookie_str).unwrap();
573 /// assert_eq!(c.expires_datetime().map(|t| t.year()), Some(2017));
574 /// ```
575 #[inline]
576 pub fn expires_datetime(&self) -> Option<OffsetDateTime> {
577 self.expires.and_then(|e| e.datetime())
578 }
579
580 /// Sets the name of `self` to `name`.
581 ///
582 /// # Example
583 ///
584 /// ```
585 /// use cookie::Cookie;
586 ///
587 /// let mut c = Cookie::new("name", "value");
588 /// assert_eq!(c.name(), "name");
589 ///
590 /// c.set_name("foo");
591 /// assert_eq!(c.name(), "foo");
592 /// ```
593 pub fn set_name<N: Into<Cow<'c, str>>>(&mut self, name: N) {
594 self.name = CookieStr::Concrete(name.into())
595 }
596
597 /// Sets the value of `self` to `value`.
598 ///
599 /// # Example
600 ///
601 /// ```
602 /// use cookie::Cookie;
603 ///
604 /// let mut c = Cookie::new("name", "value");
605 /// assert_eq!(c.value(), "value");
606 ///
607 /// c.set_value("bar");
608 /// assert_eq!(c.value(), "bar");
609 /// ```
610 pub fn set_value<V: Into<Cow<'c, str>>>(&mut self, value: V) {
611 self.value = CookieStr::Concrete(value.into())
612 }
613
614 /// Sets the value of `http_only` in `self` to `value`. If `value` is
615 /// `None`, the field is unset.
616 ///
617 /// # Example
618 ///
619 /// ```
620 /// use cookie::Cookie;
621 ///
622 /// let mut c = Cookie::new("name", "value");
623 /// assert_eq!(c.http_only(), None);
624 ///
625 /// c.set_http_only(true);
626 /// assert_eq!(c.http_only(), Some(true));
627 ///
628 /// c.set_http_only(false);
629 /// assert_eq!(c.http_only(), Some(false));
630 ///
631 /// c.set_http_only(None);
632 /// assert_eq!(c.http_only(), None);
633 /// ```
634 #[inline]
635 pub fn set_http_only<T: Into<Option<bool>>>(&mut self, value: T) {
636 self.http_only = value.into();
637 }
638
639 /// Sets the value of `secure` in `self` to `value`. If `value` is `None`,
640 /// the field is unset.
641 ///
642 /// # Example
643 ///
644 /// ```
645 /// use cookie::Cookie;
646 ///
647 /// let mut c = Cookie::new("name", "value");
648 /// assert_eq!(c.secure(), None);
649 ///
650 /// c.set_secure(true);
651 /// assert_eq!(c.secure(), Some(true));
652 ///
653 /// c.set_secure(false);
654 /// assert_eq!(c.secure(), Some(false));
655 ///
656 /// c.set_secure(None);
657 /// assert_eq!(c.secure(), None);
658 /// ```
659 #[inline]
660 pub fn set_secure<T: Into<Option<bool>>>(&mut self, value: T) {
661 self.secure = value.into();
662 }
663
664 /// Sets the value of `same_site` in `self` to `value`. If `value` is
665 /// `None`, the field is unset. If `value` is `SameSite::None`, the "Secure"
666 /// flag will be set when the cookie is written out unless `secure` is
667 /// explicitly set to `false` via [`Cookie::set_secure()`] or the equivalent
668 /// builder method.
669 ///
670 /// [HTTP draft]: https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
671 ///
672 /// # Example
673 ///
674 /// ```
675 /// use cookie::{Cookie, SameSite};
676 ///
677 /// let mut c = Cookie::new("name", "value");
678 /// assert_eq!(c.same_site(), None);
679 ///
680 /// c.set_same_site(SameSite::None);
681 /// assert_eq!(c.same_site(), Some(SameSite::None));
682 /// assert_eq!(c.to_string(), "name=value; SameSite=None; Secure");
683 ///
684 /// c.set_secure(false);
685 /// assert_eq!(c.to_string(), "name=value; SameSite=None");
686 ///
687 /// let mut c = Cookie::new("name", "value");
688 /// assert_eq!(c.same_site(), None);
689 ///
690 /// c.set_same_site(SameSite::Strict);
691 /// assert_eq!(c.same_site(), Some(SameSite::Strict));
692 /// assert_eq!(c.to_string(), "name=value; SameSite=Strict");
693 ///
694 /// c.set_same_site(None);
695 /// assert_eq!(c.same_site(), None);
696 /// assert_eq!(c.to_string(), "name=value");
697 /// ```
698 #[inline]
699 pub fn set_same_site<T: Into<Option<SameSite>>>(&mut self, value: T) {
700 self.same_site = value.into();
701 }
702
703 /// Sets the value of `max_age` in `self` to `value`. If `value` is `None`,
704 /// the field is unset.
705 ///
706 /// # Example
707 ///
708 /// ```rust
709 /// # extern crate cookie;
710 /// extern crate time;
711 ///
712 /// use cookie::Cookie;
713 /// use time::Duration;
714 ///
715 /// # fn main() {
716 /// let mut c = Cookie::new("name", "value");
717 /// assert_eq!(c.max_age(), None);
718 ///
719 /// c.set_max_age(Duration::hours(10));
720 /// assert_eq!(c.max_age(), Some(Duration::hours(10)));
721 ///
722 /// c.set_max_age(None);
723 /// assert!(c.max_age().is_none());
724 /// # }
725 /// ```
726 #[inline]
727 pub fn set_max_age<D: Into<Option<Duration>>>(&mut self, value: D) {
728 self.max_age = value.into();
729 }
730
731 /// Sets the `path` of `self` to `path`.
732 ///
733 /// # Example
734 ///
735 /// ```rust
736 /// use cookie::Cookie;
737 ///
738 /// let mut c = Cookie::new("name", "value");
739 /// assert_eq!(c.path(), None);
740 ///
741 /// c.set_path("/");
742 /// assert_eq!(c.path(), Some("/"));
743 /// ```
744 pub fn set_path<P: Into<Cow<'c, str>>>(&mut self, path: P) {
745 self.path = Some(CookieStr::Concrete(path.into()));
746 }
747
748 /// Unsets the `path` of `self`.
749 ///
750 /// # Example
751 ///
752 /// ```
753 /// use cookie::Cookie;
754 ///
755 /// let mut c = Cookie::new("name", "value");
756 /// assert_eq!(c.path(), None);
757 ///
758 /// c.set_path("/");
759 /// assert_eq!(c.path(), Some("/"));
760 ///
761 /// c.unset_path();
762 /// assert_eq!(c.path(), None);
763 /// ```
764 pub fn unset_path(&mut self) {
765 self.path = None;
766 }
767
768 /// Sets the `domain` of `self` to `domain`.
769 ///
770 /// # Example
771 ///
772 /// ```
773 /// use cookie::Cookie;
774 ///
775 /// let mut c = Cookie::new("name", "value");
776 /// assert_eq!(c.domain(), None);
777 ///
778 /// c.set_domain("rust-lang.org");
779 /// assert_eq!(c.domain(), Some("rust-lang.org"));
780 /// ```
781 pub fn set_domain<D: Into<Cow<'c, str>>>(&mut self, domain: D) {
782 self.domain = Some(CookieStr::Concrete(domain.into()));
783 }
784
785 /// Unsets the `domain` of `self`.
786 ///
787 /// # Example
788 ///
789 /// ```
790 /// use cookie::Cookie;
791 ///
792 /// let mut c = Cookie::new("name", "value");
793 /// assert_eq!(c.domain(), None);
794 ///
795 /// c.set_domain("rust-lang.org");
796 /// assert_eq!(c.domain(), Some("rust-lang.org"));
797 ///
798 /// c.unset_domain();
799 /// assert_eq!(c.domain(), None);
800 /// ```
801 pub fn unset_domain(&mut self) {
802 self.domain = None;
803 }
804
805 /// Sets the expires field of `self` to `time`. If `time` is `None`, an
806 /// expiration of [`Session`](Expiration::Session) is set.
807 ///
808 /// # Example
809 ///
810 /// ```
811 /// # extern crate cookie;
812 /// extern crate time;
813 /// use cookie::{Cookie, Expiration};
814 /// use time::{Duration, OffsetDateTime};
815 ///
816 /// let mut c = Cookie::new("name", "value");
817 /// assert_eq!(c.expires(), None);
818 ///
819 /// let mut now = OffsetDateTime::now();
820 /// now += Duration::weeks(52);
821 ///
822 /// c.set_expires(now);
823 /// assert!(c.expires().is_some());
824 ///
825 /// c.set_expires(None);
826 /// assert_eq!(c.expires(), Some(Expiration::Session));
827 /// ```
828 pub fn set_expires<T: Into<Expiration>>(&mut self, time: T) {
829 use time::{date, time, offset};
830 static MAX_DATETIME: OffsetDateTime = date!(9999-12-31)
831 .with_time(time!(23:59:59.999_999))
832 .assume_utc()
833 .to_offset(offset!(UTC));
834
835 // RFC 6265 requires dates not to exceed 9999 years.
836 self.expires = Some(time.into()
837 .map(|time| std::cmp::min(time, MAX_DATETIME)));
838 }
839
840 /// Unsets the `expires` of `self`.
841 ///
842 /// # Example
843 ///
844 /// ```
845 /// use cookie::{Cookie, Expiration};
846 ///
847 /// let mut c = Cookie::new("name", "value");
848 /// assert_eq!(c.expires(), None);
849 ///
850 /// c.set_expires(None);
851 /// assert_eq!(c.expires(), Some(Expiration::Session));
852 ///
853 /// c.unset_expires();
854 /// assert_eq!(c.expires(), None);
855 /// ```
856 pub fn unset_expires(&mut self) {
857 self.expires = None;
858 }
859
860 /// Makes `self` a "permanent" cookie by extending its expiration and max
861 /// age 20 years into the future.
862 ///
863 /// # Example
864 ///
865 /// ```rust
866 /// # extern crate cookie;
867 /// extern crate time;
868 ///
869 /// use cookie::Cookie;
870 /// use time::Duration;
871 ///
872 /// # fn main() {
873 /// let mut c = Cookie::new("foo", "bar");
874 /// assert!(c.expires().is_none());
875 /// assert!(c.max_age().is_none());
876 ///
877 /// c.make_permanent();
878 /// assert!(c.expires().is_some());
879 /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
880 /// # }
881 /// ```
882 pub fn make_permanent(&mut self) {
883 let twenty_years = Duration::days(365 * 20);
884 self.set_max_age(twenty_years);
885 self.set_expires(OffsetDateTime::now_utc() + twenty_years);
886 }
887
888 /// Make `self` a "removal" cookie by clearing its value, setting a max-age
889 /// of `0`, and setting an expiration date far in the past.
890 ///
891 /// # Example
892 ///
893 /// ```rust
894 /// # extern crate cookie;
895 /// extern crate time;
896 ///
897 /// use cookie::Cookie;
898 /// use time::Duration;
899 ///
900 /// # fn main() {
901 /// let mut c = Cookie::new("foo", "bar");
902 /// c.make_permanent();
903 /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
904 /// assert_eq!(c.value(), "bar");
905 ///
906 /// c.make_removal();
907 /// assert_eq!(c.value(), "");
908 /// assert_eq!(c.max_age(), Some(Duration::zero()));
909 /// # }
910 /// ```
911 pub fn make_removal(&mut self) {
912 self.set_value("");
913 self.set_max_age(Duration::seconds(0));
914 self.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
915 }
916
917 fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result {
918 if let Some(true) = self.http_only() {
919 write!(f, "; HttpOnly")?;
920 }
921
922 if let Some(same_site) = self.same_site() {
923 write!(f, "; SameSite={}", same_site)?;
924
925 if same_site.is_none() && self.secure().is_none() {
926 write!(f, "; Secure")?;
927 }
928 }
929
930 if let Some(true) = self.secure() {
931 write!(f, "; Secure")?;
932 }
933
934 if let Some(path) = self.path() {
935 write!(f, "; Path={}", path)?;
936 }
937
938 if let Some(domain) = self.domain() {
939 write!(f, "; Domain={}", domain)?;
940 }
941
942 if let Some(max_age) = self.max_age() {
943 write!(f, "; Max-Age={}", max_age.whole_seconds())?;
944 }
945
946 if let Some(time) = self.expires_datetime() {
947 let time = time.to_offset(UtcOffset::UTC);
948 write!(f, "; Expires={}", time.format("%a, %d %b %Y %H:%M:%S GMT"))?;
949 }
950
951 Ok(())
952 }
953
954 /// Returns the name of `self` as a string slice of the raw string `self`
955 /// was originally parsed from. If `self` was not originally parsed from a
956 /// raw string, returns `None`.
957 ///
958 /// This method differs from [`Cookie::name()`] in that it returns a string
959 /// with the same lifetime as the originally parsed string. This lifetime
960 /// may outlive `self`. If a longer lifetime is not required, or you're
961 /// unsure if you need a longer lifetime, use [`Cookie::name()`].
962 ///
963 /// # Example
964 ///
965 /// ```
966 /// use cookie::Cookie;
967 ///
968 /// let cookie_string = format!("{}={}", "foo", "bar");
969 ///
970 /// // `c` will be dropped at the end of the scope, but `name` will live on
971 /// let name = {
972 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
973 /// c.name_raw()
974 /// };
975 ///
976 /// assert_eq!(name, Some("foo"));
977 /// ```
978 #[inline]
979 pub fn name_raw(&self) -> Option<&'c str> {
980 self.cookie_string.as_ref()
981 .and_then(|s| self.name.to_raw_str(s))
982 }
983
984 /// Returns the value of `self` as a string slice of the raw string `self`
985 /// was originally parsed from. If `self` was not originally parsed from a
986 /// raw string, returns `None`.
987 ///
988 /// This method differs from [`Cookie::value()`] in that it returns a
989 /// string with the same lifetime as the originally parsed string. This
990 /// lifetime may outlive `self`. If a longer lifetime is not required, or
991 /// you're unsure if you need a longer lifetime, use [`Cookie::value()`].
992 ///
993 /// # Example
994 ///
995 /// ```
996 /// use cookie::Cookie;
997 ///
998 /// let cookie_string = format!("{}={}", "foo", "bar");
999 ///
1000 /// // `c` will be dropped at the end of the scope, but `value` will live on
1001 /// let value = {
1002 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
1003 /// c.value_raw()
1004 /// };
1005 ///
1006 /// assert_eq!(value, Some("bar"));
1007 /// ```
1008 #[inline]
1009 pub fn value_raw(&self) -> Option<&'c str> {
1010 self.cookie_string.as_ref()
1011 .and_then(|s| self.value.to_raw_str(s))
1012 }
1013
1014 /// Returns the `Path` of `self` as a string slice of the raw string `self`
1015 /// was originally parsed from. If `self` was not originally parsed from a
1016 /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has
1017 /// changed since parsing, returns `None`.
1018 ///
1019 /// This method differs from [`Cookie::path()`] in that it returns a
1020 /// string with the same lifetime as the originally parsed string. This
1021 /// lifetime may outlive `self`. If a longer lifetime is not required, or
1022 /// you're unsure if you need a longer lifetime, use [`Cookie::path()`].
1023 ///
1024 /// # Example
1025 ///
1026 /// ```
1027 /// use cookie::Cookie;
1028 ///
1029 /// let cookie_string = format!("{}={}; Path=/", "foo", "bar");
1030 ///
1031 /// // `c` will be dropped at the end of the scope, but `path` will live on
1032 /// let path = {
1033 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
1034 /// c.path_raw()
1035 /// };
1036 ///
1037 /// assert_eq!(path, Some("/"));
1038 /// ```
1039 #[inline]
1040 pub fn path_raw(&self) -> Option<&'c str> {
1041 match (self.path.as_ref(), self.cookie_string.as_ref()) {
1042 (Some(path), Some(string)) => path.to_raw_str(string),
1043 _ => None,
1044 }
1045 }
1046
1047 /// Returns the `Domain` of `self` as a string slice of the raw string
1048 /// `self` was originally parsed from. If `self` was not originally parsed
1049 /// from a raw string, or if `self` doesn't contain a `Domain`, or if the
1050 /// `Domain` has changed since parsing, returns `None`.
1051 ///
1052 /// This method differs from [`Cookie::domain()`] in that it returns a
1053 /// string with the same lifetime as the originally parsed string. This
1054 /// lifetime may outlive `self` struct. If a longer lifetime is not
1055 /// required, or you're unsure if you need a longer lifetime, use
1056 /// [`Cookie::domain()`].
1057 ///
1058 /// # Example
1059 ///
1060 /// ```
1061 /// use cookie::Cookie;
1062 ///
1063 /// let cookie_string = format!("{}={}; Domain=crates.io", "foo", "bar");
1064 ///
1065 /// //`c` will be dropped at the end of the scope, but `domain` will live on
1066 /// let domain = {
1067 /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
1068 /// c.domain_raw()
1069 /// };
1070 ///
1071 /// assert_eq!(domain, Some("crates.io"));
1072 /// ```
1073 #[inline]
1074 pub fn domain_raw(&self) -> Option<&'c str> {
1075 match (self.domain.as_ref(), self.cookie_string.as_ref()) {
1076 (Some(domain), Some(string)) => domain.to_raw_str(string),
1077 _ => None,
1078 }
1079 }
1080
1081 /// Wraps `self` in an encoded [`Display`]: a cost-free wrapper around
1082 /// `Cookie` whose [`fmt::Display`] implementation percent-encodes the name
1083 /// and value of the wrapped `Cookie`.
1084 ///
1085 /// The returned structure can be chained with [`Display::stripped()`] to
1086 /// display only the name and value.
1087 ///
1088 /// # Example
1089 ///
1090 /// ```rust
1091 /// use cookie::Cookie;
1092 ///
1093 /// let mut c = Cookie::build("my name", "this; value?").secure(true).finish();
1094 /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F; Secure");
1095 /// assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%3F");
1096 /// ```
1097 #[cfg(feature = "percent-encode")]
1098 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
1099 #[inline(always)]
1100 pub fn encoded<'a>(&'a self) -> Display<'a, 'c> {
1101 Display::new_encoded(self)
1102 }
1103
1104 /// Wraps `self` in a stripped `Display`]: a cost-free wrapper around
1105 /// `Cookie` whose [`fmt::Display`] implementation prints only the `name`
1106 /// and `value` of the wrapped `Cookie`.
1107 ///
1108 /// The returned structure can be chained with [`Display::encoded()`] to
1109 /// encode the name and value.
1110 ///
1111 /// # Example
1112 ///
1113 /// ```rust
1114 /// use cookie::Cookie;
1115 ///
1116 /// let mut c = Cookie::build("key?", "value").secure(true).path("/").finish();
1117 /// assert_eq!(&c.stripped().to_string(), "key?=value");
1118 #[cfg_attr(feature = "percent-encode", doc = r##"
1119// Note: `encoded()` is only available when `percent-encode` is enabled.
1120assert_eq!(&c.stripped().encoded().to_string(), "key%3F=value");
1121 #"##)]
1122 /// ```
1123 #[inline(always)]
1124 pub fn stripped<'a>(&'a self) -> Display<'a, 'c> {
1125 Display::new_stripped(self)
1126 }
1127}
1128
1129/// https://url.spec.whatwg.org/#fragment-percent-encode-set
1130#[cfg(feature = "percent-encode")]
1131const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
1132
1133/// https://url.spec.whatwg.org/#path-percent-encode-set
1134#[cfg(feature = "percent-encode")]
1135const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
1136
1137/// https://url.spec.whatwg.org/#userinfo-percent-encode-set
1138#[cfg(feature = "percent-encode")]
1139const USERINFO_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET
1140 .add(b'/')
1141 .add(b':')
1142 .add(b';')
1143 .add(b'=')
1144 .add(b'@')
1145 .add(b'[')
1146 .add(b'\\')
1147 .add(b']')
1148 .add(b'^')
1149 .add(b'|')
1150 .add(b'%');
1151
1152#[cfg(feature = "percent-encode")]
1153const COOKIE_ENCODE_SET: &AsciiSet = &USERINFO_ENCODE_SET
1154 .add(b'(')
1155 .add(b')')
1156 .add(b',');
1157
1158/// Wrapper around `Cookie` whose `Display` implementation either
1159/// percent-encodes the cookie's name and value, skips displaying the cookie's
1160/// parameters (only displaying it's name and value), or both.
1161///
1162/// A value of this type can be obtained via [`Cookie::encoded()`] and
1163/// [`Cookie::stripped()`], or an arbitrary chaining of the two methods. This
1164/// type should only be used for its `Display` implementation.
1165///
1166/// # Example
1167///
1168/// ```rust
1169/// use cookie::Cookie;
1170///
1171/// let c = Cookie::build("my name", "this; value%?").secure(true).finish();
1172/// assert_eq!(&c.stripped().to_string(), "my name=this; value%?");
1173#[cfg_attr(feature = "percent-encode", doc = r##"
1174// Note: `encoded()` is only available when `percent-encode` is enabled.
1175assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%25%3F; Secure");
1176assert_eq!(&c.stripped().encoded().to_string(), "my%20name=this%3B%20value%25%3F");
1177assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%25%3F");
1178"##)]
1179/// ```
1180pub struct Display<'a, 'c: 'a> {
1181 cookie: &'a Cookie<'c>,
1182 #[cfg(feature = "percent-encode")]
1183 encode: bool,
1184 strip: bool,
1185}
1186
1187impl<'a, 'c: 'a> fmt::Display for Display<'a, 'c> {
1188 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1189 #[cfg(feature = "percent-encode")] {
1190 if self.encode {
1191 let name = encode(self.cookie.name().as_bytes(), COOKIE_ENCODE_SET);
1192 let value = encode(self.cookie.value().as_bytes(), COOKIE_ENCODE_SET);
1193 write!(f, "{}={}", name, value)?;
1194 } else {
1195 write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1196 }
1197 }
1198
1199 #[cfg(not(feature = "percent-encode"))] {
1200 write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
1201 }
1202
1203 match self.strip {
1204 true => Ok(()),
1205 false => self.cookie.fmt_parameters(f)
1206 }
1207 }
1208}
1209
1210impl<'a, 'c> Display<'a, 'c> {
1211 #[cfg(feature = "percent-encode")]
1212 fn new_encoded(cookie: &'a Cookie<'c>) -> Self {
1213 Display { cookie, strip: false, encode: true }
1214 }
1215
1216 fn new_stripped(cookie: &'a Cookie<'c>) -> Self {
1217 Display { cookie, strip: true, #[cfg(feature = "percent-encode")] encode: false }
1218 }
1219
1220 /// Percent-encode the name and value pair.
1221 #[inline]
1222 #[cfg(feature = "percent-encode")]
1223 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
1224 pub fn encoded(mut self) -> Self {
1225 self.encode = true;
1226 self
1227 }
1228
1229 /// Only display the name and value.
1230 #[inline]
1231 pub fn stripped(mut self) -> Self {
1232 self.strip = true;
1233 self
1234 }
1235}
1236
1237impl<'c> fmt::Display for Cookie<'c> {
1238 /// Formats the cookie `self` as a `Set-Cookie` header value.
1239 ///
1240 /// Does _not_ percent-encode any values. To percent-encode, use
1241 /// [`Cookie::encoded()`].
1242 ///
1243 /// # Example
1244 ///
1245 /// ```rust
1246 /// use cookie::Cookie;
1247 ///
1248 /// let mut cookie = Cookie::build("foo", "bar")
1249 /// .path("/")
1250 /// .finish();
1251 ///
1252 /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1253 /// ```
1254 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1255 write!(f, "{}={}", self.name(), self.value())?;
1256 self.fmt_parameters(f)
1257 }
1258}
1259
1260impl FromStr for Cookie<'static> {
1261 type Err = ParseError;
1262
1263 fn from_str(s: &str) -> Result<Cookie<'static>, ParseError> {
1264 Cookie::parse(s).map(|c| c.into_owned())
1265 }
1266}
1267
1268impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
1269 fn eq(&self, other: &Cookie<'b>) -> bool {
1270 let so_far_so_good = self.name() == other.name()
1271 && self.value() == other.value()
1272 && self.http_only() == other.http_only()
1273 && self.secure() == other.secure()
1274 && self.max_age() == other.max_age()
1275 && self.expires() == other.expires();
1276
1277 if !so_far_so_good {
1278 return false;
1279 }
1280
1281 match (self.path(), other.path()) {
1282 (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1283 (None, None) => {}
1284 _ => return false,
1285 };
1286
1287 match (self.domain(), other.domain()) {
1288 (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
1289 (None, None) => {}
1290 _ => return false,
1291 };
1292
1293 true
1294 }
1295}
1296
1297#[cfg(test)]
1298mod tests {
1299 use crate::{Cookie, SameSite};
1300 use crate::parse::parse_gmt_date;
1301 use crate::{time::Duration, OffsetDateTime};
1302
1303 #[test]
1304 fn format() {
1305 let cookie = Cookie::new("foo", "bar");
1306 assert_eq!(&cookie.to_string(), "foo=bar");
1307
1308 let cookie = Cookie::build("foo", "bar")
1309 .http_only(true).finish();
1310 assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
1311
1312 let cookie = Cookie::build("foo", "bar")
1313 .max_age(Duration::seconds(10)).finish();
1314 assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
1315
1316 let cookie = Cookie::build("foo", "bar")
1317 .secure(true).finish();
1318 assert_eq!(&cookie.to_string(), "foo=bar; Secure");
1319
1320 let cookie = Cookie::build("foo", "bar")
1321 .path("/").finish();
1322 assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
1323
1324 let cookie = Cookie::build("foo", "bar")
1325 .domain("www.rust-lang.org").finish();
1326 assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
1327
1328 let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
1329 let expires = parse_gmt_date(time_str, "%a, %d %b %Y %H:%M:%S GMT").unwrap();
1330 let cookie = Cookie::build("foo", "bar")
1331 .expires(expires).finish();
1332 assert_eq!(&cookie.to_string(),
1333 "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT");
1334
1335 let cookie = Cookie::build("foo", "bar")
1336 .same_site(SameSite::Strict).finish();
1337 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict");
1338
1339 let cookie = Cookie::build("foo", "bar")
1340 .same_site(SameSite::Lax).finish();
1341 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax");
1342
1343 let mut cookie = Cookie::build("foo", "bar")
1344 .same_site(SameSite::None).finish();
1345 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
1346
1347 cookie.set_same_site(None);
1348 assert_eq!(&cookie.to_string(), "foo=bar");
1349
1350 let mut cookie = Cookie::build("foo", "bar")
1351 .same_site(SameSite::None)
1352 .secure(false)
1353 .finish();
1354 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None");
1355 cookie.set_secure(true);
1356 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
1357 }
1358
1359 #[test]
1360 #[ignore]
1361 fn format_date_wraps() {
1362 let expires = OffsetDateTime::unix_epoch() + Duration::max_value();
1363 let cookie = Cookie::build("foo", "bar")
1364 .expires(expires).finish();
1365 assert_eq!(&cookie.to_string(),
1366 "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT");
1367 }
1368
1369 #[test]
1370 fn cookie_string_long_lifetimes() {
1371 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1372 let (name, value, path, domain) = {
1373 // Create a cookie passing a slice
1374 let c = Cookie::parse(cookie_string.as_str()).unwrap();
1375 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1376 };
1377
1378 assert_eq!(name, Some("bar"));
1379 assert_eq!(value, Some("baz"));
1380 assert_eq!(path, Some("/subdir"));
1381 assert_eq!(domain, Some("crates.io"));
1382 }
1383
1384 #[test]
1385 fn owned_cookie_string() {
1386 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
1387 let (name, value, path, domain) = {
1388 // Create a cookie passing an owned string
1389 let c = Cookie::parse(cookie_string).unwrap();
1390 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1391 };
1392
1393 assert_eq!(name, None);
1394 assert_eq!(value, None);
1395 assert_eq!(path, None);
1396 assert_eq!(domain, None);
1397 }
1398
1399 #[test]
1400 fn owned_cookie_struct() {
1401 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io";
1402 let (name, value, path, domain) = {
1403 // Create an owned cookie
1404 let c = Cookie::parse(cookie_string).unwrap().into_owned();
1405
1406 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
1407 };
1408
1409 assert_eq!(name, None);
1410 assert_eq!(value, None);
1411 assert_eq!(path, None);
1412 assert_eq!(domain, None);
1413 }
1414
1415 #[test]
1416 #[cfg(feature = "percent-encode")]
1417 fn format_encoded() {
1418 let cookie = Cookie::build("foo !%?=", "bar;;, a").finish();
1419 let cookie_str = cookie.encoded().to_string();
1420 assert_eq!(&cookie_str, "foo%20!%25%3F%3D=bar%3B%3B%2C%20a");
1421
1422 let cookie = Cookie::parse_encoded(cookie_str).unwrap();
1423 assert_eq!(cookie.name_value(), ("foo !%?=", "bar;;, a"));
1424 }
1425}