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