diff options
author | Josh Matthews <josh@joshmatthews.net> | 2014-12-30 17:09:34 -0500 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2015-02-04 13:35:05 +0000 |
commit | 24c8896f88e59a93cca66ba2f4a482b6b35dac41 (patch) | |
tree | 6dad7f78a6dc30d6160a46a4874fe4bef2ed289e /components/net/cookie.rs | |
parent | ae2b74c783da97068f438097453fb8e63beb02d8 (diff) | |
download | servo-24c8896f88e59a93cca66ba2f4a482b6b35dac41.tar.gz servo-24c8896f88e59a93cca66ba2f4a482b6b35dac41.zip |
Improve redirect behaviour to clear headers and reevaluate sent cookies. Implement storage-related cookie behaviour such as domain and path matching that cookie-rs doesn't require. Remove stored cookies when an empty value is stored. Document cookie code.
Diffstat (limited to 'components/net/cookie.rs')
-rw-r--r-- | components/net/cookie.rs | 261 |
1 files changed, 97 insertions, 164 deletions
diff --git a/components/net/cookie.rs b/components/net/cookie.rs index 1c6fa5d0f7a..cc696172bc7 100644 --- a/components/net/cookie.rs +++ b/components/net/cookie.rs @@ -2,206 +2,122 @@ * 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/. */ -//use resource_task::{Metadata, Payload, Done, LoadResponse, LoaderTask, start_sending}; -//use time::Tm; +//! Implementation of cookie creation and matching as specified by +//! http://tools.ietf.org/html/rfc6265 + +use pub_domains::PUB_DOMAINS; + +use cookie_rs; +use time::{Tm, now, at, Timespec}; use url::Url; -use std::ascii::AsciiExt; -use time::{strptime, Tm, at, get_time, Timespec, now}; +use std::borrow::ToOwned; use std::i64; -use pub_domains::PUB_DOMAINS; use std::io::net::ip::IpAddr; +use std::time::Duration; +/// A stored cookie that wraps the definition in cookie-rs. This is used to implement +/// various behaviours defined in the spec that rely on an associated request URL, +/// which cookie-rs and hyper's header parsing do not support. #[derive(Clone, Show)] pub struct Cookie { - pub name: String, - pub value: String, - pub domain: String, - pub expires: Option<Tm>, - pub path: String, - pub secure: bool, - pub http_only: bool, + pub cookie: cookie_rs::Cookie, pub host_only: bool, pub persistent: bool, pub created_at: Tm, pub last_access: Tm, - pub scheme: String + pub scheme: String, + pub expiry_time: Tm, } impl Cookie { - pub fn new(header_value: String, url: &Url) -> Option<Cookie> { - let mut secure = false; - let mut http_only = false; - let mut name = None; - let mut value = None; - let mut domain = "".to_string(); - let mut path = "".to_string(); - let mut expires = None; - let mut max_age = None; - - let mut set_cookie_iter = header_value.as_slice().split(';'); - match set_cookie_iter.next() { - Some(name_value_pair) => { - if !name_value_pair.contains_char('=') { - return None; - } - let mut data = name_value_pair.split('=').map(|x| x.trim()); - name = data.next(); - value = data.next(); - } - None => { return None } - } - - if name.is_some() && name.unwrap() == "" { - return None - } - - for spl in set_cookie_iter { - let cookie_av = spl.trim(); - if cookie_av.contains_char('=') { - match cookie_av.split('=').map(|x| x.trim()).collect::<Vec<&str>>().as_slice() { - [attr, val] if attr.eq_ignore_ascii_case("domain") => { - if val == "" { - continue; - } - let cookie_domain; - if val.char_at(0) == '.' { - cookie_domain = val.slice_from(1); - } else { - cookie_domain = val; - } - domain = cookie_domain.to_ascii_lowercase(); - } - [attr, val] if attr.eq_ignore_ascii_case("path") => { - if val == "" || val.char_at(0) != '/' { - match url.path() { - Some(x) => { - let mut url_path = "".to_string(); - for v in x.iter() { - url_path.push_str(v.as_slice()) - } - path = Cookie::default_path(url_path.as_slice()) - } - _ => { - return None - } - } - } else { - path = val.to_string(); - } - - } - [attr, val] if attr.eq_ignore_ascii_case("expires") => { - // we try strptime with three date formats according to - // http://tools.ietf.org/html/rfc2616#section-3.3.1 - match strptime(val, "%a, %d %b %Y %H:%M:%S %Z") { - Ok(x) => expires = Some(x), - Err(_) => { - match strptime(val, "%A, %d-%b-%y %H:%M:%S %Z") { - Ok(x) => expires = Some(x), - Err(_) => { - match strptime(val, "%a %b %d %H:%M:%S %Y") { - Ok(x) => expires = Some(x), - Err(_) => continue - } - } - } - } - } - } - [attr, val] if attr.eq_ignore_ascii_case("max-age") => { - match val.parse() { - Some(x) if x > 0 => { - let mut expires = get_time(); - expires.sec += x; - max_age = Some(at(expires)); - } - Some(_) => { - max_age = Some(at(Timespec::new(0, 0))) - } - None => continue - } - } - x => { println!("Undefined cookie attr value: {:?}", x); } - } - } else if cookie_av.eq_ignore_ascii_case("secure") { - secure = true; - } else if cookie_av.eq_ignore_ascii_case("httponly") { - http_only = true; - } else { - println!("Undefined cookie attr value: {}", cookie_av) - } - } - - let url_host = match url.host() { - Some(x) => x.serialize(), - None => "".to_string() - }; - let mut cookie = Cookie { - name: name.unwrap().to_string(), - value: value.unwrap().to_string(), - created_at: now(), - last_access: now(), - domain: url_host.clone(), - expires: None, - path: path, - secure: secure, - http_only: http_only, - host_only: true, - persistent: false, - scheme: url.scheme.clone() + /// http://tools.ietf.org/html/rfc6265#section-5.3 + pub fn new_wrapped(mut cookie: cookie_rs::Cookie, request: &Url) -> Option<Cookie> { + // Step 3 + let (persistent, expiry_time) = match (&cookie.max_age, &cookie.expires) { + (&Some(max_age), _) => (true, at(now().to_timespec() + Duration::seconds(max_age as i64))), + (_, &Some(expires)) => (true, expires), + _ => (false, at(Timespec::new(i64::MAX, 0))) }; - if max_age.is_some() { - cookie.persistent = true; - cookie.expires = max_age; - } else if expires.is_some() { - cookie.persistent = true; - cookie.expires = expires; - } else { - cookie.expires = Some(at(Timespec::new(i64::MAX, 0))) - } + let url_host = request.host().map(|host| host.serialize()).unwrap_or("".to_owned()); + // Step 4 + let mut domain = cookie.domain.clone().unwrap_or("".to_owned()); + + // Step 5 match PUB_DOMAINS.iter().find(|&x| domain == *x) { Some(val) if *val == url_host => domain = "".to_string(), Some(_) => return None, None => {} } - if !domain.is_empty() { + + // Step 6 + let host_only = if !domain.is_empty() { if !Cookie::domain_match(url_host.as_slice(), domain.as_slice()) { return None; } else { - cookie.host_only = false; - cookie.domain = domain; + cookie.domain = Some(domain); + false + } + } else { + cookie.domain = Some(url_host); + true + }; + + // Step 7 + let mut path = cookie.path.unwrap_or("".to_owned()); + if path.is_empty() || path.char_at(0) != '/' { + let mut url_path: String = "".to_owned(); + if let Some(paths) = request.path() { + for path in paths.iter() { + url_path.extend(path.chars()); + } } + path = Cookie::default_path(url_path.as_slice()); } - if cookie.http_only && !url.scheme.as_slice().starts_with("http") { + cookie.path = Some(path); + + + // Step 10 + if cookie.httponly && !request.scheme.as_slice().starts_with("http") { return None; } - Some(cookie) + Some(Cookie { + cookie: cookie, + host_only: host_only, + persistent: persistent, + created_at: now(), + last_access: now(), + scheme: request.scheme.clone(), + expiry_time: expiry_time, + }) } pub fn touch(&mut self) { self.last_access = now(); } + // http://tools.ietf.org/html/rfc6265#section-5.1.4 fn default_path(request_path: &str) -> String { if request_path == "" || request_path.char_at(0) != '/' || request_path == "/" { - return "/".to_string(); + return "/".to_owned(); } if request_path.ends_with("/") { return request_path.slice_to(request_path.len()-1).to_string(); } - return request_path.clone().to_string(); + return request_path.to_owned(); } + // http://tools.ietf.org/html/rfc6265#section-5.1.4 pub fn path_match(request_path: &str, cookie_path: &str) -> bool { request_path == cookie_path || ( request_path.starts_with(cookie_path) && - ( request_path.ends_with("/") || request_path.char_at(cookie_path.len()) == '/' ) + ( request_path.ends_with("/") || request_path.char_at(cookie_path.len() - 1) == '/' ) ) } + // http://tools.ietf.org/html/rfc6265#section-5.1.3 pub fn domain_match(string: &str, domain_string: &str) -> bool { if string == domain_string { return true; @@ -214,22 +130,35 @@ impl Cookie { false } + // http://tools.ietf.org/html/rfc6265#section-5.4 step 1 pub fn appropriate_for_url(&self, url: Url) -> bool { - let domain = url.host().unwrap().serialize(); - let mut result = if self.host_only { - self.domain == domain + let domain = url.host().map(|host| host.serialize()); + if self.host_only { + if self.cookie.domain != domain { + return false; + } } else { - Cookie::domain_match(domain.as_slice(), self.domain.as_slice()) - }; - result = result && Cookie::path_match(url.serialize_path().unwrap().as_slice(), self.path.as_slice()); + if let (Some(ref domain), &Some(ref cookie_domain)) = (domain, &self.cookie.domain) { + if !Cookie::domain_match(domain.as_slice(), cookie_domain.as_slice()) { + return false; + } + } + } - if self.secure { - result = result && url.scheme == "https".to_string() + if let (Some(ref path), &Some(ref cookie_path)) = (url.serialize_path(), &self.cookie.path) { + if !Cookie::path_match(path.as_slice(), cookie_path.as_slice()) { + return false; + } } - if self.http_only { - result = result && url.scheme.as_slice().starts_with("http") + + if self.cookie.secure && url.scheme != "https".to_string() { + return false; } - result + if self.cookie.httponly && !url.scheme.as_slice().starts_with("http") { + return false; + } + + return true; } } @@ -300,9 +229,13 @@ impl CookieManager { } pub fn add(&mut self, cookie: &Cookie) -> bool { - match self.cookies.iter().find(|x| x.domain == cookie.domain && x.name == cookie.name && x.path == cookie.path) { + match self.cookies.iter().find(|x| { + x.cookie.domain == cookie.cookie.domain && + x.cookie.name == cookie.cookie.name && + x.cookie.path == cookie.cookie.path + }) { Some(c) => { - if c.http_only && !cookie.scheme.as_slice().starts_with("http") { + if c.cookie.httponly && !cookie.scheme.as_slice().starts_with("http") { return false } } |