1 use std::fmt::{self, Formatter}; 2 use std::io::{BufRead, Write}; 3 use std::iter; 4 use std::ops::Deref; 5 6 use ::cookie::Cookie as RawCookie; 7 use log::debug; 8 use publicsuffix; 9 use serde::de::{SeqAccess, Visitor}; 10 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 11 use url::Url; 12 13 use crate::cookie::Cookie; 14 use crate::cookie_domain::{is_match as domain_match, CookieDomain}; 15 use crate::cookie_path::is_match as path_match; 16 use crate::utils::{is_http_scheme, is_secure}; 17 use crate::CookieError; 18 19 #[cfg(feature = "preserve_order")] 20 use indexmap::IndexMap; 21 #[cfg(not(feature = "preserve_order"))] 22 use std::collections::HashMap; 23 #[cfg(feature = "preserve_order")] 24 type Map<K, V> = IndexMap<K, V>; 25 #[cfg(not(feature = "preserve_order"))] 26 type Map<K, V> = HashMap<K, V>; 27 28 type NameMap = Map<String, Cookie<'static>>; 29 type PathMap = Map<String, NameMap>; 30 type DomainMap = Map<String, PathMap>; 31 32 #[derive(PartialEq, Clone, Debug, Eq)] 33 pub enum StoreAction { 34 /// The `Cookie` was successfully added to the store 35 Inserted, 36 /// The `Cookie` successfully expired a `Cookie` already in the store 37 ExpiredExisting, 38 /// The `Cookie` was added to the store, replacing an existing entry 39 UpdatedExisting, 40 } 41 42 pub type StoreResult<T> = Result<T, crate::Error>; 43 pub type InsertResult = Result<StoreAction, CookieError>; 44 #[derive(Debug, Default)] 45 pub struct CookieStore { 46 /// Cookies stored by domain, path, then name 47 cookies: DomainMap, 48 /// If set, enables [public suffix](https://tools.ietf.org/html/rfc6265#section-5.3) rejection based on the provided `publicsuffix::List` 49 public_suffix_list: Option<publicsuffix::List>, 50 } 51 52 impl CookieStore { 53 /// Return an `Iterator` of the cookies for `url` in the store get_request_cookies(&self, url: &Url) -> impl Iterator<Item = &RawCookie<'static>>54 pub fn get_request_cookies(&self, url: &Url) -> impl Iterator<Item = &RawCookie<'static>> { 55 self.matches(url).into_iter().map(|c| c.deref()) 56 } 57 58 /// Store the `cookies` received from `url` store_response_cookies<I: Iterator<Item = RawCookie<'static>>>( &mut self, cookies: I, url: &Url, )59 pub fn store_response_cookies<I: Iterator<Item = RawCookie<'static>>>( 60 &mut self, 61 cookies: I, 62 url: &Url, 63 ) { 64 for cookie in cookies { 65 debug!("inserting Set-Cookie '{:?}'", cookie); 66 if let Err(e) = self.insert_raw(&cookie, url) { 67 debug!("unable to store Set-Cookie: {:?}", e); 68 } 69 } 70 } 71 72 /// Specify a `publicsuffix::List` for the `CookieStore` to allow [public suffix 73 /// matching](https://tools.ietf.org/html/rfc6265#section-5.3) with_suffix_list(self, psl: publicsuffix::List) -> CookieStore74 pub fn with_suffix_list(self, psl: publicsuffix::List) -> CookieStore { 75 CookieStore { 76 cookies: self.cookies, 77 public_suffix_list: Some(psl), 78 } 79 } 80 81 /// Returns true if the `CookieStore` contains an __unexpired__ `Cookie` corresponding to the 82 /// specified `domain`, `path`, and `name`. contains(&self, domain: &str, path: &str, name: &str) -> bool83 pub fn contains(&self, domain: &str, path: &str, name: &str) -> bool { 84 self.get(domain, path, name).is_some() 85 } 86 87 /// Returns true if the `CookieStore` contains any (even an __expired__) `Cookie` corresponding 88 /// to the specified `domain`, `path`, and `name`. contains_any(&self, domain: &str, path: &str, name: &str) -> bool89 pub fn contains_any(&self, domain: &str, path: &str, name: &str) -> bool { 90 self.get_any(domain, path, name).is_some() 91 } 92 93 /// Returns a reference to the __unexpired__ `Cookie` corresponding to the specified `domain`, 94 /// `path`, and `name`. get(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'_>>95 pub fn get(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'_>> { 96 self.get_any(domain, path, name).and_then(|cookie| { 97 if cookie.is_expired() { 98 None 99 } else { 100 Some(cookie) 101 } 102 }) 103 } 104 105 /// Returns a mutable reference to the __unexpired__ `Cookie` corresponding to the specified 106 /// `domain`, `path`, and `name`. get_mut(&mut self, domain: &str, path: &str, name: &str) -> Option<&mut Cookie<'static>>107 fn get_mut(&mut self, domain: &str, path: &str, name: &str) -> Option<&mut Cookie<'static>> { 108 self.get_mut_any(domain, path, name).and_then(|cookie| { 109 if cookie.is_expired() { 110 None 111 } else { 112 Some(cookie) 113 } 114 }) 115 } 116 117 /// Returns a reference to the (possibly __expired__) `Cookie` corresponding to the specified 118 /// `domain`, `path`, and `name`. get_any(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'static>>119 pub fn get_any(&self, domain: &str, path: &str, name: &str) -> Option<&Cookie<'static>> { 120 self.cookies.get(domain).and_then(|domain_cookies| { 121 domain_cookies 122 .get(path) 123 .and_then(|path_cookies| path_cookies.get(name)) 124 }) 125 } 126 127 /// Returns a mutable reference to the (possibly __expired__) `Cookie` corresponding to the 128 /// specified `domain`, `path`, and `name`. get_mut_any( &mut self, domain: &str, path: &str, name: &str, ) -> Option<&mut Cookie<'static>>129 fn get_mut_any( 130 &mut self, 131 domain: &str, 132 path: &str, 133 name: &str, 134 ) -> Option<&mut Cookie<'static>> { 135 self.cookies.get_mut(domain).and_then(|domain_cookies| { 136 domain_cookies 137 .get_mut(path) 138 .and_then(|path_cookies| path_cookies.get_mut(name)) 139 }) 140 } 141 142 /// Removes a `Cookie` from the store, returning the `Cookie` if it was in the store remove(&mut self, domain: &str, path: &str, name: &str) -> Option<Cookie<'static>>143 pub fn remove(&mut self, domain: &str, path: &str, name: &str) -> Option<Cookie<'static>> { 144 #[cfg(not(feature = "preserve_order"))] 145 fn map_remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V> 146 where 147 K: std::borrow::Borrow<Q> + std::cmp::Eq + std::hash::Hash, 148 Q: std::cmp::Eq + std::hash::Hash + ?Sized, 149 { 150 map.remove(key) 151 } 152 #[cfg(feature = "preserve_order")] 153 fn map_remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V> 154 where 155 K: std::borrow::Borrow<Q> + std::cmp::Eq + std::hash::Hash, 156 Q: std::cmp::Eq + std::hash::Hash + ?Sized, 157 { 158 map.shift_remove(key) 159 } 160 161 let (removed, remove_domain) = match self.cookies.get_mut(domain) { 162 None => (None, false), 163 Some(domain_cookies) => { 164 let (removed, remove_path) = match domain_cookies.get_mut(path) { 165 None => (None, false), 166 Some(path_cookies) => { 167 let removed = map_remove(path_cookies, name); 168 (removed, path_cookies.is_empty()) 169 } 170 }; 171 172 if remove_path { 173 map_remove(domain_cookies, path); 174 (removed, domain_cookies.is_empty()) 175 } else { 176 (removed, false) 177 } 178 } 179 }; 180 181 if remove_domain { 182 map_remove(&mut self.cookies, domain); 183 } 184 185 removed 186 } 187 188 /// Returns a collection of references to __unexpired__ cookies that path- and domain-match 189 /// `request_url`, as well as having HttpOnly and Secure attributes compatible with the 190 /// `request_url`. matches(&self, request_url: &Url) -> Vec<&Cookie<'static>>191 pub fn matches(&self, request_url: &Url) -> Vec<&Cookie<'static>> { 192 // although we domain_match and path_match as we descend through the tree, we 193 // still need to 194 // do a full Cookie::matches() check in the last filter. Otherwise, we cannot 195 // properly deal 196 // with HostOnly Cookies. 197 let cookies = self 198 .cookies 199 .iter() 200 .filter(|&(d, _)| domain_match(d, request_url)) 201 .flat_map(|(_, dcs)| { 202 dcs.iter() 203 .filter(|&(p, _)| path_match(p, request_url)) 204 .flat_map(|(_, pcs)| { 205 pcs.values() 206 .filter(|c| !c.is_expired() && c.matches(request_url)) 207 }) 208 }); 209 match (!is_http_scheme(request_url), !is_secure(request_url)) { 210 (true, true) => cookies 211 .filter(|c| !c.http_only().unwrap_or(false) && !c.secure().unwrap_or(false)) 212 .collect(), 213 (true, false) => cookies 214 .filter(|c| !c.http_only().unwrap_or(false)) 215 .collect(), 216 (false, true) => cookies.filter(|c| !c.secure().unwrap_or(false)).collect(), 217 (false, false) => cookies.collect(), 218 } 219 } 220 221 /// Parses a new `Cookie` from `cookie_str` and inserts it into the store. parse(&mut self, cookie_str: &str, request_url: &Url) -> InsertResult222 pub fn parse(&mut self, cookie_str: &str, request_url: &Url) -> InsertResult { 223 Cookie::parse(cookie_str, request_url) 224 .and_then(|cookie| self.insert(cookie.into_owned(), request_url)) 225 } 226 227 /// Converts a `cookie::Cookie` (from the `cookie` crate) into a `cookie_store::Cookie` and 228 /// inserts it into the store. insert_raw(&mut self, cookie: &RawCookie<'_>, request_url: &Url) -> InsertResult229 pub fn insert_raw(&mut self, cookie: &RawCookie<'_>, request_url: &Url) -> InsertResult { 230 Cookie::try_from_raw_cookie(cookie, request_url) 231 .and_then(|cookie| self.insert(cookie.into_owned(), request_url)) 232 } 233 234 /// Inserts `cookie`, received from `request_url`, into the store, following the rules of the 235 /// [IETF RFC6265 Storage Model](http://tools.ietf.org/html/rfc6265#section-5.3). If the 236 /// `Cookie` is __unexpired__ and is successfully inserted, returns 237 /// `Ok(StoreAction::Inserted)`. If the `Cookie` is __expired__ *and* matches an existing 238 /// `Cookie` in the store, the existing `Cookie` wil be `expired()` and 239 /// `Ok(StoreAction::ExpiredExisting)` will be returned. insert(&mut self, mut cookie: Cookie<'static>, request_url: &Url) -> InsertResult240 pub fn insert(&mut self, mut cookie: Cookie<'static>, request_url: &Url) -> InsertResult { 241 if cookie.http_only().unwrap_or(false) && !is_http_scheme(request_url) { 242 // If the cookie was received from a "non-HTTP" API and the 243 // cookie's http-only-flag is set, abort these steps and ignore the 244 // cookie entirely. 245 return Err(CookieError::NonHttpScheme); 246 } else if let Some(ref psl) = self.public_suffix_list { 247 // If the user agent is configured to reject "public suffixes" 248 if cookie.domain.is_public_suffix(psl) { 249 // and the domain-attribute is a public suffix: 250 if cookie.domain.host_is_identical(request_url) { 251 // If the domain-attribute is identical to the canonicalized 252 // request-host: 253 // Let the domain-attribute be the empty string. 254 // (NB: at this point, an empty domain-attribute should be represented 255 // as the HostOnly variant of CookieDomain) 256 cookie.domain = CookieDomain::host_only(request_url)?; 257 } else { 258 // Otherwise: 259 // Ignore the cookie entirely and abort these steps. 260 return Err(CookieError::PublicSuffix); 261 } 262 } 263 } else if !cookie.domain.matches(request_url) { 264 // If the canonicalized request-host does not domain-match the 265 // domain-attribute: 266 // Ignore the cookie entirely and abort these steps. 267 return Err(CookieError::DomainMismatch); 268 } 269 // NB: we do not bail out above on is_expired(), as servers can remove a cookie 270 // by sending 271 // an expired one, so we need to do the old_cookie check below before checking 272 // is_expired() on an incoming cookie 273 274 { 275 // At this point in parsing, any non-present Domain attribute should have been 276 // converted into a HostOnly variant 277 let cookie_domain = cookie 278 .domain 279 .as_cow() 280 .ok_or_else(|| CookieError::UnspecifiedDomain)?; 281 if let Some(old_cookie) = self.get_mut(&cookie_domain, &cookie.path, cookie.name()) { 282 if old_cookie.http_only().unwrap_or(false) && !is_http_scheme(request_url) { 283 // 2. If the newly created cookie was received from a "non-HTTP" 284 // API and the old-cookie's http-only-flag is set, abort these 285 // steps and ignore the newly created cookie entirely. 286 return Err(CookieError::NonHttpScheme); 287 } else if cookie.is_expired() { 288 old_cookie.expire(); 289 return Ok(StoreAction::ExpiredExisting); 290 } 291 } 292 } 293 294 if !cookie.is_expired() { 295 Ok( 296 if self 297 .cookies 298 .entry(String::from(&cookie.domain)) 299 .or_insert_with(Map::new) 300 .entry(String::from(&cookie.path)) 301 .or_insert_with(Map::new) 302 .insert(cookie.name().to_owned(), cookie) 303 .is_none() 304 { 305 StoreAction::Inserted 306 } else { 307 StoreAction::UpdatedExisting 308 }, 309 ) 310 } else { 311 Err(CookieError::Expired) 312 } 313 } 314 315 /// Clear the contents of the store clear(&mut self)316 pub fn clear(&mut self) { 317 self.cookies.clear() 318 } 319 320 /// An iterator visiting all the __unexpired__ cookies in the store iter_unexpired<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a321 pub fn iter_unexpired<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a { 322 self.cookies 323 .values() 324 .flat_map(|dcs| dcs.values()) 325 .flat_map(|pcs| pcs.values()) 326 .filter(|c| !c.is_expired()) 327 } 328 329 /// An iterator visiting all (including __expired__) cookies in the store iter_any<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a330 pub fn iter_any<'a>(&'a self) -> impl Iterator<Item = &'a Cookie<'static>> + 'a { 331 self.cookies 332 .values() 333 .flat_map(|dcs| dcs.values()) 334 .flat_map(|pcs| pcs.values()) 335 } 336 337 /// Serialize any __unexpired__ and __persistent__ cookies in the store with `cookie_to_string` 338 /// and write them to `writer` save<W, E, F>(&self, writer: &mut W, cookie_to_string: F) -> StoreResult<()> where W: Write, F: Fn(&Cookie<'static>) -> Result<String, E>, crate::Error: From<E>,339 pub fn save<W, E, F>(&self, writer: &mut W, cookie_to_string: F) -> StoreResult<()> 340 where 341 W: Write, 342 F: Fn(&Cookie<'static>) -> Result<String, E>, 343 crate::Error: From<E>, 344 { 345 for cookie in self.iter_unexpired().filter_map(|c| { 346 if c.is_persistent() { 347 Some(cookie_to_string(c)) 348 } else { 349 None 350 } 351 }) { 352 writeln!(writer, "{}", cookie?)?; 353 } 354 Ok(()) 355 } 356 357 /// Serialize any __unexpired__ and __persistent__ cookies in the store to JSON format and 358 /// write them to `writer` save_json<W: Write>(&self, writer: &mut W) -> StoreResult<()>359 pub fn save_json<W: Write>(&self, writer: &mut W) -> StoreResult<()> { 360 self.save(writer, ::serde_json::to_string) 361 } 362 363 /// Load cookies from `reader`, deserializing with `cookie_from_str`, skipping any __expired__ 364 /// cookies load<R, E, F>(reader: R, cookie_from_str: F) -> StoreResult<CookieStore> where R: BufRead, F: Fn(&str) -> Result<Cookie<'static>, E>, crate::Error: From<E>,365 pub fn load<R, E, F>(reader: R, cookie_from_str: F) -> StoreResult<CookieStore> 366 where 367 R: BufRead, 368 F: Fn(&str) -> Result<Cookie<'static>, E>, 369 crate::Error: From<E>, 370 { 371 let cookies = reader.lines().filter_map(|line_result| { 372 let cookie = line_result 373 .map_err(Into::into) 374 .and_then(|line| cookie_from_str(&line).map_err(crate::Error::from)); 375 match cookie { 376 Ok(c) if c.is_expired() => None, 377 _ => Some(cookie), 378 } 379 }); 380 Self::from_cookies(cookies) 381 } 382 383 /// Load JSON-formatted cookies from `reader`, skipping any __expired__ cookies load_json<R: BufRead>(reader: R) -> StoreResult<CookieStore>384 pub fn load_json<R: BufRead>(reader: R) -> StoreResult<CookieStore> { 385 CookieStore::load(reader, |cookie| ::serde_json::from_str(cookie)) 386 } 387 from_cookies<I, E>(iter: I) -> Result<Self, E> where I: IntoIterator<Item = Result<Cookie<'static>, E>>,388 fn from_cookies<I, E>(iter: I) -> Result<Self, E> 389 where 390 I: IntoIterator<Item = Result<Cookie<'static>, E>>, 391 { 392 let mut cookies = Map::new(); 393 for cookie in iter { 394 let cookie = cookie?; 395 if !cookie.is_expired() { 396 cookies 397 .entry(String::from(&cookie.domain)) 398 .or_insert_with(Map::new) 399 .entry(String::from(&cookie.path)) 400 .or_insert_with(Map::new) 401 .insert(cookie.name().to_owned(), cookie); 402 } 403 } 404 Ok(Self { 405 cookies, 406 public_suffix_list: None, 407 }) 408 } 409 } 410 411 impl Serialize for CookieStore { serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,412 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 413 where 414 S: Serializer, 415 { 416 serializer.collect_seq(self.iter_unexpired().filter(|c| c.is_persistent())) 417 } 418 } 419 420 struct CookieStoreVisitor; 421 422 impl<'de> Visitor<'de> for CookieStoreVisitor { 423 type Value = CookieStore; 424 expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result425 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 426 write!(formatter, "a sequence of cookies") 427 } 428 visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> where A: SeqAccess<'de>,429 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 430 where 431 A: SeqAccess<'de>, 432 { 433 CookieStore::from_cookies(iter::from_fn(|| seq.next_element().transpose())) 434 } 435 } 436 437 impl<'de> Deserialize<'de> for CookieStore { deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,438 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 439 where 440 D: Deserializer<'de>, 441 { 442 deserializer.deserialize_seq(CookieStoreVisitor) 443 } 444 } 445 446 #[cfg(test)] 447 mod tests { 448 use super::CookieStore; 449 use super::{InsertResult, StoreAction}; 450 use crate::cookie::Cookie; 451 use crate::CookieError; 452 use ::cookie::Cookie as RawCookie; 453 use std::str::from_utf8; 454 use time::OffsetDateTime; 455 456 use crate::utils::test as test_utils; 457 458 macro_rules! has_str { 459 ($e: expr, $i: ident) => {{ 460 let val = from_utf8(&$i[..]).unwrap(); 461 assert!(val.contains($e), "exp: {}\nval: {}", $e, val); 462 }}; 463 } 464 macro_rules! not_has_str { 465 ($e: expr, $i: ident) => {{ 466 let val = from_utf8(&$i[..]).unwrap(); 467 assert!(!val.contains($e), "exp: {}\nval: {}", $e, val); 468 }}; 469 } 470 macro_rules! inserted { 471 ($e: expr) => { 472 assert_eq!(Ok(StoreAction::Inserted), $e) 473 }; 474 } 475 macro_rules! updated { 476 ($e: expr) => { 477 assert_eq!(Ok(StoreAction::UpdatedExisting), $e) 478 }; 479 } 480 macro_rules! expired_existing { 481 ($e: expr) => { 482 assert_eq!(Ok(StoreAction::ExpiredExisting), $e) 483 }; 484 } 485 macro_rules! domain_mismatch { 486 ($e: expr) => { 487 assert_eq!(Err(CookieError::DomainMismatch), $e) 488 }; 489 } 490 macro_rules! non_http_scheme { 491 ($e: expr) => { 492 assert_eq!(Err(CookieError::NonHttpScheme), $e) 493 }; 494 } 495 macro_rules! non_rel_scheme { 496 ($e: expr) => { 497 assert_eq!(Err(CookieError::NonRelativeScheme), $e) 498 }; 499 } 500 macro_rules! expired_err { 501 ($e: expr) => { 502 assert_eq!(Err(CookieError::Expired), $e) 503 }; 504 } 505 macro_rules! values_are { 506 ($store: expr, $url: expr, $values: expr) => {{ 507 let mut matched_values = $store 508 .matches(&test_utils::url($url)) 509 .iter() 510 .map(|c| &c.value()[..]) 511 .collect::<Vec<_>>(); 512 matched_values.sort(); 513 514 let mut values: Vec<&str> = $values; 515 values.sort(); 516 517 assert!( 518 matched_values == values, 519 "\n{:?}\n!=\n{:?}\n", 520 matched_values, 521 values 522 ); 523 }}; 524 } 525 add_cookie( store: &mut CookieStore, cookie: &str, url: &str, expires: Option<OffsetDateTime>, max_age: Option<u64>, ) -> InsertResult526 fn add_cookie( 527 store: &mut CookieStore, 528 cookie: &str, 529 url: &str, 530 expires: Option<OffsetDateTime>, 531 max_age: Option<u64>, 532 ) -> InsertResult { 533 store.insert( 534 test_utils::make_cookie(cookie, url, expires, max_age), 535 &test_utils::url(url), 536 ) 537 } 538 make_match_store() -> CookieStore539 fn make_match_store() -> CookieStore { 540 let mut store = CookieStore::default(); 541 inserted!(add_cookie( 542 &mut store, 543 "cookie1=1", 544 "http://example.com/foo/bar", 545 None, 546 Some(60 * 5), 547 )); 548 inserted!(add_cookie( 549 &mut store, 550 "cookie2=2; Secure", 551 "https://example.com/sec/", 552 None, 553 Some(60 * 5), 554 )); 555 inserted!(add_cookie( 556 &mut store, 557 "cookie3=3; HttpOnly", 558 "https://example.com/sec/", 559 None, 560 Some(60 * 5), 561 )); 562 inserted!(add_cookie( 563 &mut store, 564 "cookie4=4; Secure; HttpOnly", 565 "https://example.com/sec/", 566 None, 567 Some(60 * 5), 568 )); 569 inserted!(add_cookie( 570 &mut store, 571 "cookie5=5", 572 "http://example.com/foo/", 573 None, 574 Some(60 * 5), 575 )); 576 inserted!(add_cookie( 577 &mut store, 578 "cookie6=6", 579 "http://example.com/", 580 None, 581 Some(60 * 5), 582 )); 583 inserted!(add_cookie( 584 &mut store, 585 "cookie7=7", 586 "http://bar.example.com/foo/", 587 None, 588 Some(60 * 5), 589 )); 590 591 inserted!(add_cookie( 592 &mut store, 593 "cookie8=8", 594 "http://example.org/foo/bar", 595 None, 596 Some(60 * 5), 597 )); 598 inserted!(add_cookie( 599 &mut store, 600 "cookie9=9", 601 "http://bar.example.org/foo/bar", 602 None, 603 Some(60 * 5), 604 )); 605 store 606 } 607 608 macro_rules! check_matches { 609 ($store: expr) => {{ 610 values_are!($store, "http://unknowndomain.org/foo/bar", vec![]); 611 values_are!($store, "http://example.org/foo/bar", vec!["8"]); 612 values_are!($store, "http://example.org/bus/bar", vec![]); 613 values_are!($store, "http://bar.example.org/foo/bar", vec!["9"]); 614 values_are!($store, "http://bar.example.org/bus/bar", vec![]); 615 values_are!( 616 $store, 617 "https://example.com/sec/foo", 618 vec!["6", "4", "3", "2"] 619 ); 620 values_are!($store, "http://example.com/sec/foo", vec!["6", "3"]); 621 values_are!($store, "ftp://example.com/sec/foo", vec!["6"]); 622 values_are!($store, "http://bar.example.com/foo/bar/bus", vec!["7"]); 623 values_are!( 624 $store, 625 "http://example.com/foo/bar/bus", 626 vec!["1", "5", "6"] 627 ); 628 }}; 629 } 630 631 #[test] insert_raw()632 fn insert_raw() { 633 let mut store = CookieStore::default(); 634 inserted!(store.insert_raw( 635 &RawCookie::parse("cookie1=value1").unwrap(), 636 &test_utils::url("http://example.com/foo/bar"), 637 )); 638 non_rel_scheme!(store.insert_raw( 639 &RawCookie::parse("cookie1=value1").unwrap(), 640 &test_utils::url("data:nonrelativescheme"), 641 )); 642 non_http_scheme!(store.insert_raw( 643 &RawCookie::parse("cookie1=value1; HttpOnly").unwrap(), 644 &test_utils::url("ftp://example.com/"), 645 )); 646 expired_existing!(store.insert_raw( 647 &RawCookie::parse("cookie1=value1; Max-Age=0").unwrap(), 648 &test_utils::url("http://example.com/foo/bar"), 649 )); 650 expired_err!(store.insert_raw( 651 &RawCookie::parse("cookie1=value1; Max-Age=-1").unwrap(), 652 &test_utils::url("http://example.com/foo/bar"), 653 )); 654 updated!(store.insert_raw( 655 &RawCookie::parse("cookie1=value1").unwrap(), 656 &test_utils::url("http://example.com/foo/bar"), 657 )); 658 expired_existing!(store.insert_raw( 659 &RawCookie::parse("cookie1=value1; Max-Age=-1").unwrap(), 660 &test_utils::url("http://example.com/foo/bar"), 661 )); 662 domain_mismatch!(store.insert_raw( 663 &RawCookie::parse("cookie1=value1; Domain=bar.example.com").unwrap(), 664 &test_utils::url("http://example.com/foo/bar"), 665 )); 666 } 667 668 #[test] parse()669 fn parse() { 670 let mut store = CookieStore::default(); 671 inserted!(store.parse( 672 "cookie1=value1", 673 &test_utils::url("http://example.com/foo/bar"), 674 )); 675 non_rel_scheme!(store.parse("cookie1=value1", &test_utils::url("data:nonrelativescheme"),)); 676 non_http_scheme!(store.parse( 677 "cookie1=value1; HttpOnly", 678 &test_utils::url("ftp://example.com/"), 679 )); 680 expired_existing!(store.parse( 681 "cookie1=value1; Max-Age=0", 682 &test_utils::url("http://example.com/foo/bar"), 683 )); 684 expired_err!(store.parse( 685 "cookie1=value1; Max-Age=-1", 686 &test_utils::url("http://example.com/foo/bar"), 687 )); 688 updated!(store.parse( 689 "cookie1=value1", 690 &test_utils::url("http://example.com/foo/bar"), 691 )); 692 expired_existing!(store.parse( 693 "cookie1=value1; Max-Age=-1", 694 &test_utils::url("http://example.com/foo/bar"), 695 )); 696 domain_mismatch!(store.parse( 697 "cookie1=value1; Domain=bar.example.com", 698 &test_utils::url("http://example.com/foo/bar"), 699 )); 700 } 701 702 #[test] save()703 fn save() { 704 let mut output = vec![]; 705 let mut store = CookieStore::default(); 706 store.save_json(&mut output).unwrap(); 707 assert_eq!("", from_utf8(&output[..]).unwrap()); 708 // non-persistent cookie, should not be saved 709 inserted!(add_cookie( 710 &mut store, 711 "cookie0=value0", 712 "http://example.com/foo/bar", 713 None, 714 None, 715 )); 716 store.save_json(&mut output).unwrap(); 717 assert_eq!("", from_utf8(&output[..]).unwrap()); 718 719 // persistent cookie, Max-Age 720 inserted!(add_cookie( 721 &mut store, 722 "cookie1=value1", 723 "http://example.com/foo/bar", 724 None, 725 Some(10), 726 )); 727 store.save_json(&mut output).unwrap(); 728 not_has_str!("cookie0=value0", output); 729 has_str!("cookie1=value1", output); 730 output.clear(); 731 732 // persistent cookie, Expires 733 inserted!(add_cookie( 734 &mut store, 735 "cookie2=value2", 736 "http://example.com/foo/bar", 737 Some(test_utils::in_days(1)), 738 None, 739 )); 740 store.save_json(&mut output).unwrap(); 741 not_has_str!("cookie0=value0", output); 742 has_str!("cookie1=value1", output); 743 has_str!("cookie2=value2", output); 744 output.clear(); 745 746 inserted!(add_cookie( 747 &mut store, 748 "cookie3=value3; Domain=example.com", 749 "http://foo.example.com/foo/bar", 750 Some(test_utils::in_days(1)), 751 None, 752 )); 753 inserted!(add_cookie( 754 &mut store, 755 "cookie4=value4; Path=/foo/", 756 "http://foo.example.com/foo/bar", 757 Some(test_utils::in_days(1)), 758 None, 759 )); 760 inserted!(add_cookie( 761 &mut store, 762 "cookie5=value5", 763 "http://127.0.0.1/foo/bar", 764 Some(test_utils::in_days(1)), 765 None, 766 )); 767 inserted!(add_cookie( 768 &mut store, 769 "cookie6=value6", 770 "http://[::1]/foo/bar", 771 Some(test_utils::in_days(1)), 772 None, 773 )); 774 inserted!(add_cookie( 775 &mut store, 776 "cookie7=value7; Secure", 777 "https://[::1]/foo/bar", 778 Some(test_utils::in_days(1)), 779 None, 780 )); 781 inserted!(add_cookie( 782 &mut store, 783 "cookie8=value8; HttpOnly", 784 "http://[::1]/foo/bar", 785 Some(test_utils::in_days(1)), 786 None, 787 )); 788 store.save_json(&mut output).unwrap(); 789 not_has_str!("cookie0=value0", output); 790 has_str!("cookie1=value1", output); 791 has_str!("cookie2=value2", output); 792 has_str!("cookie3=value3", output); 793 has_str!("cookie4=value4", output); 794 has_str!("cookie5=value5", output); 795 has_str!("cookie6=value6", output); 796 has_str!("cookie7=value7; Secure", output); 797 has_str!("cookie8=value8; HttpOnly", output); 798 output.clear(); 799 } 800 801 #[test] serialize()802 fn serialize() { 803 let mut output = vec![]; 804 let mut store = CookieStore::default(); 805 serde_json::to_writer(&mut output, &store).unwrap(); 806 assert_eq!("[]", from_utf8(&output[..]).unwrap()); 807 output.clear(); 808 809 // non-persistent cookie, should not be saved 810 inserted!(add_cookie( 811 &mut store, 812 "cookie0=value0", 813 "http://example.com/foo/bar", 814 None, 815 None, 816 )); 817 serde_json::to_writer(&mut output, &store).unwrap(); 818 assert_eq!("[]", from_utf8(&output[..]).unwrap()); 819 output.clear(); 820 821 // persistent cookie, Max-Age 822 inserted!(add_cookie( 823 &mut store, 824 "cookie1=value1", 825 "http://example.com/foo/bar", 826 None, 827 Some(10), 828 )); 829 serde_json::to_writer(&mut output, &store).unwrap(); 830 not_has_str!("cookie0=value0", output); 831 has_str!("cookie1=value1", output); 832 output.clear(); 833 834 // persistent cookie, Expires 835 inserted!(add_cookie( 836 &mut store, 837 "cookie2=value2", 838 "http://example.com/foo/bar", 839 Some(test_utils::in_days(1)), 840 None, 841 )); 842 serde_json::to_writer(&mut output, &store).unwrap(); 843 not_has_str!("cookie0=value0", output); 844 has_str!("cookie1=value1", output); 845 has_str!("cookie2=value2", output); 846 output.clear(); 847 848 inserted!(add_cookie( 849 &mut store, 850 "cookie3=value3; Domain=example.com", 851 "http://foo.example.com/foo/bar", 852 Some(test_utils::in_days(1)), 853 None, 854 )); 855 inserted!(add_cookie( 856 &mut store, 857 "cookie4=value4; Path=/foo/", 858 "http://foo.example.com/foo/bar", 859 Some(test_utils::in_days(1)), 860 None, 861 )); 862 inserted!(add_cookie( 863 &mut store, 864 "cookie5=value5", 865 "http://127.0.0.1/foo/bar", 866 Some(test_utils::in_days(1)), 867 None, 868 )); 869 inserted!(add_cookie( 870 &mut store, 871 "cookie6=value6", 872 "http://[::1]/foo/bar", 873 Some(test_utils::in_days(1)), 874 None, 875 )); 876 inserted!(add_cookie( 877 &mut store, 878 "cookie7=value7; Secure", 879 "https://[::1]/foo/bar", 880 Some(test_utils::in_days(1)), 881 None, 882 )); 883 inserted!(add_cookie( 884 &mut store, 885 "cookie8=value8; HttpOnly", 886 "http://[::1]/foo/bar", 887 Some(test_utils::in_days(1)), 888 None, 889 )); 890 serde_json::to_writer(&mut output, &store).unwrap(); 891 not_has_str!("cookie0=value0", output); 892 has_str!("cookie1=value1", output); 893 has_str!("cookie2=value2", output); 894 has_str!("cookie3=value3", output); 895 has_str!("cookie4=value4", output); 896 has_str!("cookie5=value5", output); 897 has_str!("cookie6=value6", output); 898 has_str!("cookie7=value7; Secure", output); 899 has_str!("cookie8=value8; HttpOnly", output); 900 output.clear(); 901 } 902 903 #[test] domains()904 fn domains() { 905 let mut store = CookieStore::default(); 906 // The user agent will reject cookies unless the Domain attribute 907 // specifies a scope for the cookie that would include the origin 908 // server. For example, the user agent will accept a cookie with a 909 // Domain attribute of "example.com" or of "foo.example.com" from 910 // foo.example.com, but the user agent will not accept a cookie with a 911 // Domain attribute of "bar.example.com" or of "baz.foo.example.com". 912 fn domain_cookie_from(domain: &str, request_url: &str) -> Cookie<'static> { 913 let cookie_str = format!("cookie1=value1; Domain={}", domain); 914 Cookie::parse(cookie_str, &test_utils::url(request_url)).unwrap() 915 } 916 917 { 918 let request_url = test_utils::url("http://foo.example.com"); 919 // foo.example.com can submit cookies for example.com and foo.example.com 920 inserted!(store.insert( 921 domain_cookie_from("example.com", "http://foo.example.com",), 922 &request_url, 923 )); 924 updated!(store.insert( 925 domain_cookie_from(".example.com", "http://foo.example.com",), 926 &request_url, 927 )); 928 inserted!(store.insert( 929 domain_cookie_from("foo.example.com", "http://foo.example.com",), 930 &request_url, 931 )); 932 updated!(store.insert( 933 domain_cookie_from(".foo.example.com", "http://foo.example.com",), 934 &request_url, 935 )); 936 // not for bar.example.com 937 domain_mismatch!(store.insert( 938 domain_cookie_from("bar.example.com", "http://bar.example.com",), 939 &request_url, 940 )); 941 domain_mismatch!(store.insert( 942 domain_cookie_from(".bar.example.com", "http://bar.example.com",), 943 &request_url, 944 )); 945 // not for bar.foo.example.com 946 domain_mismatch!(store.insert( 947 domain_cookie_from("bar.foo.example.com", "http://bar.foo.example.com",), 948 &request_url, 949 )); 950 domain_mismatch!(store.insert( 951 domain_cookie_from(".bar.foo.example.com", "http://bar.foo.example.com",), 952 &request_url, 953 )); 954 } 955 956 { 957 let request_url = test_utils::url("http://bar.example.com"); 958 // bar.example.com can submit for example.com and bar.example.com 959 updated!(store.insert( 960 domain_cookie_from("example.com", "http://foo.example.com",), 961 &request_url, 962 )); 963 updated!(store.insert( 964 domain_cookie_from(".example.com", "http://foo.example.com",), 965 &request_url, 966 )); 967 inserted!(store.insert( 968 domain_cookie_from("bar.example.com", "http://bar.example.com",), 969 &request_url, 970 )); 971 updated!(store.insert( 972 domain_cookie_from(".bar.example.com", "http://bar.example.com",), 973 &request_url, 974 )); 975 // bar.example.com cannot submit for foo.example.com 976 domain_mismatch!(store.insert( 977 domain_cookie_from("foo.example.com", "http://foo.example.com",), 978 &request_url, 979 )); 980 domain_mismatch!(store.insert( 981 domain_cookie_from(".foo.example.com", "http://foo.example.com",), 982 &request_url, 983 )); 984 } 985 { 986 let request_url = test_utils::url("http://example.com"); 987 // example.com can submit for example.com 988 updated!(store.insert( 989 domain_cookie_from("example.com", "http://foo.example.com",), 990 &request_url, 991 )); 992 updated!(store.insert( 993 domain_cookie_from(".example.com", "http://foo.example.com",), 994 &request_url, 995 )); 996 // example.com cannot submit for foo.example.com or bar.example.com 997 domain_mismatch!(store.insert( 998 domain_cookie_from("foo.example.com", "http://foo.example.com",), 999 &request_url, 1000 )); 1001 domain_mismatch!(store.insert( 1002 domain_cookie_from(".foo.example.com", "http://foo.example.com",), 1003 &request_url, 1004 )); 1005 domain_mismatch!(store.insert( 1006 domain_cookie_from("bar.example.com", "http://bar.example.com",), 1007 &request_url, 1008 )); 1009 domain_mismatch!(store.insert( 1010 domain_cookie_from(".bar.example.com", "http://bar.example.com",), 1011 &request_url, 1012 )); 1013 } 1014 } 1015 1016 #[test] http_only()1017 fn http_only() { 1018 let mut store = CookieStore::default(); 1019 let c = Cookie::parse( 1020 "cookie1=value1; HttpOnly", 1021 &test_utils::url("http://example.com/foo/bar"), 1022 ) 1023 .unwrap(); 1024 // cannot add a HttpOnly cookies from a non-http source 1025 non_http_scheme!(store.insert(c, &test_utils::url("ftp://example.com/foo/bar"),)); 1026 } 1027 1028 #[test] load()1029 fn load() { 1030 let mut store = CookieStore::default(); 1031 // non-persistent cookie, should not be saved 1032 inserted!(add_cookie( 1033 &mut store, 1034 "cookie0=value0", 1035 "http://example.com/foo/bar", 1036 None, 1037 None, 1038 )); 1039 // persistent cookie, Max-Age 1040 inserted!(add_cookie( 1041 &mut store, 1042 "cookie1=value1", 1043 "http://example.com/foo/bar", 1044 None, 1045 Some(10), 1046 )); 1047 // persistent cookie, Expires 1048 inserted!(add_cookie( 1049 &mut store, 1050 "cookie2=value2", 1051 "http://example.com/foo/bar", 1052 Some(test_utils::in_days(1)), 1053 None, 1054 )); 1055 inserted!(add_cookie( 1056 &mut store, 1057 "cookie3=value3; Domain=example.com", 1058 "http://foo.example.com/foo/bar", 1059 Some(test_utils::in_days(1)), 1060 None, 1061 )); 1062 inserted!(add_cookie( 1063 &mut store, 1064 "cookie4=value4; Path=/foo/", 1065 "http://foo.example.com/foo/bar", 1066 Some(test_utils::in_days(1)), 1067 None, 1068 )); 1069 inserted!(add_cookie( 1070 &mut store, 1071 "cookie5=value5", 1072 "http://127.0.0.1/foo/bar", 1073 Some(test_utils::in_days(1)), 1074 None, 1075 )); 1076 inserted!(add_cookie( 1077 &mut store, 1078 "cookie6=value6", 1079 "http://[::1]/foo/bar", 1080 Some(test_utils::in_days(1)), 1081 None, 1082 )); 1083 inserted!(add_cookie( 1084 &mut store, 1085 "cookie7=value7; Secure", 1086 "http://example.com/foo/bar", 1087 Some(test_utils::in_days(1)), 1088 None, 1089 )); 1090 inserted!(add_cookie( 1091 &mut store, 1092 "cookie8=value8; HttpOnly", 1093 "http://example.com/foo/bar", 1094 Some(test_utils::in_days(1)), 1095 None, 1096 )); 1097 let mut output = vec![]; 1098 store.save_json(&mut output).unwrap(); 1099 not_has_str!("cookie0=value0", output); 1100 has_str!("cookie1=value1", output); 1101 has_str!("cookie2=value2", output); 1102 has_str!("cookie3=value3", output); 1103 has_str!("cookie4=value4", output); 1104 has_str!("cookie5=value5", output); 1105 has_str!("cookie6=value6", output); 1106 has_str!("cookie7=value7; Secure", output); 1107 has_str!("cookie8=value8; HttpOnly", output); 1108 let store = CookieStore::load_json(&output[..]).unwrap(); 1109 assert!(store.get("example.com", "/foo", "cookie0").is_none()); 1110 assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1"); 1111 assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value2"); 1112 assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value3"); 1113 assert!( 1114 store 1115 .get("foo.example.com", "/foo/", "cookie4") 1116 .unwrap() 1117 .value() 1118 == "value4" 1119 ); 1120 assert!(store.get("127.0.0.1", "/foo", "cookie5").unwrap().value() == "value5"); 1121 assert!(store.get("[::1]", "/foo", "cookie6").unwrap().value() == "value6"); 1122 assert!(store.get("example.com", "/foo", "cookie7").unwrap().value() == "value7"); 1123 assert!(store.get("example.com", "/foo", "cookie8").unwrap().value() == "value8"); 1124 1125 output.clear(); 1126 let store = make_match_store(); 1127 store.save_json(&mut output).unwrap(); 1128 let store = CookieStore::load_json(&output[..]).unwrap(); 1129 check_matches!(&store); 1130 } 1131 1132 #[test] deserialize()1133 fn deserialize() { 1134 let mut store = CookieStore::default(); 1135 // non-persistent cookie, should not be saved 1136 inserted!(add_cookie( 1137 &mut store, 1138 "cookie0=value0", 1139 "http://example.com/foo/bar", 1140 None, 1141 None, 1142 )); 1143 // persistent cookie, Max-Age 1144 inserted!(add_cookie( 1145 &mut store, 1146 "cookie1=value1", 1147 "http://example.com/foo/bar", 1148 None, 1149 Some(10), 1150 )); 1151 // persistent cookie, Expires 1152 inserted!(add_cookie( 1153 &mut store, 1154 "cookie2=value2", 1155 "http://example.com/foo/bar", 1156 Some(test_utils::in_days(1)), 1157 None, 1158 )); 1159 inserted!(add_cookie( 1160 &mut store, 1161 "cookie3=value3; Domain=example.com", 1162 "http://foo.example.com/foo/bar", 1163 Some(test_utils::in_days(1)), 1164 None, 1165 )); 1166 inserted!(add_cookie( 1167 &mut store, 1168 "cookie4=value4; Path=/foo/", 1169 "http://foo.example.com/foo/bar", 1170 Some(test_utils::in_days(1)), 1171 None, 1172 )); 1173 inserted!(add_cookie( 1174 &mut store, 1175 "cookie5=value5", 1176 "http://127.0.0.1/foo/bar", 1177 Some(test_utils::in_days(1)), 1178 None, 1179 )); 1180 inserted!(add_cookie( 1181 &mut store, 1182 "cookie6=value6", 1183 "http://[::1]/foo/bar", 1184 Some(test_utils::in_days(1)), 1185 None, 1186 )); 1187 inserted!(add_cookie( 1188 &mut store, 1189 "cookie7=value7; Secure", 1190 "http://example.com/foo/bar", 1191 Some(test_utils::in_days(1)), 1192 None, 1193 )); 1194 inserted!(add_cookie( 1195 &mut store, 1196 "cookie8=value8; HttpOnly", 1197 "http://example.com/foo/bar", 1198 Some(test_utils::in_days(1)), 1199 None, 1200 )); 1201 let mut output = vec![]; 1202 serde_json::to_writer(&mut output, &store).unwrap(); 1203 not_has_str!("cookie0=value0", output); 1204 has_str!("cookie1=value1", output); 1205 has_str!("cookie2=value2", output); 1206 has_str!("cookie3=value3", output); 1207 has_str!("cookie4=value4", output); 1208 has_str!("cookie5=value5", output); 1209 has_str!("cookie6=value6", output); 1210 has_str!("cookie7=value7; Secure", output); 1211 has_str!("cookie8=value8; HttpOnly", output); 1212 let store: CookieStore = serde_json::from_reader(&output[..]).unwrap(); 1213 assert!(store.get("example.com", "/foo", "cookie0").is_none()); 1214 assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1"); 1215 assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value2"); 1216 assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value3"); 1217 assert!( 1218 store 1219 .get("foo.example.com", "/foo/", "cookie4") 1220 .unwrap() 1221 .value() 1222 == "value4" 1223 ); 1224 assert!(store.get("127.0.0.1", "/foo", "cookie5").unwrap().value() == "value5"); 1225 assert!(store.get("[::1]", "/foo", "cookie6").unwrap().value() == "value6"); 1226 assert!(store.get("example.com", "/foo", "cookie7").unwrap().value() == "value7"); 1227 assert!(store.get("example.com", "/foo", "cookie8").unwrap().value() == "value8"); 1228 1229 output.clear(); 1230 let store = make_match_store(); 1231 serde_json::to_writer(&mut output, &store).unwrap(); 1232 let store: CookieStore = serde_json::from_reader(&output[..]).unwrap(); 1233 check_matches!(&store); 1234 } 1235 1236 #[test] clear()1237 fn clear() { 1238 let mut output = vec![]; 1239 let mut store = CookieStore::default(); 1240 inserted!(add_cookie( 1241 &mut store, 1242 "cookie1=value1", 1243 "http://example.com/foo/bar", 1244 Some(test_utils::in_days(1)), 1245 None, 1246 )); 1247 store.save_json(&mut output).unwrap(); 1248 has_str!("cookie1=value1", output); 1249 output.clear(); 1250 store.clear(); 1251 store.save_json(&mut output).unwrap(); 1252 assert_eq!("", from_utf8(&output[..]).unwrap()); 1253 } 1254 1255 #[test] add_and_get()1256 fn add_and_get() { 1257 let mut store = CookieStore::default(); 1258 assert!(store.get("example.com", "/foo", "cookie1").is_none()); 1259 1260 inserted!(add_cookie( 1261 &mut store, 1262 "cookie1=value1", 1263 "http://example.com/foo/bar", 1264 None, 1265 None, 1266 )); 1267 assert!(store.get("example.com", "/foo/bar", "cookie1").is_none()); 1268 assert!(store.get("example.com", "/foo", "cookie2").is_none()); 1269 assert!(store.get("example.org", "/foo", "cookie1").is_none()); 1270 assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value1"); 1271 1272 updated!(add_cookie( 1273 &mut store, 1274 "cookie1=value2", 1275 "http://example.com/foo/bar", 1276 None, 1277 None, 1278 )); 1279 assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2"); 1280 1281 inserted!(add_cookie( 1282 &mut store, 1283 "cookie2=value3", 1284 "http://example.com/foo/bar", 1285 None, 1286 None, 1287 )); 1288 assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2"); 1289 assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3"); 1290 1291 inserted!(add_cookie( 1292 &mut store, 1293 "cookie3=value4; HttpOnly", 1294 "http://example.com/foo/bar", 1295 None, 1296 None, 1297 )); 1298 assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2"); 1299 assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3"); 1300 assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value4"); 1301 1302 non_http_scheme!(add_cookie( 1303 &mut store, 1304 "cookie3=value5", 1305 "ftp://example.com/foo/bar", 1306 None, 1307 None, 1308 )); 1309 assert!(store.get("example.com", "/foo", "cookie1").unwrap().value() == "value2"); 1310 assert!(store.get("example.com", "/foo", "cookie2").unwrap().value() == "value3"); 1311 assert!(store.get("example.com", "/foo", "cookie3").unwrap().value() == "value4"); 1312 } 1313 1314 #[test] matches()1315 fn matches() { 1316 let store = make_match_store(); 1317 check_matches!(&store); 1318 } 1319 1320 #[test] expiry()1321 fn expiry() { 1322 let mut store = make_match_store(); 1323 let request_url = test_utils::url("http://foo.example.com"); 1324 let expired_cookie = Cookie::parse("cookie1=value1; Max-Age=-1", &request_url).unwrap(); 1325 expired_err!(store.insert(expired_cookie, &request_url)); 1326 check_matches!(&store); 1327 match store.get_mut("example.com", "/", "cookie6") { 1328 Some(cookie) => cookie.expire(), 1329 None => unreachable!(), 1330 } 1331 values_are!(store, "http://unknowndomain.org/foo/bar", vec![]); 1332 values_are!(store, "http://example.org/foo/bar", vec!["8"]); 1333 values_are!(store, "http://example.org/bus/bar", vec![]); 1334 values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]); 1335 values_are!(store, "http://bar.example.org/bus/bar", vec![]); 1336 values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]); 1337 values_are!(store, "http://example.com/sec/foo", vec!["3"]); 1338 values_are!(store, "ftp://example.com/sec/foo", vec![]); 1339 values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]); 1340 values_are!(store, "http://example.com/foo/bar/bus", vec!["1", "5"]); 1341 match store.get_any("example.com", "/", "cookie6") { 1342 Some(cookie) => assert!(cookie.is_expired()), 1343 None => unreachable!(), 1344 } 1345 // inserting an expired cookie that matches an existing cookie should expire 1346 // the existing 1347 let request_url = test_utils::url("http://example.com/foo/"); 1348 let expired_cookie = Cookie::parse("cookie5=value5; Max-Age=-1", &request_url).unwrap(); 1349 expired_existing!(store.insert(expired_cookie, &request_url)); 1350 values_are!(store, "http://unknowndomain.org/foo/bar", vec![]); 1351 values_are!(store, "http://example.org/foo/bar", vec!["8"]); 1352 values_are!(store, "http://example.org/bus/bar", vec![]); 1353 values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]); 1354 values_are!(store, "http://bar.example.org/bus/bar", vec![]); 1355 values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]); 1356 values_are!(store, "http://example.com/sec/foo", vec!["3"]); 1357 values_are!(store, "ftp://example.com/sec/foo", vec![]); 1358 values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]); 1359 values_are!(store, "http://example.com/foo/bar/bus", vec!["1"]); 1360 match store.get_any("example.com", "/foo", "cookie5") { 1361 Some(cookie) => assert!(cookie.is_expired()), 1362 None => unreachable!(), 1363 } 1364 // save and loading the store should drop any expired cookies 1365 let mut output = vec![]; 1366 store.save_json(&mut output).unwrap(); 1367 store = CookieStore::load_json(&output[..]).unwrap(); 1368 values_are!(store, "http://unknowndomain.org/foo/bar", vec![]); 1369 values_are!(store, "http://example.org/foo/bar", vec!["8"]); 1370 values_are!(store, "http://example.org/bus/bar", vec![]); 1371 values_are!(store, "http://bar.example.org/foo/bar", vec!["9"]); 1372 values_are!(store, "http://bar.example.org/bus/bar", vec![]); 1373 values_are!(store, "https://example.com/sec/foo", vec!["4", "3", "2"]); 1374 values_are!(store, "http://example.com/sec/foo", vec!["3"]); 1375 values_are!(store, "ftp://example.com/sec/foo", vec![]); 1376 values_are!(store, "http://bar.example.com/foo/bar/bus", vec!["7"]); 1377 values_are!(store, "http://example.com/foo/bar/bus", vec!["1"]); 1378 assert!(store.get_any("example.com", "/", "cookie6").is_none()); 1379 assert!(store.get_any("example.com", "/foo", "cookie5").is_none()); 1380 } 1381 1382 #[test] non_persistent()1383 fn non_persistent() { 1384 let mut store = make_match_store(); 1385 check_matches!(&store); 1386 let request_url = test_utils::url("http://example.com/tmp/"); 1387 let non_persistent = Cookie::parse("cookie10=value10", &request_url).unwrap(); 1388 inserted!(store.insert(non_persistent, &request_url)); 1389 match store.get("example.com", "/tmp", "cookie10") { 1390 None => unreachable!(), 1391 Some(cookie) => assert_eq!("value10", cookie.value()), 1392 } 1393 // save and loading the store should drop any non-persistent cookies 1394 let mut output = vec![]; 1395 store.save_json(&mut output).unwrap(); 1396 store = CookieStore::load_json(&output[..]).unwrap(); 1397 check_matches!(&store); 1398 assert!(store.get("example.com", "/tmp", "cookie10").is_none()); 1399 assert!(store.get_any("example.com", "/tmp", "cookie10").is_none()); 1400 } 1401 1402 macro_rules! dump { 1403 ($e: expr, $i: ident) => {{ 1404 use serde_json; 1405 println!(""); 1406 println!( 1407 "==== {}: {} ====", 1408 $e, 1409 time::OffsetDateTime::now_utc().format(crate::rfc3339_fmt::RFC3339_FORMAT) 1410 ); 1411 for c in $i.iter_any() { 1412 println!( 1413 "{} {}", 1414 if c.is_expired() { 1415 "XXXXX" 1416 } else if c.is_persistent() { 1417 "PPPPP" 1418 } else { 1419 " " 1420 }, 1421 serde_json::to_string(c).unwrap() 1422 ); 1423 println!("----------------"); 1424 } 1425 println!("================"); 1426 }}; 1427 } 1428 matches_are(store: &CookieStore, url: &str, exp: Vec<&str>)1429 fn matches_are(store: &CookieStore, url: &str, exp: Vec<&str>) { 1430 let matches = store 1431 .matches(&test_utils::url(url)) 1432 .iter() 1433 .map(|c| format!("{}={}", c.name(), c.value())) 1434 .collect::<Vec<_>>(); 1435 for e in &exp { 1436 assert!( 1437 matches.iter().any(|m| &m[..] == *e), 1438 "{}: matches missing '{}'\nmatches: {:?}\n exp: {:?}", 1439 url, 1440 e, 1441 matches, 1442 exp 1443 ); 1444 } 1445 assert!( 1446 matches.len() == exp.len(), 1447 "{}: matches={:?} != exp={:?}", 1448 url, 1449 matches, 1450 exp 1451 ); 1452 } 1453 1454 #[test] domain_collisions()1455 fn domain_collisions() { 1456 let mut store = CookieStore::default(); 1457 // - HostOnly, so no collisions here 1458 inserted!(add_cookie( 1459 &mut store, 1460 "cookie1=1a", 1461 "http://foo.bus.example.com/", 1462 None, 1463 None, 1464 )); 1465 inserted!(add_cookie( 1466 &mut store, 1467 "cookie1=1b", 1468 "http://bus.example.com/", 1469 None, 1470 None, 1471 )); 1472 // - Suffix 1473 // both cookie2's domain-match bus.example.com 1474 inserted!(add_cookie( 1475 &mut store, 1476 "cookie2=2a; Domain=bus.example.com", 1477 "http://foo.bus.example.com/", 1478 None, 1479 None, 1480 )); 1481 inserted!(add_cookie( 1482 &mut store, 1483 "cookie2=2b; Domain=example.com", 1484 "http://bus.example.com/", 1485 None, 1486 None, 1487 )); 1488 dump!("domain_collisions", store); 1489 matches_are( 1490 &store, 1491 "http://foo.bus.example.com/", 1492 vec!["cookie1=1a", "cookie2=2a", "cookie2=2b"], 1493 ); 1494 matches_are( 1495 &store, 1496 "http://bus.example.com/", 1497 vec!["cookie1=1b", "cookie2=2a", "cookie2=2b"], 1498 ); 1499 matches_are(&store, "http://example.com/", vec!["cookie2=2b"]); 1500 matches_are(&store, "http://foo.example.com/", vec!["cookie2=2b"]); 1501 } 1502 1503 #[test] path_collisions()1504 fn path_collisions() { 1505 let mut store = CookieStore::default(); 1506 // will be default-path: /foo/bar, and /foo, resp. 1507 // both should match /foo/bar/ 1508 inserted!(add_cookie( 1509 &mut store, 1510 "cookie3=3a", 1511 "http://bus.example.com/foo/bar/", 1512 None, 1513 None, 1514 )); 1515 inserted!(add_cookie( 1516 &mut store, 1517 "cookie3=3b", 1518 "http://bus.example.com/foo/", 1519 None, 1520 None, 1521 )); 1522 // - Path set explicitly 1523 inserted!(add_cookie( 1524 &mut store, 1525 "cookie4=4a; Path=/foo/bar/", 1526 "http://bus.example.com/", 1527 None, 1528 None, 1529 )); 1530 inserted!(add_cookie( 1531 &mut store, 1532 "cookie4=4b; Path=/foo/", 1533 "http://bus.example.com/", 1534 None, 1535 None, 1536 )); 1537 dump!("path_collisions", store); 1538 matches_are( 1539 &store, 1540 "http://bus.example.com/foo/bar/", 1541 vec!["cookie3=3a", "cookie3=3b", "cookie4=4a", "cookie4=4b"], 1542 ); 1543 // Agrees w/ chrome, but not FF... FF also sends cookie4=4a, but this should be 1544 // a path-match 1545 // fail since request-uri /foo/bar is a *prefix* of the cookie path /foo/bar/ 1546 matches_are( 1547 &store, 1548 "http://bus.example.com/foo/bar", 1549 vec!["cookie3=3a", "cookie3=3b", "cookie4=4b"], 1550 ); 1551 matches_are( 1552 &store, 1553 "http://bus.example.com/foo/ba", 1554 vec!["cookie3=3b", "cookie4=4b"], 1555 ); 1556 matches_are( 1557 &store, 1558 "http://bus.example.com/foo/", 1559 vec!["cookie3=3b", "cookie4=4b"], 1560 ); 1561 // Agrees w/ chrome, but not FF... FF also sends cookie4=4b, but this should be 1562 // a path-match 1563 // fail since request-uri /foo is a *prefix* of the cookie path /foo/ 1564 matches_are(&store, "http://bus.example.com/foo", vec!["cookie3=3b"]); 1565 matches_are(&store, "http://bus.example.com/fo", vec![]); 1566 matches_are(&store, "http://bus.example.com/", vec![]); 1567 matches_are(&store, "http://bus.example.com", vec![]); 1568 } 1569 } 1570