servo: Merge #4519 - Cookie support (from jdm:cookies); r=Ms2ger

As specified in http://tools.ietf.org/html/rfc6265. Requires https://github.com/servo/cookie-rs/pull/1. Tested against http://www.joshmatthews.net/cookie.php, http://www.html-kit.com/tools/cookietester/, https://github.com/login, and https://mobile.twitter.com/session/new .

Source-Repo: https://github.com/servo/servo
Source-Revision: 7e3f504d94ffb77ec6148166d2ab73978e1c71c8
This commit is contained in:
Josh Matthews 2015-02-04 11:57:54 -07:00
parent 18cd53c8d1
commit 21a974a25d
23 changed files with 6627 additions and 140 deletions

View File

@ -9,3 +9,6 @@ path = "lib.rs"
[dependencies.msg]
path = "../msg"
[dependencies.util]
path = "../util"

View File

@ -22,7 +22,7 @@ git = "https://github.com/servo/rust-geom"
[dependencies.hyper]
git = "https://github.com/servo/hyper"
branch = "servo"
branch = "old_servo_new_cookies"
[dependencies.layers]
git = "https://github.com/servo/rust-layers"
@ -34,4 +34,4 @@ git = "https://github.com/servo/rust-core-foundation"
git = "https://github.com/servo/rust-io-surface"
[dependencies]
url = "0.2.16"
url = "0.2.16"

View File

@ -15,7 +15,11 @@ git = "https://github.com/servo/rust-geom"
[dependencies.hyper]
git = "https://github.com/servo/hyper"
branch = "servo"
branch = "old_servo_new_cookies"
[dependencies.cookie]
git = "https://github.com/servo/cookie-rs"
branch = "lenientparse_backport"
[dependencies.png]
git = "https://github.com/servo/rust-png"
@ -26,4 +30,4 @@ git = "https://github.com/servo/rust-stb-image"
[dependencies]
url = "0.2.16"
time = "0.1.12"
openssl="0.2.15"
openssl="0.2.15"

View File

@ -26,7 +26,7 @@ pub fn factory(mut load_data: LoadData, start_chan: Sender<TargetedLoadResponse>
content_type: Some(("text".to_string(), "html".to_string())),
charset: Some("utf-8".to_string()),
headers: None,
status: Some(RawStatus(200, "OK".to_owned()))
status: Some(RawStatus(200, "OK".to_owned())),
});
chan.send(Done(Ok(()))).unwrap();
return

View File

@ -0,0 +1,233 @@
/* 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 creation and matching as specified by
//! http://tools.ietf.org/html/rfc6265
use cookie_storage::CookieSource;
use pub_domains::PUB_DOMAINS;
use cookie_rs;
use time::{Tm, now, at, Timespec};
use url::Url;
use std::borrow::ToOwned;
use std::i64;
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 cookie: cookie_rs::Cookie,
pub host_only: bool,
pub persistent: bool,
pub creation_time: Tm,
pub last_access: Tm,
pub expiry_time: Tm,
}
impl Cookie {
/// http://tools.ietf.org/html/rfc6265#section-5.3
pub fn new_wrapped(mut cookie: cookie_rs::Cookie, request: &Url, source: CookieSource)
-> 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)))
};
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 => {}
}
// Step 6
let host_only = if !domain.is_empty() {
if !Cookie::domain_match(url_host.as_slice(), domain.as_slice()) {
return None;
} else {
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 url_path = request.serialize_path();
let url_path = url_path.as_ref().map(|path| path.as_slice());
path = Cookie::default_path(url_path.unwrap_or(""));
}
cookie.path = Some(path);
// Step 10
if cookie.httponly && source != CookieSource::HTTP {
return None;
}
Some(Cookie {
cookie: cookie,
host_only: host_only,
persistent: persistent,
creation_time: now(),
last_access: now(),
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.chars().filter(|&c| c == '/').count() == 1 {
"/".to_owned()
} else if request_path.ends_with("/") {
request_path.slice_to(request_path.len()-1).to_owned()
} else {
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() - 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;
}
if string.ends_with(domain_string)
&& string.char_at(string.len()-domain_string.len()-1) == '.'
&& string.parse::<IpAddr>().is_none() {
return true;
}
false
}
// http://tools.ietf.org/html/rfc6265#section-5.4 step 1
pub fn appropriate_for_url(&self, url: &Url, source: CookieSource) -> bool {
let domain = url.host().map(|host| host.serialize());
if self.host_only {
if self.cookie.domain != domain {
return false;
}
} else {
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 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.cookie.secure && url.scheme != "https".to_string() {
return false;
}
if self.cookie.httponly && source == CookieSource::NonHTTP {
return false;
}
return true;
}
}
#[test]
fn test_domain_match() {
assert!(Cookie::domain_match("foo.com", "foo.com"));
assert!(Cookie::domain_match("bar.foo.com", "foo.com"));
assert!(Cookie::domain_match("baz.bar.foo.com", "foo.com"));
assert!(!Cookie::domain_match("bar.foo.com", "bar.com"));
assert!(!Cookie::domain_match("bar.com", "baz.bar.com"));
assert!(!Cookie::domain_match("foo.com", "bar.com"));
assert!(!Cookie::domain_match("bar.com", "bbar.com"));
assert!(Cookie::domain_match("235.132.2.3", "235.132.2.3"));
assert!(!Cookie::domain_match("235.132.2.3", "1.1.1.1"));
assert!(!Cookie::domain_match("235.132.2.3", ".2.3"));
}
#[test]
fn test_default_path() {
assert!(Cookie::default_path("/foo/bar/baz/").as_slice() == "/foo/bar/baz");
assert!(Cookie::default_path("/foo/").as_slice() == "/foo");
assert!(Cookie::default_path("/foo").as_slice() == "/");
assert!(Cookie::default_path("/").as_slice() == "/");
assert!(Cookie::default_path("").as_slice() == "/");
assert!(Cookie::default_path("foo").as_slice() == "/");
}
#[test]
fn fn_cookie_constructor() {
use cookie_storage::CookieSource;
let url = &Url::parse("http://example.com/foo").unwrap();
let gov_url = &Url::parse("http://gov.ac/foo").unwrap();
// cookie name/value test
assert!(cookie_rs::Cookie::parse(" baz ").is_err());
assert!(cookie_rs::Cookie::parse(" = bar ").is_err());
assert!(cookie_rs::Cookie::parse(" baz = ").is_ok());
// cookie domains test
let cookie = cookie_rs::Cookie::parse(" baz = bar; Domain = ").unwrap();
assert!(Cookie::new_wrapped(cookie.clone(), url, CookieSource::HTTP).is_some());
let cookie = Cookie::new_wrapped(cookie, url, CookieSource::HTTP).unwrap();
assert!(cookie.cookie.domain.as_ref().unwrap().as_slice() == "example.com");
// cookie public domains test
let cookie = cookie_rs::Cookie::parse(" baz = bar; Domain = gov.ac").unwrap();
assert!(Cookie::new_wrapped(cookie.clone(), url, CookieSource::HTTP).is_none());
assert!(Cookie::new_wrapped(cookie, gov_url, CookieSource::HTTP).is_some());
// cookie domain matching test
let cookie = cookie_rs::Cookie::parse(" baz = bar ; Secure; Domain = bazample.com").unwrap();
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none());
let cookie = cookie_rs::Cookie::parse(" baz = bar ; Secure; Path = /foo/bar/").unwrap();
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_some());
let cookie = cookie_rs::Cookie::parse(" baz = bar ; HttpOnly").unwrap();
assert!(Cookie::new_wrapped(cookie, url, CookieSource::NonHTTP).is_none());
let cookie = cookie_rs::Cookie::parse(" baz = bar ; Secure; Path = /foo/bar/").unwrap();
let cookie = Cookie::new_wrapped(cookie, url, CookieSource::HTTP).unwrap();
assert!(cookie.cookie.value.as_slice() == "bar");
assert!(cookie.cookie.name.as_slice() == "baz");
assert!(cookie.cookie.secure);
assert!(cookie.cookie.path.as_ref().unwrap().as_slice() == "/foo/bar/");
assert!(cookie.cookie.domain.as_ref().unwrap().as_slice() == "example.com");
assert!(cookie.host_only);
let u = &Url::parse("http://example.com/foobar").unwrap();
let cookie = cookie_rs::Cookie::parse("foobar=value;path=/").unwrap();
assert!(Cookie::new_wrapped(cookie, u, CookieSource::HTTP).is_some());
}

View File

@ -0,0 +1,143 @@
/* 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 url::Url;
use cookie::Cookie;
use std::cmp::Ordering;
/// The creator of a given cookie
#[derive(PartialEq, Copy)]
pub enum CookieSource {
/// An HTTP API
HTTP,
/// A non-HTTP API
NonHTTP,
}
pub struct CookieStorage {
cookies: Vec<Cookie>
}
impl CookieStorage {
pub fn new() -> CookieStorage {
CookieStorage {
cookies: Vec::new()
}
}
// http://tools.ietf.org/html/rfc6265#section-5.3
pub fn remove(&mut self, cookie: &Cookie, source: CookieSource) -> Result<Option<Cookie>, ()> {
// Step 1
let position = self.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);
// 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);
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;
}
if cookie.cookie.value.is_empty() {
return;
}
// Step 11
if let Some(old_cookie) = old_cookie.unwrap() {
// Step 11.3
cookie.creation_time = old_cookie.creation_time;
}
// Step 12
self.cookies.push(cookie);
}
fn cookie_comparator(a: &Cookie, b: &Cookie) -> Ordering {
let a_path_len = a.cookie.path.as_ref().map(|p| p.len()).unwrap_or(0);
let b_path_len = b.cookie.path.as_ref().map(|p| p.len()).unwrap_or(0);
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: &Url, 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 mut url_cookies: Vec<&mut Cookie> = self.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.as_slice() + "=" + c.cookie.value.as_slice()
};
let result = url_cookies.iter_mut().fold("".to_string(), reducer);
info!(" === COOKIES SENT: {}", result);
match result.len() {
0 => None,
_ => Some(result)
}
}
}
#[test]
fn test_sort_order() {
use cookie_rs;
let url = &Url::parse("http://example.com/foo").unwrap();
let a_wrapped = cookie_rs::Cookie::parse("baz=bar; Path=/foo/bar/").unwrap();
let a = Cookie::new_wrapped(a_wrapped.clone(), url, CookieSource::HTTP).unwrap();
let a_prime = Cookie::new_wrapped(a_wrapped, url, CookieSource::HTTP).unwrap();
let b = cookie_rs::Cookie::parse("baz=bar;Path=/foo/bar/baz/").unwrap();
let b = Cookie::new_wrapped(b, url, CookieSource::HTTP).unwrap();
assert!(b.cookie.path.as_ref().unwrap().len() > a.cookie.path.as_ref().unwrap().len());
assert!(CookieStorage::cookie_comparator(&a, &b) == Ordering::Greater);
assert!(CookieStorage::cookie_comparator(&b, &a) == Ordering::Less);
assert!(CookieStorage::cookie_comparator(&a, &a_prime) == Ordering::Less);
assert!(CookieStorage::cookie_comparator(&a_prime, &a) == Ordering::Greater);
assert!(CookieStorage::cookie_comparator(&a, &a) == Ordering::Equal);
}

View File

@ -2,7 +2,9 @@
* 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 cookie_storage::CookieSource;
use resource_task::{Metadata, TargetedLoadResponse, LoadData, start_sending_opt, ResponseSenders};
use resource_task::ControlMsg;
use resource_task::ProgressMsg::{Payload, Done};
use log;
@ -13,19 +15,23 @@ use hyper::header::common::{ContentLength, ContentType, Host, Location};
use hyper::HttpError;
use hyper::method::Method;
use hyper::net::HttpConnector;
use hyper::status::StatusClass;
use hyper::status::{StatusCode, StatusClass};
use std::error::Error;
use openssl::ssl::{SslContext, SslVerifyMode};
use std::io::{IoError, IoErrorKind, Reader};
use std::sync::mpsc::Sender;
use std::sync::mpsc::{Sender, channel};
use std::thunk::Invoke;
use util::task::spawn_named;
use util::resource_files::resources_dir_path;
use url::{Url, UrlParser};
use std::borrow::ToOwned;
pub fn factory(load_data: LoadData, start_chan: Sender<TargetedLoadResponse>) {
spawn_named("http_loader".to_owned(), move || load(load_data, start_chan))
pub fn factory(cookies_chan: Sender<ControlMsg>)
-> Box<Invoke<(LoadData, Sender<TargetedLoadResponse>)> + Send> {
box move |:(load_data, start_chan)| {
spawn_named("http_loader".to_owned(), move || load(load_data, start_chan, cookies_chan))
}
}
fn send_error(url: Url, err: String, senders: ResponseSenders) {
@ -38,7 +44,7 @@ fn send_error(url: Url, err: String, senders: ResponseSenders) {
};
}
fn load(load_data: LoadData, start_chan: Sender<TargetedLoadResponse>) {
fn load(mut load_data: LoadData, start_chan: Sender<TargetedLoadResponse>, cookies_chan: Sender<ControlMsg>) {
// FIXME: At the time of writing this FIXME, servo didn't have any central
// location for configuration. If you're reading this and such a
// repository DOES exist, please update this constant to use it.
@ -61,13 +67,6 @@ fn load(load_data: LoadData, start_chan: Sender<TargetedLoadResponse>) {
return;
}
if redirected_to.contains(&url) {
send_error(url, "redirect loop".to_string(), senders);
return;
}
redirected_to.insert(url.clone());
match url.scheme.as_slice() {
"http" | "https" => {}
_ => {
@ -111,15 +110,45 @@ reason: \"certificate verify failed\" }]";
// Preserve the `host` header set automatically by Request.
let host = req.headers().get::<Host>().unwrap().clone();
*req.headers_mut() = load_data.headers.clone();
// Avoid automatically preserving request headers when redirects occur.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=401564 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=216828 .
// Only preserve ones which have been explicitly marked as such.
if iters == 1 {
let mut combined_headers = load_data.headers.clone();
combined_headers.extend(load_data.preserved_headers.iter());
*req.headers_mut() = combined_headers;
} else {
*req.headers_mut() = load_data.preserved_headers.clone();
}
req.headers_mut().set(host);
let (tx, rx) = channel();
cookies_chan.send(ControlMsg::GetCookiesForUrl(url.clone(), tx, CookieSource::HTTP));
if let Some(cookie_list) = rx.recv().unwrap() {
let mut v = Vec::new();
v.push(cookie_list.into_bytes());
req.headers_mut().set_raw("Cookie".to_owned(), v);
}
// FIXME(seanmonstar): use AcceptEncoding from Hyper once available
//if !req.headers.has::<AcceptEncoding>() {
// We currently don't support HTTP Compression (FIXME #2587)
req.headers_mut().set_raw("Accept-Encoding".to_owned(), vec![b"identity".to_vec()]);
//}
if log_enabled!(log::INFO) {
info!("{}", load_data.method);
for header in req.headers().iter() {
info!(" - {}", header);
}
info!("{:?}", load_data.data);
}
// Avoid automatically sending request body if a redirect has occurred.
let writer = match load_data.data {
Some(ref data) => {
Some(ref data) if iters == 1 => {
req.headers_mut().set(ContentLength(data.len() as u64));
let mut writer = match req.start() {
Ok(w) => w,
@ -137,7 +166,7 @@ reason: \"certificate verify failed\" }]";
};
writer
},
None => {
_ => {
match load_data.method {
Method::Get | Method::Head => (),
_ => req.headers_mut().set(ContentLength(0))
@ -167,6 +196,16 @@ reason: \"certificate verify failed\" }]";
}
}
if let Some(cookies) = response.headers.get_raw("set-cookie") {
for cookie in cookies.iter() {
if let Ok(cookies) = String::from_utf8(cookie.clone()) {
cookies_chan.send(ControlMsg::SetCookiesForUrl(url.clone(),
cookies,
CookieSource::HTTP));
}
}
}
if response.status.class() == StatusClass::Redirection {
match response.headers.get::<Location>() {
Some(&Location(ref new_url)) => {
@ -193,6 +232,21 @@ reason: \"certificate verify failed\" }]";
};
info!("redirecting to {}", new_url);
url = new_url;
// According to https://tools.ietf.org/html/rfc7231#section-6.4.2,
// historically UAs have rewritten POST->GET on 301 and 302 responses.
if load_data.method == Method::Post &&
(response.status == StatusCode::MovedPermanently ||
response.status == StatusCode::Found) {
load_data.method = Method::Get;
}
if redirected_to.contains(&url) {
send_error(url, "redirect loop".to_string(), senders);
return;
}
redirected_to.insert(url.clone());
continue;
}
None => ()

View File

@ -568,7 +568,8 @@ mod tests {
Url::parse("file:///fake").unwrap()));
on_load.invoke(chan);
}
resource_task::ControlMsg::Exit => break
resource_task::ControlMsg::Exit => break,
_ => {}
}
}
})
@ -730,6 +731,7 @@ mod tests {
resource_task_exited_chan.send(());
break
}
_ => {}
}
}
});
@ -762,7 +764,6 @@ mod tests {
let (image_bin_sent_chan, image_bin_sent) = channel();
let (resource_task_exited_chan, resource_task_exited) = channel();
let mock_resource_task = spawn_listener(move |port: Receiver<resource_task::ControlMsg>| {
loop {
match port.recv().unwrap() {
@ -782,6 +783,7 @@ mod tests {
resource_task_exited_chan.send(());
break
}
_ => {}
}
}
});

View File

@ -11,6 +11,7 @@
#![allow(missing_copy_implementations)]
#![allow(unstable)]
extern crate "cookie" as cookie_rs;
extern crate collections;
extern crate geom;
extern crate hyper;
@ -38,8 +39,11 @@ pub mod about_loader;
pub mod file_loader;
pub mod http_loader;
pub mod data_loader;
pub mod cookie;
pub mod cookie_storage;
pub mod image_cache_task;
pub mod local_image_cache;
pub mod pub_domains;
pub mod resource_task;
pub mod storage_task;
mod sniffer_task;

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,13 @@ use file_loader;
use http_loader;
use sniffer_task;
use sniffer_task::SnifferTask;
use cookie_storage::{CookieStorage, CookieSource};
use cookie;
use util::task::spawn_named;
use hyper::header::common::UserAgent;
use hyper::header::Headers;
use hyper::header::{Headers, Header, SetCookie};
use hyper::http::RawStatus;
use hyper::method::Method;
use hyper::mime::{Mime, Attr};
@ -22,10 +24,15 @@ use url::Url;
use std::borrow::ToOwned;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thunk::Invoke;
pub enum ControlMsg {
/// Request the data associated with a particular URL
Load(LoadData),
/// Store a set of cookies for a given originating URL
SetCookiesForUrl(Url, String, CookieSource),
/// Retrieve the stored cookies for a given URL
GetCookiesForUrl(Url, Sender<Option<String>>, CookieSource),
Exit
}
@ -33,7 +40,10 @@ pub enum ControlMsg {
pub struct LoadData {
pub url: Url,
pub method: Method,
/// Headers that will apply to the initial request only
pub headers: Headers,
/// Headers that will apply to the initial request and any redirects
pub preserved_headers: Headers,
pub data: Option<Vec<u8>>,
pub cors: Option<ResourceCORSData>,
pub consumer: Sender<LoadResponse>,
@ -45,6 +55,7 @@ impl LoadData {
url: url,
method: Method::Get,
headers: Headers::new(),
preserved_headers: Headers::new(),
data: None,
cors: None,
consumer: consumer,
@ -61,6 +72,7 @@ pub struct ResourceCORSData {
}
/// Metadata about a loaded resource, such as is obtained from HTTP headers.
#[deriving(Clone)]
pub struct Metadata {
/// Final URL after redirects.
pub final_url: Url,
@ -75,7 +87,7 @@ pub struct Metadata {
pub headers: Option<Headers>,
/// HTTP Status
pub status: Option<RawStatus>
pub status: Option<RawStatus>,
}
impl Metadata {
@ -87,7 +99,7 @@ impl Metadata {
charset: None,
headers: None,
// http://fetch.spec.whatwg.org/#concept-response-status-message
status: Some(RawStatus(200, "OK".to_owned()))
status: Some(RawStatus(200, "OK".to_owned())),
}
}
@ -184,8 +196,9 @@ pub type ResourceTask = Sender<ControlMsg>;
pub fn new_resource_task(user_agent: Option<String>) -> ResourceTask {
let (setup_chan, setup_port) = channel();
let sniffer_task = sniffer_task::new_sniffer_task();
let setup_chan_clone = setup_chan.clone();
spawn_named("ResourceManager".to_owned(), move || {
ResourceManager::new(setup_port, user_agent, sniffer_task).start();
ResourceManager::new(setup_port, user_agent, sniffer_task, setup_chan_clone).start();
});
setup_chan
}
@ -194,26 +207,44 @@ struct ResourceManager {
from_client: Receiver<ControlMsg>,
user_agent: Option<String>,
sniffer_task: SnifferTask,
cookie_storage: CookieStorage,
resource_task: Sender<ControlMsg>,
}
impl ResourceManager {
fn new(from_client: Receiver<ControlMsg>, user_agent: Option<String>, sniffer_task: SnifferTask) -> ResourceManager {
fn new(from_client: Receiver<ControlMsg>, user_agent: Option<String>, sniffer_task: SnifferTask,
resource_task: Sender<ControlMsg>) -> ResourceManager {
ResourceManager {
from_client: from_client,
user_agent: user_agent,
sniffer_task: sniffer_task,
cookie_storage: CookieStorage::new(),
resource_task: resource_task,
}
}
}
impl ResourceManager {
fn start(&self) {
fn start(&mut self) {
loop {
match self.from_client.recv().unwrap() {
ControlMsg::Load(load_data) => {
self.load(load_data)
}
ControlMsg::SetCookiesForUrl(request, cookie_list, source) => {
let header = Header::parse_header([cookie_list.into_bytes()].as_slice());
if let Some(SetCookie(cookies)) = header {
for bare_cookie in cookies.into_iter() {
if let Some(cookie) = cookie::Cookie::new_wrapped(bare_cookie, &request, source) {
self.cookie_storage.push(cookie, source);
}
}
}
}
ControlMsg::GetCookiesForUrl(url, consumer, source) => {
consumer.send(self.cookie_storage.cookies_for_url(&url, source));
}
ControlMsg::Exit => {
break
}
@ -221,7 +252,7 @@ impl ResourceManager {
}
}
fn load(&self, load_data: LoadData) {
fn load(&mut self, load_data: LoadData) {
let mut load_data = load_data;
self.user_agent.as_ref().map(|ua| load_data.headers.set(UserAgent(ua.clone())));
let senders = ResponseSenders {
@ -229,19 +260,28 @@ impl ResourceManager {
eventual_consumer: load_data.consumer.clone(),
};
debug!("resource_task: loading url: {}", load_data.url.serialize());
match load_data.url.scheme.as_slice() {
"file" => file_loader::factory(load_data, self.sniffer_task.clone()),
"http" | "https" => http_loader::factory(load_data, self.sniffer_task.clone()),
"data" => data_loader::factory(load_data, self.sniffer_task.clone()),
"about" => about_loader::factory(load_data, self.sniffer_task.clone()),
fn from_factory(factory: fn(LoadData, Sender<TargetedLoadResponse>))
-> Box<Invoke<(LoadData, Sender<TargetedLoadResponse>)> + Send> {
box move |&:(load_data, start_chan)| {
factory(load_data, start_chan)
}
}
let loader = match load_data.url.scheme.as_slice() {
"file" => from_factory(file_loader::factory),
"http" | "https" => http_loader::factory(self.resource_task.clone()),
"data" => from_factory(data_loader::factory),
"about" => from_factory(about_loader::factory),
_ => {
debug!("resource_task: no loader for scheme {}", load_data.url.scheme);
start_sending(senders, Metadata::default(load_data.url))
.send(ProgressMsg::Done(Err("no loader for scheme".to_string()))).unwrap();
return
}
}
};
debug!("resource_task: loading url: {}", load_data.url.serialize());
loader.invoke((load_data, self.sniffer_task.clone()));
}
}

View File

@ -50,7 +50,7 @@ git = "https://github.com/servo/html5ever"
[dependencies.hyper]
git = "https://github.com/servo/hyper"
branch = "servo"
branch = "old_servo_new_cookies"
[dependencies.js]
git = "https://github.com/servo/rust-mozjs"
@ -67,4 +67,4 @@ git = "https://github.com/servo/string-cache"
[dependencies]
encoding = "0.2"
url = "0.2.16"
time = "0.1.12"
time = "0.1.12"

View File

@ -19,7 +19,7 @@ use dom::bindings::codegen::InheritTypes::{HTMLAreaElementDerived, HTMLEmbedElem
use dom::bindings::codegen::InheritTypes::{HTMLFormElementDerived, HTMLImageElementDerived};
use dom::bindings::codegen::InheritTypes::{HTMLScriptElementDerived};
use dom::bindings::error::{ErrorResult, Fallible};
use dom::bindings::error::Error::{NotSupported, InvalidCharacter};
use dom::bindings::error::Error::{NotSupported, InvalidCharacter, Security};
use dom::bindings::error::Error::{HierarchyRequest, NamespaceError};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{MutNullableJS, JS, JSRef, LayoutJS, Temporary, TemporaryPushable};
@ -54,6 +54,8 @@ use dom::range::Range;
use dom::treewalker::TreeWalker;
use dom::uievent::UIEvent;
use dom::window::{Window, WindowHelpers};
use net::resource_task::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl};
use net::cookie_storage::CookieSource::NonHTTP;
use util::namespace;
use util::str::{DOMString, split_html_space_chars};
@ -68,6 +70,7 @@ use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::ascii::AsciiExt;
use std::cell::{Cell, Ref};
use std::default::Default;
use std::sync::mpsc::channel;
use time;
#[derive(PartialEq)]
@ -170,7 +173,7 @@ pub trait DocumentHelpers<'a> {
fn window(self) -> Temporary<Window>;
fn encoding_name(self) -> Ref<'a, DOMString>;
fn is_html_document(self) -> bool;
fn url(self) -> &'a Url;
fn url(self) -> Url;
fn quirks_mode(self) -> QuirksMode;
fn set_quirks_mode(self, mode: QuirksMode);
fn set_last_modified(self, value: DOMString);
@ -206,8 +209,9 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
self.is_html_document
}
fn url(self) -> &'a Url {
&self.extended_deref().url
// http://dom.spec.whatwg.org/#dom-document-url
fn url(self) -> Url {
self.url.clone()
}
fn quirks_mode(self) -> QuirksMode {
@ -1003,7 +1007,38 @@ impl<'a> DocumentMethods for JSRef<'a, Document> {
Temporary::new(self.window)
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-document-cookie
fn GetCookie(self) -> Fallible<DOMString> {
//TODO: return empty string for cookie-averse Document
let url = self.url();
if !is_scheme_host_port_tuple(&url) {
return Err(Security);
}
let window = self.window.root();
let page = window.page();
let (tx, rx) = channel();
let _ = page.resource_task.send(GetCookiesForUrl(url, tx, NonHTTP));
let cookies = rx.recv().unwrap();
Ok(cookies.unwrap_or("".to_owned()))
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-document-cookie
fn SetCookie(self, cookie: DOMString) -> ErrorResult {
//TODO: ignore for cookie-averse Document
let url = self.url();
if !is_scheme_host_port_tuple(&url) {
return Err(Security);
}
let window = self.window.root();
let page = window.page();
let _ = page.resource_task.send(SetCookiesForUrl(url, cookie, NonHTTP));
Ok(())
}
global_event_handlers!();
event_handler!(readystatechange, GetOnreadystatechange, SetOnreadystatechange);
}
fn is_scheme_host_port_tuple(url: &Url) -> bool {
url.host().is_some() && url.port_or_default().is_some()
}

View File

@ -777,7 +777,7 @@ impl<'a> AttributeHandlers for JSRef<'a, Element> {
let base = doc.r().url();
// https://html.spec.whatwg.org/multipage/infrastructure.html#reflect
// XXXManishearth this doesn't handle `javascript:` urls properly
match UrlParser::new().base_url(base).parse(url.as_slice()) {
match UrlParser::new().base_url(&base).parse(url.as_slice()) {
Ok(parsed) => parsed.serialize(),
Err(_) => "".to_owned()
}
@ -1174,7 +1174,7 @@ impl<'a> VirtualMethods for JSRef<'a, Element> {
// Modifying the `style` attribute might change style.
let node: JSRef<Node> = NodeCast::from_ref(*self);
let doc = document_from_node(*self).root();
let base_url = doc.r().url().clone();
let base_url = doc.r().url();
let value = attr.value();
let style = Some(parse_style_attribute(value.as_slice(), &base_url));
*self.style_attribute.borrow_mut() = style;

View File

@ -23,6 +23,8 @@ use dom::htmlbuttonelement::{HTMLButtonElement};
use dom::htmltextareaelement::{HTMLTextAreaElement, HTMLTextAreaElementHelpers};
use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node};
use hyper::method::Method;
use hyper::header::common::ContentType;
use hyper::mime;
use servo_msg::constellation_msg::LoadData;
use util::str::DOMString;
use script_task::{ScriptChan, ScriptMsg};
@ -178,7 +180,7 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> {
}
// TODO: Resolve the url relative to the submitter element
// Step 10-15
let action_components = UrlParser::new().base_url(base).parse(action.as_slice()).unwrap_or(base.clone());
let action_components = UrlParser::new().base_url(&base).parse(action.as_slice()).unwrap_or(base);
let _action = action_components.serialize();
let scheme = action_components.scheme.clone();
let enctype = submitter.enctype();
@ -186,12 +188,17 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> {
let _target = submitter.target();
// TODO: Handle browsing contexts, partially loaded documents (step 16-17)
let mut load_data = LoadData::new(action_components);
let parsed_data = match enctype {
FormEncType::UrlEncoded => serialize(form_data.iter().map(|d| (d.name.as_slice(), d.value.as_slice()))),
FormEncType::UrlEncoded => {
let mime: mime::Mime = "application/x-www-form-urlencoded".parse().unwrap();
load_data.headers.set(ContentType(mime));
serialize(form_data.iter().map(|d| (d.name.as_slice(), d.value.as_slice())))
}
_ => "".to_owned() // TODO: Add serializers for the other encoding types
};
let mut load_data = LoadData::new(action_components);
// Step 18
match (scheme.as_slice(), method) {
(_, FormMethod::FormDialog) => return, // Unimplemented

View File

@ -1574,7 +1574,7 @@ impl Node {
false => IsHTMLDocument::NonHTMLDocument,
};
let window = document.window().root();
let document = Document::new(window.r(), Some(document.url().clone()),
let document = Document::new(window.r(), Some(document.url()),
is_html_doc, None,
DocumentSource::NotFromParser);
NodeCast::from_temporary(document)

View File

@ -65,6 +65,8 @@ partial interface Document {
readonly attribute DocumentReadyState readyState;
readonly attribute DOMString lastModified;
readonly attribute Location location;
[Throws]
attribute DOMString cookie;
// DOM tree accessors
[SetterThrows]

View File

@ -561,41 +561,35 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
let mut load_data = LoadData::new(self.request_url.borrow().clone().unwrap(), start_chan);
load_data.data = extracted;
// Default headers
{
#[inline]
fn join_raw(a: &str, b: &str) -> Vec<u8> {
let len = a.len() + b.len();
let mut vec = Vec::with_capacity(len);
vec.push_all(a.as_bytes());
vec.push_all(b.as_bytes());
vec
}
let ref mut request_headers = self.request_headers.borrow_mut();
if !request_headers.has::<ContentType>() {
// XHR spec differs from http, and says UTF-8 should be in capitals,
// instead of "utf-8", which is what Hyper defaults to.
let params = ";charset=UTF-8";
let n = "content-type";
match data {
Some(eString(_)) =>
request_headers.set_raw(n.to_owned(), vec![join_raw("text/plain", params)]),
Some(eURLSearchParams(_)) =>
request_headers.set_raw(
n.to_owned(), vec![join_raw("application/x-www-form-urlencoded", params)]),
None => ()
}
}
#[inline]
fn join_raw(a: &str, b: &str) -> Vec<u8> {
let len = a.len() + b.len();
let mut vec = Vec::with_capacity(len);
vec.push_all(a.as_bytes());
vec.push_all(b.as_bytes());
vec
}
// XHR spec differs from http, and says UTF-8 should be in capitals,
// instead of "utf-8", which is what Hyper defaults to.
let params = ";charset=UTF-8";
let n = "content-type";
match data {
Some(eString(_)) =>
load_data.headers.set_raw(n.to_owned(), vec![join_raw("text/plain", params)]),
Some(eURLSearchParams(_)) =>
load_data.headers.set_raw(
n.to_owned(), vec![join_raw("application/x-www-form-urlencoded", params)]),
None => ()
}
if !request_headers.has::<Accept>() {
let mime = Mime(mime::TopLevel::Star, mime::SubLevel::Star, vec![]);
request_headers.set(
Accept(vec![QualityItem::new(mime, 1.0)]));
}
} // drops the borrow_mut
load_data.preserved_headers = (*self.request_headers.borrow()).clone();
if !load_data.preserved_headers.has::<Accept>() {
let mime = Mime(mime::TopLevel::Star, mime::SubLevel::Star, vec![]);
load_data.preserved_headers.set(Accept(vec![QualityItem::new(mime, 1.0)]));
}
load_data.headers = (*self.request_headers.borrow()).clone();
load_data.method = (*self.request_method.borrow()).clone();
let (terminate_sender, terminate_receiver) = channel();
*self.terminate_sender.borrow_mut() = Some(terminate_sender);
@ -607,8 +601,10 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
} else {
RequestMode::CORS
};
let mut combined_headers = load_data.headers.clone();
combined_headers.extend(load_data.preserved_headers.iter());
let cors_request = CORSRequest::maybe_new(referer_url.clone(), load_data.url.clone(), mode,
load_data.method.clone(), load_data.headers.clone());
load_data.method.clone(), combined_headers);
match cors_request {
Ok(None) => {
let mut buf = String::new();

View File

@ -67,7 +67,7 @@ use util::task::spawn_named_with_send_on_failure;
use util::task_state;
use geom::point::Point2D;
use hyper::header::{Header, HeaderFormat};
use hyper::header::{Header, Headers, HeaderFormat};
use hyper::header::shared::util as header_util;
use js::jsapi::{JS_SetWrapObjectCallbacks, JS_SetGCZeal, JS_DEFAULT_ZEAL_FREQ, JS_GC};
use js::jsapi::{JSContext, JSRuntime, JSObject};
@ -787,6 +787,44 @@ impl ScriptTask {
let is_javascript = url.scheme.as_slice() == "javascript";
self.compositor.borrow_mut().set_ready_state(pipeline_id, Loading);
let (mut parser_input, final_url, last_modified) = if !is_javascript {
// Wait for the LoadResponse so that the parser knows the final URL.
let (input_chan, input_port) = channel();
self.resource_task.send(ControlMsg::Load(NetLoadData {
url: url,
method: load_data.method,
headers: Headers::new(),
preserved_headers: load_data.headers,
data: load_data.data,
cors: None,
consumer: input_chan,
})).unwrap();
let load_response = input_port.recv().unwrap();
let last_modified = load_response.metadata.headers.as_ref().and_then(|headers| {
headers.get().map(|&LastModified(ref tm)| tm.clone())
});
let final_url = load_response.metadata.final_url.clone();
(Some(HTMLInput::InputUrl(load_response)), final_url, last_modified)
} else {
let doc_url = last_url.unwrap_or_else(|| {
Url::parse("about:blank").unwrap()
});
(None, doc_url, None)
};
// Store the final URL before we start parsing, so that DOM routines
// (e.g. HTMLImageElement::update_image) can resolve relative URLs
// correctly.
{
*page.mut_url() = Some((final_url.clone(), true));
}
let cx = self.js_context.borrow();
let cx = cx.as_ref().unwrap();
// Create the window and document objects.
@ -796,22 +834,15 @@ impl ScriptTask {
self.control_chan.clone(),
self.compositor.borrow_mut().dup(),
self.image_cache_task.clone()).root();
let doc_url = if is_javascript {
let doc_url = last_url.unwrap_or_else(|| {
Url::parse("about:blank").unwrap()
});
*page.mut_url() = Some((doc_url.clone(), true));
doc_url
} else {
url.clone()
};
let document = Document::new(window.r(), Some(doc_url.clone()),
let document = Document::new(window.r(), Some(final_url.clone()),
IsHTMLDocument::HTMLDocument, None,
DocumentSource::FromParser).root();
if let Some(tm) = last_modified {
document.set_last_modified(dom_last_modified(&tm));
}
window.r().init_browser_context(document.r());
self.compositor.borrow_mut().set_ready_state(pipeline_id, Loading);
{
// Create the root frame.
@ -822,45 +853,15 @@ impl ScriptTask {
});
}
let (parser_input, final_url) = if !is_javascript {
// Wait for the LoadResponse so that the parser knows the final URL.
let (input_chan, input_port) = channel();
self.resource_task.send(ControlMsg::Load(NetLoadData {
url: url,
method: load_data.method,
headers: load_data.headers,
data: load_data.data,
cors: None,
consumer: input_chan,
})).unwrap();
let load_response = input_port.recv().unwrap();
load_response.metadata.headers.as_ref().map(|headers| {
headers.get().map(|&LastModified(ref tm)| {
document.r().set_last_modified(dom_last_modified(tm));
});
});
let final_url = load_response.metadata.final_url.clone();
{
// Store the final URL before we start parsing, so that DOM routines
// (e.g. HTMLImageElement::update_image) can resolve relative URLs
// correctly.
*page.mut_url() = Some((final_url.clone(), true));
}
(HTMLInput::InputUrl(load_response), final_url)
} else {
if is_javascript {
let evalstr = load_data.url.non_relative_scheme_data().unwrap();
let jsval = window.r().evaluate_js_on_global_with_result(evalstr);
let strval = FromJSValConvertible::from_jsval(self.get_cx(), jsval,
StringificationBehavior::Empty);
(HTMLInput::InputString(strval.unwrap_or("".to_owned())), doc_url)
parser_input = Some(HTMLInput::InputString(strval.unwrap_or("".to_owned())));
};
parse_html(document.r(), parser_input, &final_url);
parse_html(document.r(), parser_input.unwrap(), &final_url);
document.r().set_ready_state(DocumentReadyState::Interactive);
self.compositor.borrow_mut().set_ready_state(pipeline_id, PerformingLayout);

View File

@ -100,7 +100,7 @@ dependencies = [
[[package]]
name = "cookie"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
source = "git+https://github.com/servo/cookie-rs?branch=lenientparse_backport#47ffa4d3c6f85d28f222d6e1d54635fff5622ea3"
dependencies = [
"openssl 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -154,6 +154,7 @@ name = "devtools_traits"
version = "0.0.1"
dependencies = [
"msg 0.0.1",
"util 0.0.1",
]
[[package]]
@ -398,9 +399,9 @@ source = "git+https://github.com/servo/html5ever#d35dfaaf0d85007057a299afc370d07
[[package]]
name = "hyper"
version = "0.1.1"
source = "git+https://github.com/servo/hyper?branch=servo#7f48a7e945180a4f762dc75236210d20a69b4a6a"
source = "git+https://github.com/servo/hyper?branch=old_servo_new_cookies#7a346f481d683705709526594aa5f13b5c923bc1"
dependencies = [
"cookie 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"cookie 0.1.8 (git+https://github.com/servo/cookie-rs?branch=lenientparse_backport)",
"log 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mucell 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@ -541,7 +542,7 @@ dependencies = [
"azure 0.1.0 (git+https://github.com/servo/rust-azure)",
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=servo)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=old_servo_new_cookies)",
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface)",
"layers 0.1.0 (git+https://github.com/servo/rust-layers)",
"style 0.0.1",
@ -558,8 +559,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "net"
version = "0.0.1"
dependencies = [
"cookie 0.1.8 (git+https://github.com/servo/cookie-rs?branch=lenientparse_backport)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=servo)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=old_servo_new_cookies)",
"openssl 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"stb_image 0.1.0 (git+https://github.com/servo/rust-stb-image)",
@ -655,7 +657,7 @@ dependencies = [
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
"gfx 0.0.1",
"html5ever 0.0.0 (git+https://github.com/servo/html5ever)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=servo)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=old_servo_new_cookies)",
"js 0.1.0 (git+https://github.com/servo/rust-mozjs)",
"msg 0.0.1",
"net 0.0.1",

View File

@ -99,7 +99,7 @@ dependencies = [
[[package]]
name = "cookie"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
source = "git+https://github.com/servo/cookie-rs?branch=lenientparse_backport#47ffa4d3c6f85d28f222d6e1d54635fff5622ea3"
dependencies = [
"openssl 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -153,6 +153,7 @@ name = "devtools_traits"
version = "0.0.1"
dependencies = [
"msg 0.0.1",
"util 0.0.1",
]
[[package]]
@ -397,9 +398,9 @@ source = "git+https://github.com/servo/html5ever#d35dfaaf0d85007057a299afc370d07
[[package]]
name = "hyper"
version = "0.1.1"
source = "git+https://github.com/servo/hyper?branch=servo#7f48a7e945180a4f762dc75236210d20a69b4a6a"
source = "git+https://github.com/servo/hyper?branch=old_servo_new_cookies#7a346f481d683705709526594aa5f13b5c923bc1"
dependencies = [
"cookie 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"cookie 0.1.8 (git+https://github.com/servo/cookie-rs?branch=lenientparse_backport)",
"log 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mucell 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@ -540,7 +541,7 @@ dependencies = [
"azure 0.1.0 (git+https://github.com/servo/rust-azure)",
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=servo)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=old_servo_new_cookies)",
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface)",
"layers 0.1.0 (git+https://github.com/servo/rust-layers)",
"style 0.0.1",
@ -557,8 +558,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "net"
version = "0.0.1"
dependencies = [
"cookie 0.1.8 (git+https://github.com/servo/cookie-rs?branch=lenientparse_backport)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=servo)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=old_servo_new_cookies)",
"openssl 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.1.0 (git+https://github.com/servo/rust-png)",
"stb_image 0.1.0 (git+https://github.com/servo/rust-stb-image)",
@ -654,7 +656,7 @@ dependencies = [
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
"gfx 0.0.1",
"html5ever 0.0.0 (git+https://github.com/servo/html5ever)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=servo)",
"hyper 0.1.1 (git+https://github.com/servo/hyper?branch=old_servo_new_cookies)",
"js 0.1.0 (git+https://github.com/servo/rust-mozjs)",
"msg 0.0.1",
"net 0.0.1",

View File

@ -124,6 +124,7 @@ name = "devtools_traits"
version = "0.0.1"
dependencies = [
"msg 0.0.1",
"util 0.0.1",
]
[[package]]

View File

@ -163,4 +163,3 @@ impl<Window> Browser<Window> where Window: WindowMethods + 'static {
self.compositor.shutdown();
}
}