aboutsummaryrefslogtreecommitdiffstats
path: root/components/net/cookie.rs
diff options
context:
space:
mode:
authorJosh Matthews <josh@joshmatthews.net>2014-12-30 17:09:34 -0500
committerJosh Matthews <josh@joshmatthews.net>2015-02-04 13:35:05 +0000
commit24c8896f88e59a93cca66ba2f4a482b6b35dac41 (patch)
tree6dad7f78a6dc30d6160a46a4874fe4bef2ed289e /components/net/cookie.rs
parentae2b74c783da97068f438097453fb8e63beb02d8 (diff)
downloadservo-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.rs261
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
}
}