Refactor http_fetch to reflect the new standard

This commit is contained in:
Keith Yeung 2015-11-11 21:00:25 -08:00
parent 7623e89506
commit 7d3eb72a26
3 changed files with 152 additions and 83 deletions

View file

@ -11,10 +11,13 @@ use hyper::header::{QualityItem, q, qitem};
use hyper::method::Method; use hyper::method::Method;
use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value}; use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value};
use hyper::status::StatusCode; use hyper::status::StatusCode;
use net_traits::{AsyncFetchListener, Response, ResponseType, Metadata}; use net_traits::{AsyncFetchListener, Response};
use net_traits::{ResponseType, Metadata};
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::cell::RefCell;
use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
use url::Url; use url::{Url, UrlParser};
use util::task::spawn_named; use util::task::spawn_named;
/// A [request context](https://fetch.spec.whatwg.org/#concept-request-context) /// A [request context](https://fetch.spec.whatwg.org/#concept-request-context)
@ -90,7 +93,8 @@ pub enum ResponseTainting {
/// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec /// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
pub struct Request { pub struct Request {
pub method: Method, pub method: Method,
pub url: Url, // Use the last method on url_list to act as spec url field
pub url_list: Vec<Url>,
pub headers: Headers, pub headers: Headers,
pub unsafe_request: bool, pub unsafe_request: bool,
pub body: Option<Vec<u8>>, pub body: Option<Vec<u8>>,
@ -101,7 +105,7 @@ pub struct Request {
pub skip_service_worker: bool, pub skip_service_worker: bool,
pub context: Context, pub context: Context,
pub context_frame_type: ContextFrameType, pub context_frame_type: ContextFrameType,
pub origin: Option<Url>, pub origin: Option<Url>, // FIXME: Use Url::Origin
pub force_origin_header: bool, pub force_origin_header: bool,
pub same_origin_data: bool, pub same_origin_data: bool,
pub referer: Referer, pub referer: Referer,
@ -121,7 +125,7 @@ impl Request {
pub fn new(url: Url, context: Context, is_service_worker_global_scope: bool) -> Request { pub fn new(url: Url, context: Context, is_service_worker_global_scope: bool) -> Request {
Request { Request {
method: Method::Get, method: Method::Get,
url: url, url_list: vec![url],
headers: Headers::new(), headers: Headers::new(),
unsafe_request: false, unsafe_request: false,
body: None, body: None,
@ -147,13 +151,17 @@ impl Request {
} }
} }
fn get_last_url_string(&self) -> String {
self.url_list.last().unwrap().serialize()
}
pub fn fetch_async(mut self, pub fn fetch_async(mut self,
cors_flag: bool, cors_flag: bool,
listener: Box<AsyncFetchListener + Send>) { listener: Box<AsyncFetchListener + Send>) {
spawn_named(format!("fetch for {:?}", self.url.serialize()), move || { spawn_named(format!("fetch for {:?}", self.get_last_url_string()), move || {
let res = self.fetch(cors_flag); let res = self.fetch(cors_flag);
listener.response_available(res); listener.response_available(res);
}); })
} }
/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch) /// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
@ -211,17 +219,21 @@ impl Request {
/// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch) /// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
pub fn basic_fetch(&mut self) -> Response { pub fn basic_fetch(&mut self) -> Response {
match &*self.url.scheme { let scheme = self.url_list.last().unwrap().scheme.clone();
"about" => match self.url.non_relative_scheme_data() { match &*scheme {
Some(s) if &*s == "blank" => { "about" => {
let mut response = Response::new(); let url = self.url_list.last().unwrap();
response.headers.set(ContentType(Mime( match url.non_relative_scheme_data() {
TopLevel::Text, SubLevel::Html, Some(s) if &*s == "blank" => {
vec![(Attr::Charset, Value::Utf8)]))); let mut response = Response::new();
response response.headers.set(ContentType(Mime(
}, TopLevel::Text, SubLevel::Html,
_ => Response::network_error() vec![(Attr::Charset, Value::Utf8)])));
}, response
},
_ => Response::network_error()
}
}
"http" | "https" => { "http" | "https" => {
self.http_fetch(false, false, false) self.http_fetch(false, false, false)
}, },
@ -234,31 +246,64 @@ impl Request {
} }
} }
pub fn http_fetch_async(mut self, cors_flag: bool,
cors_preflight_flag: bool,
authentication_fetch_flag: bool,
listener: Box<AsyncFetchListener + Send>) {
spawn_named(format!("http_fetch for {:?}", self.get_last_url_string()), move || {
let res = self.http_fetch(cors_flag, cors_preflight_flag,
authentication_fetch_flag);
listener.response_available(res);
});
}
/// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch) /// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch)
pub fn http_fetch(&mut self, cors_flag: bool, cors_preflight_flag: bool, pub fn http_fetch(&mut self, cors_flag: bool, cors_preflight_flag: bool,
authentication_fetch_flag: bool) -> Response { authentication_fetch_flag: bool) -> Response {
// Step 1 // Step 1
let mut response: Option<Response> = None; let mut response: Option<Rc<RefCell<Response>>> = None;
// Step 2 // Step 2
let mut actual_response: Option<Rc<RefCell<Response>>> = None;
// Step 3
if !self.skip_service_worker && !self.is_service_worker_global_scope { if !self.skip_service_worker && !self.is_service_worker_global_scope {
// TODO: Substep 1 (handle fetch unimplemented) // TODO: Substep 1 (handle fetch unimplemented)
// Substep 2
if let Some(ref res) = response { if let Some(ref res) = response {
if (res.response_type == ResponseType::Opaque && self.mode != RequestMode::NoCORS) || let resp = res.borrow();
res.response_type == ResponseType::Error { // Substep 2
actual_response = match resp.internal_response {
Some(ref internal_res) => Some(internal_res.clone()),
None => Some(res.clone())
};
// Substep 3
if (resp.response_type == ResponseType::Opaque &&
self.mode != RequestMode::NoCORS) ||
(resp.response_type == ResponseType::OpaqueRedirect &&
self.redirect_mode != RedirectMode::Manual) ||
resp.response_type == ResponseType::Error {
return Response::network_error(); return Response::network_error();
} }
} }
// Substep 4
if let Some(ref res) = actual_response {
let mut resp = res.borrow_mut();
if resp.url_list.is_empty() {
resp.url_list = self.url_list.clone();
}
}
// Substep 5
// TODO: set response's CSP list on actual_response
} }
// Step 3 // Step 4
if response.is_none() { if response.is_none() {
// Substep 1 // Substep 1
if cors_preflight_flag { if cors_preflight_flag {
let mut method_mismatch = false; let mut method_mismatch = false;
let mut header_mismatch = false; let mut header_mismatch = false;
if let Some(ref mut cache) = self.cache { if let Some(ref mut cache) = self.cache {
// FIXME: Once Url::Origin is available, rewrite origin to
// take an Origin instead of a Url
let origin = self.origin.clone().unwrap_or(Url::parse("").unwrap()); let origin = self.origin.clone().unwrap_or(Url::parse("").unwrap());
let url = self.url.clone(); let url = self.url_list.last().unwrap().clone();
let credentials = self.credentials_mode == CredentialsMode::Include; let credentials = self.credentials_mode == CredentialsMode::Include;
let method_cache_match = cache.match_method(CacheRequestDetails { let method_cache_match = cache.match_method(CacheRequestDetails {
origin: origin.clone(), origin: origin.clone(),
@ -280,7 +325,7 @@ impl Request {
if preflight_result.response_type == ResponseType::Error { if preflight_result.response_type == ResponseType::Error {
return Response::network_error(); return Response::network_error();
} }
response = Some(preflight_result); response = Some(Rc::new(RefCell::new(preflight_result)));
} }
} }
// Substep 2 // Substep 2
@ -288,31 +333,24 @@ impl Request {
// Substep 3 // Substep 3
let credentials = match self.credentials_mode { let credentials = match self.credentials_mode {
CredentialsMode::Include => true, CredentialsMode::Include => true,
CredentialsMode::CredentialsSameOrigin if !cors_flag => true, CredentialsMode::CredentialsSameOrigin if (!cors_flag ||
self.response_tainting == ResponseTainting::Opaque)
=> true,
_ => false _ => false
}; };
// Substep 4 // Substep 4
if self.cache_mode == CacheMode::Default && is_no_store_cache(&self.headers) {
self.cache_mode = CacheMode::NoStore;
}
// Substep 5
let fetch_result = self.http_network_or_cache_fetch(credentials, authentication_fetch_flag); let fetch_result = self.http_network_or_cache_fetch(credentials, authentication_fetch_flag);
// Substep 6 // Substep 5
if cors_flag && self.cors_check(&fetch_result).is_err() { if cors_flag && self.cors_check(&fetch_result).is_err() {
return Response::network_error(); return Response::network_error();
} }
response = Some(fetch_result); response = Some(Rc::new(RefCell::new(fetch_result)));
actual_response = response.clone();
} }
// Step 4 // Step 5
let mut response = response.unwrap(); let mut actual_response = Rc::try_unwrap(actual_response.unwrap()).ok().unwrap().into_inner();
match response.status.unwrap() { let mut response = Rc::try_unwrap(response.unwrap()).ok().unwrap();
// Code 304 match actual_response.status.unwrap() {
StatusCode::NotModified => match self.cache_mode {
CacheMode::Default | CacheMode::NoCache => {
// TODO: Check HTTP cache for request and response entry
}
_ => { }
},
// Code 301, 302, 303, 307, 308 // Code 301, 302, 303, 307, 308
StatusCode::MovedPermanently | StatusCode::Found | StatusCode::SeeOther | StatusCode::MovedPermanently | StatusCode::Found | StatusCode::SeeOther |
StatusCode::TemporaryRedirect | StatusCode::PermanentRedirect => { StatusCode::TemporaryRedirect | StatusCode::PermanentRedirect => {
@ -321,19 +359,20 @@ impl Request {
return Response::network_error(); return Response::network_error();
} }
// Step 2-4 // Step 2-4
if !response.headers.has::<Location>() { if !actual_response.headers.has::<Location>() {
return response; return actual_response;
} }
let location = match response.headers.get::<Location>() { let location = match actual_response.headers.get::<Location>() {
None => return Response::network_error(), Some(&Location(ref location)) => location.clone(),
Some(location) => location, _ => return Response::network_error(),
}; };
// Step 5 // Step 5
let location_url = Url::parse(location); let location_url = UrlParser::new().base_url(self.url_list.last().unwrap()).parse(&*location);
// Step 6 // Step 6
let location_url = match location_url { let location_url = match location_url {
Ok(ref url) if url.scheme == "data" => { return Response::network_error(); }
Ok(url) => url, Ok(url) => url,
Err(_) => return Response::network_error() _ => { return Response::network_error(); }
}; };
// Step 7 // Step 7
if self.redirect_count == 20 { if self.redirect_count == 20 {
@ -341,62 +380,79 @@ impl Request {
} }
// Step 8 // Step 8
self.redirect_count += 1; self.redirect_count += 1;
// Step 9 match self.redirect_mode {
self.same_origin_data = false; // Step 9
// Step 10 RedirectMode::Manual => {
if self.redirect_mode == RedirectMode::Follow { *response.borrow_mut() = actual_response.to_filtered(ResponseType::Opaque);
// FIXME: Origin method of the Url crate hasn't been implemented
// https://github.com/servo/rust-url/issues/54
// Substep 1
// if cors_flag && location_url.origin() != self.url.origin() { self.origin = None; }
// Substep 2
if cors_flag && (!location_url.username().unwrap_or("").is_empty() ||
location_url.password().is_some()) {
return Response::network_error();
} }
// Substep 3 // Step 10
if response.status.unwrap() == StatusCode::MovedPermanently || RedirectMode::Follow => {
response.status.unwrap() == StatusCode::SeeOther || // Substep 1
(response.status.unwrap() == StatusCode::Found && self.method == Method::Post) { // FIXME: Use Url::origin
self.method = Method::Get; // if (self.mode == RequestMode::CORSMode || self.mode == RequestMode::ForcedPreflightMode) &&
// location_url.origin() != self.url.origin() &&
// has_credentials(&location_url) {
// return Response::network_error();
// }
// Substep 2
if cors_flag && has_credentials(&location_url) {
return Response::network_error();
}
// Substep 3
// FIXME: Use Url::origin
// if cors_flag && location_url.origin() != self.url.origin() {
// self.origin = Origin::UID(OpaqueOrigin);
// }
// Substep 4
if actual_response.status.unwrap() == StatusCode::SeeOther ||
((actual_response.status.unwrap() == StatusCode::MovedPermanently ||
actual_response.status.unwrap() == StatusCode::Found) &&
self.method == Method::Post) {
self.method = Method::Get;
}
// Substep 5
self.url_list.push(location_url);
// Substep 6
return self.main_fetch(cors_flag);
} }
// Substep 4 RedirectMode::Error => { panic!("RedirectMode is Error after step 8") }
self.url = location_url;
// Substep 5
return self.fetch(cors_flag);
} }
} }
// Code 401 // Code 401
StatusCode::Unauthorized => { StatusCode::Unauthorized => {
// Step 1 // Step 1
if !self.authentication || cors_flag { // FIXME: Figure out what to do with request window objects
return response; if cors_flag {
return response.into_inner();
} }
// Step 2 // Step 2
// TODO: Spec says requires testing // TODO: Spec says requires testing on multiple WWW-Authenticate headers
// Step 3 // Step 3
if !self.use_url_credentials || authentication_fetch_flag { if !self.use_url_credentials || authentication_fetch_flag {
// TODO: Prompt the user for username and password // TODO: Prompt the user for username and password from the window
} }
// Step 4
return self.http_fetch(cors_flag, cors_preflight_flag, true); return self.http_fetch(cors_flag, cors_preflight_flag, true);
} }
// Code 407 // Code 407
StatusCode::ProxyAuthenticationRequired => { StatusCode::ProxyAuthenticationRequired => {
// Step 1 // Step 1
// TODO: Spec says requires testing // TODO: Figure out what to do with request window objects
// Step 2 // Step 2
// TODO: Prompt the user for proxy authentication credentials // TODO: Spec says requires testing on Proxy-Authenticate headers
// Step 3 // Step 3
// TODO: Prompt the user for proxy authentication credentials
// Step 4
return self.http_fetch(cors_flag, cors_preflight_flag, authentication_fetch_flag); return self.http_fetch(cors_flag, cors_preflight_flag, authentication_fetch_flag);
} }
_ => { } _ => { }
} }
// Step 5 let mut response = response.into_inner();
// Step 6
if authentication_fetch_flag { if authentication_fetch_flag {
// TODO: Create authentication entry for this request // TODO: Create authentication entry for this request
} }
// Step 6 // Step 7
response response
} }
@ -421,6 +477,10 @@ impl Request {
} }
} }
fn has_credentials(url: &Url) -> bool {
!url.username().unwrap_or("").is_empty() || url.password().is_some()
}
fn is_no_store_cache(headers: &Headers) -> bool { fn is_no_store_cache(headers: &Headers) -> bool {
headers.has::<IfModifiedSince>() | headers.has::<IfNoneMatch>() | headers.has::<IfModifiedSince>() | headers.has::<IfNoneMatch>() |
headers.has::<IfUnmodifiedSince>() | headers.has::<IfMatch>() | headers.has::<IfUnmodifiedSince>() | headers.has::<IfMatch>() |

View file

@ -6,6 +6,8 @@ use hyper::header::Headers;
use hyper::status::StatusCode; use hyper::status::StatusCode;
use net_traits::{Response, ResponseBody, ResponseType}; use net_traits::{Response, ResponseBody, ResponseType};
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use url::Url; use url::Url;
@ -20,6 +22,7 @@ impl ResponseMethods for Response {
response_type: ResponseType::Default, response_type: ResponseType::Default,
termination_reason: None, termination_reason: None,
url: None, url: None,
url_list: Vec::new(),
status: Some(StatusCode::Ok), status: Some(StatusCode::Ok),
headers: Headers::new(), headers: Headers::new(),
body: ResponseBody::Empty, body: ResponseBody::Empty,
@ -37,7 +40,7 @@ impl ResponseMethods for Response {
} }
let old_headers = self.headers.clone(); let old_headers = self.headers.clone();
let mut response = self.clone(); let mut response = self.clone();
response.internal_response = Some(box self); response.internal_response = Some(Rc::new(RefCell::new(self)));
match filter_type { match filter_type {
ResponseType::Default | ResponseType::Error => unreachable!(), ResponseType::Default | ResponseType::Error => unreachable!(),
ResponseType::Basic => { ResponseType::Basic => {
@ -62,7 +65,8 @@ impl ResponseMethods for Response {
response.headers = headers; response.headers = headers;
response.response_type = filter_type; response.response_type = filter_type;
}, },
ResponseType::Opaque => { ResponseType::Opaque |
ResponseType::OpaqueRedirect => {
response.headers = Headers::new(); response.headers = Headers::new();
response.status = None; response.status = None;
response.body = ResponseBody::Empty; response.body = ResponseBody::Empty;

View file

@ -35,6 +35,8 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use msg::constellation_msg::{PipelineId}; use msg::constellation_msg::{PipelineId};
use regex::Regex; use regex::Regex;
use serde::{Deserializer, Serializer}; use serde::{Deserializer, Serializer};
use std::cell::RefCell;
use std::rc::Rc;
use std::thread; use std::thread;
use url::Url; use url::Url;
use util::mem::HeapSizeOf; use util::mem::HeapSizeOf;
@ -55,7 +57,8 @@ pub enum ResponseType {
CORS, CORS,
Default, Default,
Error, Error,
Opaque Opaque,
OpaqueRedirect
} }
/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason) /// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
@ -87,13 +90,14 @@ pub struct Response {
pub response_type: ResponseType, pub response_type: ResponseType,
pub termination_reason: Option<TerminationReason>, pub termination_reason: Option<TerminationReason>,
pub url: Option<Url>, pub url: Option<Url>,
pub url_list: Vec<Url>,
/// `None` can be considered a StatusCode of `0`. /// `None` can be considered a StatusCode of `0`.
pub status: Option<StatusCode>, pub status: Option<StatusCode>,
pub headers: Headers, pub headers: Headers,
pub body: ResponseBody, pub body: ResponseBody,
/// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
/// is a filtered response /// is a filtered response
pub internal_response: Option<Box<Response>>, pub internal_response: Option<Rc<RefCell<Response>>>,
} }
impl Response { impl Response {
@ -102,6 +106,7 @@ impl Response {
response_type: ResponseType::Error, response_type: ResponseType::Error,
termination_reason: None, termination_reason: None,
url: None, url: None,
url_list: vec![],
status: None, status: None,
headers: Headers::new(), headers: Headers::new(),
body: ResponseBody::Empty, body: ResponseBody::Empty,