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; 83 extern crate time; 84 85 mod builder; 86 mod parse; 87 mod jar; 88 mod delta; 89 mod draft; 90 mod expiration; 91 92 #[cfg(any(feature = "private", feature = "signed"))] #[macro_use] mod secure; 93 #[cfg(any(feature = "private", feature = "signed"))] pub use secure::*; 94 95 use std::borrow::Cow; 96 use std::fmt; 97 use std::str::FromStr; 98 99 #[allow(unused_imports, deprecated)] 100 use std::ascii::AsciiExt; 101 102 #[cfg(feature = "percent-encode")] 103 use percent_encoding::{AsciiSet, percent_encode as encode}; 104 use time::{Duration, OffsetDateTime, UtcOffset}; 105 106 use crate::parse::parse_cookie; 107 pub use crate::parse::ParseError; 108 pub use crate::builder::CookieBuilder; 109 pub use crate::jar::{CookieJar, Delta, Iter}; 110 pub use crate::draft::*; 111 pub use crate::expiration::*; 112 113 #[derive(Debug, Clone)] 114 enum 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 121 impl<'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. to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str129 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)] to_raw_str<'s, 'b: 's>(&'s self, string: &'s Cow<'b, str>) -> Option<&'b str>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 into_owned(self) -> CookieStr<'static>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)] 191 pub 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 215 impl<'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 /// ``` new<N, V>(name: N, value: V) -> Self where N: Into<Cow<'c, str>>, V: Into<Cow<'c, str>>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 /// ``` named<N>(name: N) -> Cookie<'c> where N: Into<Cow<'c, str>>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 /// ``` build<N, V>(name: N, value: V) -> CookieBuilder<'c> where N: Into<Cow<'c, str>>, V: Into<Cow<'c, str>>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 /// ``` parse<S>(s: S) -> Result<Cookie<'c>, ParseError> where S: Into<Cow<'c, str>>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")))] parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError> where S: Into<Cow<'c, str>>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 /// ``` into_owned(self) -> Cookie<'static>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] name(&self) -> &str356 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] value(&self) -> &str371 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] name_value(&self) -> (&str, &str)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] http_only(&self) -> Option<bool>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] secure(&self) -> Option<bool>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] same_site(&self) -> Option<SameSite>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] max_age(&self) -> Option<Duration>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] path(&self) -> Option<&str>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] domain(&self) -> Option<&str>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] expires(&self) -> Option<Expiration>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] expires_datetime(&self) -> Option<OffsetDateTime>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 /// ``` set_name<N: Into<Cow<'c, str>>>(&mut self, name: N)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 /// ``` set_value<V: Into<Cow<'c, str>>>(&mut self, value: V)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] set_http_only<T: Into<Option<bool>>>(&mut self, value: T)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] set_secure<T: Into<Option<bool>>>(&mut self, value: T)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] set_same_site<T: Into<Option<SameSite>>>(&mut self, value: T)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] set_max_age<D: Into<Option<Duration>>>(&mut self, value: D)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 /// ``` set_path<P: Into<Cow<'c, str>>>(&mut self, path: P)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 /// ``` unset_path(&mut self)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 /// ``` set_domain<D: Into<Cow<'c, str>>>(&mut self, domain: D)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 /// ``` unset_domain(&mut self)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 /// ``` set_expires<T: Into<Expiration>>(&mut self, time: T)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 /// ``` unset_expires(&mut self)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 /// ``` make_permanent(&mut self)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 /// ``` make_removal(&mut self)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 fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result917 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] name_raw(&self) -> Option<&'c str>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] value_raw(&self) -> Option<&'c str>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] path_raw(&self) -> Option<&'c str>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] domain_raw(&self) -> Option<&'c str>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)] encoded<'a>(&'a self) -> Display<'a, 'c>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. 1120 assert_eq!(&c.stripped().encoded().to_string(), "key%3F=value"); 1121 #"##)] 1122 /// ``` 1123 #[inline(always)] stripped<'a>(&'a self) -> Display<'a, 'c>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")] 1131 const 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")] 1135 const 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")] 1139 const 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 /// Wrapper around `Cookie` whose `Display` implementation either 1153 /// percent-encodes the cookie's name and value, skips displaying the cookie's 1154 /// parameters (only displaying it's name and value), or both. 1155 /// 1156 /// A value of this type can be obtained via [`Cookie::encoded()`] and 1157 /// [`Cookie::stripped()`], or an arbitrary chaining of the two methods. This 1158 /// type should only be used for its `Display` implementation. 1159 /// 1160 /// # Example 1161 /// 1162 /// ```rust 1163 /// use cookie::Cookie; 1164 /// 1165 /// let c = Cookie::build("my name", "this; value%?").secure(true).finish(); 1166 /// assert_eq!(&c.stripped().to_string(), "my name=this; value%?"); 1167 #[cfg_attr(feature = "percent-encode", doc = r##" 1168 // Note: `encoded()` is only available when `percent-encode` is enabled. 1169 assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%25%3F; Secure"); 1170 assert_eq!(&c.stripped().encoded().to_string(), "my%20name=this%3B%20value%25%3F"); 1171 assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%25%3F"); 1172 "##)] 1173 /// ``` 1174 pub struct Display<'a, 'c: 'a> { 1175 cookie: &'a Cookie<'c>, 1176 #[cfg(feature = "percent-encode")] 1177 encode: bool, 1178 strip: bool, 1179 } 1180 1181 impl<'a, 'c: 'a> fmt::Display for Display<'a, 'c> { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result1182 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1183 #[cfg(feature = "percent-encode")] { 1184 if self.encode { 1185 let name = encode(self.cookie.name().as_bytes(), USERINFO_ENCODE_SET); 1186 let value = encode(self.cookie.value().as_bytes(), USERINFO_ENCODE_SET); 1187 write!(f, "{}={}", name, value)?; 1188 } else { 1189 write!(f, "{}={}", self.cookie.name(), self.cookie.value())?; 1190 } 1191 } 1192 1193 #[cfg(not(feature = "percent-encode"))] { 1194 write!(f, "{}={}", self.cookie.name(), self.cookie.value())?; 1195 } 1196 1197 match self.strip { 1198 true => Ok(()), 1199 false => self.cookie.fmt_parameters(f) 1200 } 1201 } 1202 } 1203 1204 impl<'a, 'c> Display<'a, 'c> { 1205 #[cfg(feature = "percent-encode")] new_encoded(cookie: &'a Cookie<'c>) -> Self1206 fn new_encoded(cookie: &'a Cookie<'c>) -> Self { 1207 Display { cookie, strip: false, encode: true } 1208 } 1209 new_stripped(cookie: &'a Cookie<'c>) -> Self1210 fn new_stripped(cookie: &'a Cookie<'c>) -> Self { 1211 Display { cookie, strip: true, #[cfg(feature = "percent-encode")] encode: false } 1212 } 1213 1214 /// Percent-encode the name and value pair. 1215 #[inline] 1216 #[cfg(feature = "percent-encode")] 1217 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))] encoded(mut self) -> Self1218 pub fn encoded(mut self) -> Self { 1219 self.encode = true; 1220 self 1221 } 1222 1223 /// Only display the name and value. 1224 #[inline] stripped(mut self) -> Self1225 pub fn stripped(mut self) -> Self { 1226 self.strip = true; 1227 self 1228 } 1229 } 1230 1231 impl<'c> fmt::Display for Cookie<'c> { 1232 /// Formats the cookie `self` as a `Set-Cookie` header value. 1233 /// 1234 /// Does _not_ percent-encode any values. To percent-encode, use 1235 /// [`Cookie::encoded()`]. 1236 /// 1237 /// # Example 1238 /// 1239 /// ```rust 1240 /// use cookie::Cookie; 1241 /// 1242 /// let mut cookie = Cookie::build("foo", "bar") 1243 /// .path("/") 1244 /// .finish(); 1245 /// 1246 /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); 1247 /// ``` fmt(&self, f: &mut fmt::Formatter) -> fmt::Result1248 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1249 write!(f, "{}={}", self.name(), self.value())?; 1250 self.fmt_parameters(f) 1251 } 1252 } 1253 1254 impl FromStr for Cookie<'static> { 1255 type Err = ParseError; 1256 from_str(s: &str) -> Result<Cookie<'static>, ParseError>1257 fn from_str(s: &str) -> Result<Cookie<'static>, ParseError> { 1258 Cookie::parse(s).map(|c| c.into_owned()) 1259 } 1260 } 1261 1262 impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> { eq(&self, other: &Cookie<'b>) -> bool1263 fn eq(&self, other: &Cookie<'b>) -> bool { 1264 let so_far_so_good = self.name() == other.name() 1265 && self.value() == other.value() 1266 && self.http_only() == other.http_only() 1267 && self.secure() == other.secure() 1268 && self.max_age() == other.max_age() 1269 && self.expires() == other.expires(); 1270 1271 if !so_far_so_good { 1272 return false; 1273 } 1274 1275 match (self.path(), other.path()) { 1276 (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} 1277 (None, None) => {} 1278 _ => return false, 1279 }; 1280 1281 match (self.domain(), other.domain()) { 1282 (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} 1283 (None, None) => {} 1284 _ => return false, 1285 }; 1286 1287 true 1288 } 1289 } 1290 1291 #[cfg(test)] 1292 mod tests { 1293 use crate::{Cookie, SameSite}; 1294 use crate::parse::parse_gmt_date; 1295 use crate::{time::Duration, OffsetDateTime}; 1296 1297 #[test] format()1298 fn format() { 1299 let cookie = Cookie::new("foo", "bar"); 1300 assert_eq!(&cookie.to_string(), "foo=bar"); 1301 1302 let cookie = Cookie::build("foo", "bar") 1303 .http_only(true).finish(); 1304 assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly"); 1305 1306 let cookie = Cookie::build("foo", "bar") 1307 .max_age(Duration::seconds(10)).finish(); 1308 assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10"); 1309 1310 let cookie = Cookie::build("foo", "bar") 1311 .secure(true).finish(); 1312 assert_eq!(&cookie.to_string(), "foo=bar; Secure"); 1313 1314 let cookie = Cookie::build("foo", "bar") 1315 .path("/").finish(); 1316 assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); 1317 1318 let cookie = Cookie::build("foo", "bar") 1319 .domain("www.rust-lang.org").finish(); 1320 assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org"); 1321 1322 let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; 1323 let expires = parse_gmt_date(time_str, "%a, %d %b %Y %H:%M:%S GMT").unwrap(); 1324 let cookie = Cookie::build("foo", "bar") 1325 .expires(expires).finish(); 1326 assert_eq!(&cookie.to_string(), 1327 "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT"); 1328 1329 let cookie = Cookie::build("foo", "bar") 1330 .same_site(SameSite::Strict).finish(); 1331 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict"); 1332 1333 let cookie = Cookie::build("foo", "bar") 1334 .same_site(SameSite::Lax).finish(); 1335 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax"); 1336 1337 let mut cookie = Cookie::build("foo", "bar") 1338 .same_site(SameSite::None).finish(); 1339 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure"); 1340 1341 cookie.set_same_site(None); 1342 assert_eq!(&cookie.to_string(), "foo=bar"); 1343 1344 let mut cookie = Cookie::build("foo", "bar") 1345 .same_site(SameSite::None) 1346 .secure(false) 1347 .finish(); 1348 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None"); 1349 cookie.set_secure(true); 1350 assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure"); 1351 } 1352 1353 #[test] 1354 #[ignore] format_date_wraps()1355 fn format_date_wraps() { 1356 let expires = OffsetDateTime::unix_epoch() + Duration::max_value(); 1357 let cookie = Cookie::build("foo", "bar") 1358 .expires(expires).finish(); 1359 assert_eq!(&cookie.to_string(), 1360 "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT"); 1361 } 1362 1363 #[test] cookie_string_long_lifetimes()1364 fn cookie_string_long_lifetimes() { 1365 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); 1366 let (name, value, path, domain) = { 1367 // Create a cookie passing a slice 1368 let c = Cookie::parse(cookie_string.as_str()).unwrap(); 1369 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) 1370 }; 1371 1372 assert_eq!(name, Some("bar")); 1373 assert_eq!(value, Some("baz")); 1374 assert_eq!(path, Some("/subdir")); 1375 assert_eq!(domain, Some("crates.io")); 1376 } 1377 1378 #[test] owned_cookie_string()1379 fn owned_cookie_string() { 1380 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); 1381 let (name, value, path, domain) = { 1382 // Create a cookie passing an owned string 1383 let c = Cookie::parse(cookie_string).unwrap(); 1384 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) 1385 }; 1386 1387 assert_eq!(name, None); 1388 assert_eq!(value, None); 1389 assert_eq!(path, None); 1390 assert_eq!(domain, None); 1391 } 1392 1393 #[test] owned_cookie_struct()1394 fn owned_cookie_struct() { 1395 let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io"; 1396 let (name, value, path, domain) = { 1397 // Create an owned cookie 1398 let c = Cookie::parse(cookie_string).unwrap().into_owned(); 1399 1400 (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) 1401 }; 1402 1403 assert_eq!(name, None); 1404 assert_eq!(value, None); 1405 assert_eq!(path, None); 1406 assert_eq!(domain, None); 1407 } 1408 1409 #[test] 1410 #[cfg(feature = "percent-encode")] format_encoded()1411 fn format_encoded() { 1412 let cookie = Cookie::build("foo !?=", "bar;; a").finish(); 1413 let cookie_str = cookie.encoded().to_string(); 1414 assert_eq!(&cookie_str, "foo%20!%3F%3D=bar%3B%3B%20a"); 1415 1416 let cookie = Cookie::parse_encoded(cookie_str).unwrap(); 1417 assert_eq!(cookie.name_value(), ("foo !?=", "bar;; a")); 1418 } 1419 } 1420