Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign CookieStorage and Implement Leave Secure Cookie Alone #14445

Merged
merged 1 commit into from
Dec 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
105 changes: 90 additions & 15 deletions components/net/cookie_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,51 @@
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: Vec<Cookie>
cookies_map: HashMap<String, Vec<Cookie>>,
max_per_host: usize,
}

impl CookieStorage {
pub fn new() -> CookieStorage {
pub fn new(max_cookies: usize) -> CookieStorage {
CookieStorage {
version: 1,
cookies: Vec::new()
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 = self.cookies.iter().position(|c| {
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 = self.cookies.remove(ind);
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.
self.cookies.push(c);
cookies.push(c);
Err(())
}
} else {
Expand All @@ -65,7 +75,20 @@ impl CookieStorage {
}

// Step 12
self.cookies.push(cookie);
let domain = reg_host(&cookie.cookie.domain.as_ref().unwrap_or(&"".to_string()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does unwrap_or("") work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We got the error when we used unwrap_or("") expected String reference found str. So we used &"".to_string()

let mut cookies = self.cookies_map.entry(domain).or_insert(vec![]);

if cookies.len() == self.max_per_host {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move all of the eviction implementation into a evict_one_cookie helper method?

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 {
Expand All @@ -87,14 +110,21 @@ impl CookieStorage {
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));
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 mut url_cookies: Vec<&mut Cookie> = self.cookies.iter_mut().filter(filterer).collect();
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 {
Expand All @@ -104,23 +134,68 @@ impl CookieStorage {
// Step 4
(match acc.len() {
0 => acc,
_ => 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)
_ => Some(result),
}
}

pub fn cookies_data_for_url<'a>(&'a mut self, url: &'a ServoUrl,
source: CookieSource) -> Box<Iterator<Item=cookie_rs::Cookie> + 'a> {
Box::new(self.cookies.iter_mut().filter(move |c| { c.appropriate_for_url(url, source) }).map(|c| {
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);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: deindent check_cookie_expired and reg_host one level.

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
}
2 changes: 1 addition & 1 deletion components/net/http_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl HttpState {
pub fn new() -> HttpState {
HttpState {
hsts_list: Arc::new(RwLock::new(HstsList::new())),
cookie_jar: Arc::new(RwLock::new(CookieStorage::new())),
cookie_jar: Arc::new(RwLock::new(CookieStorage::new(150))),
auth_cache: Arc::new(RwLock::new(AuthCache::new())),
blocked_content: Arc::new(None),
}
Expand Down
4 changes: 2 additions & 2 deletions components/net/resource_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ fn create_resource_groups(config_dir: Option<&Path>)
-> (ResourceGroup, ResourceGroup) {
let mut hsts_list = HstsList::from_servo_preload();
let mut auth_cache = AuthCache::new();
let mut cookie_jar = CookieStorage::new();
let mut cookie_jar = CookieStorage::new(150);
if let Some(config_dir) = config_dir {
read_json_from_file(&mut auth_cache, config_dir, "auth_cache.json");
read_json_from_file(&mut hsts_list, config_dir, "hsts_list.json");
Expand All @@ -198,7 +198,7 @@ fn create_resource_groups(config_dir: Option<&Path>)
connector: create_http_connector(),
};
let private_resource_group = ResourceGroup {
cookie_jar: Arc::new(RwLock::new(CookieStorage::new())),
cookie_jar: Arc::new(RwLock::new(CookieStorage::new(150))),
auth_cache: Arc::new(RwLock::new(AuthCache::new())),
hsts_list: Arc::new(RwLock::new(HstsList::new())),
connector: create_http_connector(),
Expand Down
103 changes: 101 additions & 2 deletions tests/unit/net/cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use cookie_rs;
use hyper::header::{Header, SetCookie};
use net::cookie::Cookie;
use net::cookie_storage::CookieStorage;
use net_traits::CookieSource;
Expand Down Expand Up @@ -110,8 +111,7 @@ fn delay_to_ensure_different_timestamp() {
}

#[cfg(not(target_os = "windows"))]
fn delay_to_ensure_different_timestamp() {
}
fn delay_to_ensure_different_timestamp() {}

#[test]
fn test_sort_order() {
Expand All @@ -132,3 +132,102 @@ fn test_sort_order() {
assert!(CookieStorage::cookie_comparator(&a_prime, &a) == Ordering::Greater);
assert!(CookieStorage::cookie_comparator(&a, &a) == Ordering::Equal);
}


fn add_retrieve_cookies(set_location: &str,
set_cookies: &[String],
final_location: &str)
-> String {
let mut storage = CookieStorage::new(5);
let url = ServoUrl::parse(set_location).unwrap();
let source = CookieSource::HTTP;

// Add all cookies to the store
for str_cookie in set_cookies {
let bytes = str_cookie.to_string().into_bytes();
let header = Header::parse_header(&[bytes]).unwrap();
let SetCookie(cookies) = header;
for bare_cookie in cookies {
let cookie = Cookie::new_wrapped(bare_cookie, &url, source).unwrap();
storage.push(cookie, source);
}
}

// Get cookies for the test location
let url = ServoUrl::parse(final_location).unwrap();
storage.cookies_for_url(&url, source).unwrap_or("".to_string())
}


#[test]
fn test_cookie_eviction_expired() {
let mut vec = Vec::new();
for i in 1..6 {
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2000 21:06:29 GMT",
i);
vec.push(st);
}
vec.push("foo=bar; Secure; expires=Sun, 18-Apr-2027 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001",
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
assert_eq!(&r, "foo=bar");
}


#[test]
fn test_cookie_eviction_all_secure_one_nonsecure() {
let mut vec = Vec::new();
for i in 1..5 {
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
i);
vec.push(st);
}
vec.push("foo=bar; expires=Sun, 18-Apr-2026 21:06:29 GMT".to_owned());
vec.push("foo2=bar; Secure; expires=Sun, 18-Apr-2028 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001",
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
assert_eq!(&r, "extra1=bar; extra2=bar; extra3=bar; extra4=bar; foo2=bar");
}


#[test]
fn test_cookie_eviction_all_secure_new_nonsecure() {
let mut vec = Vec::new();
for i in 1..6 {
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
i);
vec.push(st);
}
vec.push("foo=bar; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001",
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
assert_eq!(&r, "extra1=bar; extra2=bar; extra3=bar; extra4=bar; extra5=bar");
}


#[test]
fn test_cookie_eviction_all_nonsecure_new_secure() {
let mut vec = Vec::new();
for i in 1..6 {
let st = format!("extra{}=bar; expires=Sun, 18-Apr-2026 21:06:29 GMT", i);
vec.push(st);
}
vec.push("foo=bar; Secure; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001",
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
assert_eq!(&r, "extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar");
}


#[test]
fn test_cookie_eviction_all_nonsecure_new_nonsecure() {
let mut vec = Vec::new();
for i in 1..6 {
let st = format!("extra{}=bar; expires=Sun, 18-Apr-2026 21:06:29 GMT", i);
vec.push(st);
}
vec.push("foo=bar; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001",
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
assert_eq!(&r, "extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar");
}
2 changes: 1 addition & 1 deletion tests/unit/net/cookie_http_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use servo_url::ServoUrl;


fn run(set_location: &str, set_cookies: &[&str], final_location: &str) -> String {
let mut storage = CookieStorage::new();
let mut storage = CookieStorage::new(150);
let url = ServoUrl::parse(set_location).unwrap();
let source = CookieSource::HTTP;

Expand Down