servo/components/net/cookie_storage.rs
Raghav c1518adba8 Redesign CookieStorage and Implement Leave Secure Cookie Alone
CookieStorage has been refactored to use HashMap with base domain as the
key. Values of hashmap is vector of cookies.
CookieStorage now has max_per_host which restricts maximum cookies that
can be added per base domain.
Cookie eviction doesnot take place if max_per_host is not reached.
Cookie eviction logic implemented here does following steps
1) Evict all expired cookies
2) Remove oldest accessed non-secure cookie If any
3) When no non-secure cookie exist, remove oldest accessed secure cookie
if new cookie being added is secure. Else ignore new cookie
2016-12-04 16:29:38 -05:00

201 lines
6.8 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Implementation of cookie storage as specified in
//! http://tools.ietf.org/html/rfc6265
use cookie::Cookie;
use cookie_rs;
use net_traits::CookieSource;
use net_traits::pub_domains::reg_suffix;
use servo_url::ServoUrl;
use std::cmp::Ordering;
use std::collections::HashMap;
use time::Tm;
extern crate time;
#[derive(Clone, Debug, RustcDecodable, RustcEncodable)]
pub struct CookieStorage {
version: u32,
cookies_map: HashMap<String, Vec<Cookie>>,
max_per_host: usize,
}
impl CookieStorage {
pub fn new(max_cookies: usize) -> CookieStorage {
CookieStorage {
version: 1,
cookies_map: HashMap::new(),
max_per_host: max_cookies,
}
}
// http://tools.ietf.org/html/rfc6265#section-5.3
pub fn remove(&mut self, cookie: &Cookie, source: CookieSource) -> Result<Option<Cookie>, ()> {
let domain = reg_host(cookie.cookie.domain.as_ref().unwrap_or(&"".to_string()));
let cookies = self.cookies_map.entry(domain).or_insert(vec![]);
// Step 1
let position = cookies.iter().position(|c| {
c.cookie.domain == cookie.cookie.domain &&
c.cookie.path == cookie.cookie.path &&
c.cookie.name == cookie.cookie.name
});
if let Some(ind) = position {
let c = cookies.remove(ind);
// http://tools.ietf.org/html/rfc6265#section-5.3 step 11.2
if !c.cookie.httponly || source == CookieSource::HTTP {
Ok(Some(c))
} else {
// Undo the removal.
cookies.push(c);
Err(())
}
} else {
Ok(None)
}
}
// http://tools.ietf.org/html/rfc6265#section-5.3
pub fn push(&mut self, mut cookie: Cookie, source: CookieSource) {
let old_cookie = self.remove(&cookie, source);
if old_cookie.is_err() {
// This new cookie is not allowed to overwrite an existing one.
return;
}
// Step 11
if let Some(old_cookie) = old_cookie.unwrap() {
// Step 11.3
cookie.creation_time = old_cookie.creation_time;
}
// Step 12
let domain = reg_host(&cookie.cookie.domain.as_ref().unwrap_or(&"".to_string()));
let mut cookies = self.cookies_map.entry(domain).or_insert(vec![]);
if cookies.len() == self.max_per_host {
let old_len = cookies.len();
cookies.retain(|c| !is_cookie_expired(&c));
let new_len = cookies.len();
// https://datatracker.ietf.org/doc/draft-ietf-httpbis-cookie-alone
if new_len == old_len && !evict_one_cookie(cookie.cookie.secure, cookies) {
return;
}
}
cookies.push(cookie);
}
pub fn cookie_comparator(a: &Cookie, b: &Cookie) -> Ordering {
let a_path_len = a.cookie.path.as_ref().map_or(0, |p| p.len());
let b_path_len = b.cookie.path.as_ref().map_or(0, |p| p.len());
match a_path_len.cmp(&b_path_len) {
Ordering::Equal => {
let a_creation_time = a.creation_time.to_timespec();
let b_creation_time = b.creation_time.to_timespec();
a_creation_time.cmp(&b_creation_time)
}
// Ensure that longer paths are sorted earlier than shorter paths
Ordering::Greater => Ordering::Less,
Ordering::Less => Ordering::Greater,
}
}
// http://tools.ietf.org/html/rfc6265#section-5.4
pub fn cookies_for_url(&mut self, url: &ServoUrl, source: CookieSource) -> Option<String> {
let filterer = |c: &&mut Cookie| -> bool {
info!(" === SENT COOKIE : {} {} {:?} {:?}",
c.cookie.name,
c.cookie.value,
c.cookie.domain,
c.cookie.path);
info!(" === SENT COOKIE RESULT {}",
c.appropriate_for_url(url, source));
// Step 1
c.appropriate_for_url(url, source)
};
// Step 2
let domain = reg_host(url.host_str().unwrap_or(""));
let cookies = self.cookies_map.entry(domain).or_insert(vec![]);
let mut url_cookies: Vec<&mut Cookie> = cookies.iter_mut().filter(filterer).collect();
url_cookies.sort_by(|a, b| CookieStorage::cookie_comparator(*a, *b));
let reducer = |acc: String, c: &mut &mut Cookie| -> String {
// Step 3
c.touch();
// Step 4
(match acc.len() {
0 => acc,
_ => acc + "; ",
}) + &c.cookie.name + "=" + &c.cookie.value
};
let result = url_cookies.iter_mut().fold("".to_owned(), reducer);
info!(" === COOKIES SENT: {}", result);
match result.len() {
0 => None,
_ => Some(result),
}
}
pub fn cookies_data_for_url<'a>(&'a mut self,
url: &'a ServoUrl,
source: CookieSource)
-> Box<Iterator<Item = cookie_rs::Cookie> + 'a> {
let domain = reg_host(url.host_str().unwrap_or(""));
let cookies = self.cookies_map.entry(domain).or_insert(vec![]);
Box::new(cookies.iter_mut().filter(move |c| c.appropriate_for_url(url, source)).map(|c| {
c.touch();
c.cookie.clone()
}))
}
}
fn reg_host<'a>(url: &'a str) -> String {
reg_suffix(url).to_string()
}
fn is_cookie_expired(cookie: &Cookie) -> bool {
match cookie.expiry_time {
Some(t) => t.to_timespec() <= time::get_time(),
None => false,
}
}
fn evict_one_cookie(is_secure_cookie: bool, cookies: &mut Vec<Cookie>) -> bool {
// Remove non-secure cookie with oldest access time
let oldest_accessed: Option<(usize, Tm)> = get_oldest_accessed(false, cookies);
if let Some((index, _)) = oldest_accessed {
cookies.remove(index);
} else {
// All secure cookies were found
if !is_secure_cookie {
return false;
}
let oldest_accessed: Option<(usize, Tm)> = get_oldest_accessed(true, cookies);
if let Some((index, _)) = oldest_accessed {
cookies.remove(index);
}
}
return true;
}
fn get_oldest_accessed(is_secure_cookie: bool, cookies: &mut Vec<Cookie>) -> Option<(usize, Tm)> {
let mut oldest_accessed: Option<(usize, Tm)> = None;
for (i, c) in cookies.iter().enumerate() {
if (c.cookie.secure == is_secure_cookie) &&
oldest_accessed.as_ref().map_or(true, |a| c.last_access < a.1) {
oldest_accessed = Some((i, c.last_access));
}
}
oldest_accessed
}