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