mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Refactor http_fetch to reflect the new standard
This commit is contained in:
parent
7623e89506
commit
7d3eb72a26
3 changed files with 152 additions and 83 deletions
|
@ -11,10 +11,13 @@ use hyper::header::{QualityItem, q, qitem};
|
|||
use hyper::method::Method;
|
||||
use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value};
|
||||
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::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
use url::{Url, UrlParser};
|
||||
use util::task::spawn_named;
|
||||
|
||||
/// 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
|
||||
pub struct Request {
|
||||
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 unsafe_request: bool,
|
||||
pub body: Option<Vec<u8>>,
|
||||
|
@ -101,7 +105,7 @@ pub struct Request {
|
|||
pub skip_service_worker: bool,
|
||||
pub context: Context,
|
||||
pub context_frame_type: ContextFrameType,
|
||||
pub origin: Option<Url>,
|
||||
pub origin: Option<Url>, // FIXME: Use Url::Origin
|
||||
pub force_origin_header: bool,
|
||||
pub same_origin_data: bool,
|
||||
pub referer: Referer,
|
||||
|
@ -121,7 +125,7 @@ impl Request {
|
|||
pub fn new(url: Url, context: Context, is_service_worker_global_scope: bool) -> Request {
|
||||
Request {
|
||||
method: Method::Get,
|
||||
url: url,
|
||||
url_list: vec![url],
|
||||
headers: Headers::new(),
|
||||
unsafe_request: false,
|
||||
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,
|
||||
cors_flag: bool,
|
||||
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);
|
||||
listener.response_available(res);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
|
||||
|
@ -211,17 +219,21 @@ impl Request {
|
|||
|
||||
/// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
|
||||
pub fn basic_fetch(&mut self) -> Response {
|
||||
match &*self.url.scheme {
|
||||
"about" => match self.url.non_relative_scheme_data() {
|
||||
Some(s) if &*s == "blank" => {
|
||||
let mut response = Response::new();
|
||||
response.headers.set(ContentType(Mime(
|
||||
TopLevel::Text, SubLevel::Html,
|
||||
vec![(Attr::Charset, Value::Utf8)])));
|
||||
response
|
||||
},
|
||||
_ => Response::network_error()
|
||||
},
|
||||
let scheme = self.url_list.last().unwrap().scheme.clone();
|
||||
match &*scheme {
|
||||
"about" => {
|
||||
let url = self.url_list.last().unwrap();
|
||||
match url.non_relative_scheme_data() {
|
||||
Some(s) if &*s == "blank" => {
|
||||
let mut response = Response::new();
|
||||
response.headers.set(ContentType(Mime(
|
||||
TopLevel::Text, SubLevel::Html,
|
||||
vec![(Attr::Charset, Value::Utf8)])));
|
||||
response
|
||||
},
|
||||
_ => Response::network_error()
|
||||
}
|
||||
}
|
||||
"http" | "https" => {
|
||||
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)
|
||||
pub fn http_fetch(&mut self, cors_flag: bool, cors_preflight_flag: bool,
|
||||
authentication_fetch_flag: bool) -> Response {
|
||||
// Step 1
|
||||
let mut response: Option<Response> = None;
|
||||
let mut response: Option<Rc<RefCell<Response>>> = None;
|
||||
// Step 2
|
||||
let mut actual_response: Option<Rc<RefCell<Response>>> = None;
|
||||
// Step 3
|
||||
if !self.skip_service_worker && !self.is_service_worker_global_scope {
|
||||
// TODO: Substep 1 (handle fetch unimplemented)
|
||||
// Substep 2
|
||||
if let Some(ref res) = response {
|
||||
if (res.response_type == ResponseType::Opaque && self.mode != RequestMode::NoCORS) ||
|
||||
res.response_type == ResponseType::Error {
|
||||
let resp = res.borrow();
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
// 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() {
|
||||
// Substep 1
|
||||
if cors_preflight_flag {
|
||||
let mut method_mismatch = false;
|
||||
let mut header_mismatch = false;
|
||||
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 url = self.url.clone();
|
||||
let url = self.url_list.last().unwrap().clone();
|
||||
let credentials = self.credentials_mode == CredentialsMode::Include;
|
||||
let method_cache_match = cache.match_method(CacheRequestDetails {
|
||||
origin: origin.clone(),
|
||||
|
@ -280,7 +325,7 @@ impl Request {
|
|||
if preflight_result.response_type == ResponseType::Error {
|
||||
return Response::network_error();
|
||||
}
|
||||
response = Some(preflight_result);
|
||||
response = Some(Rc::new(RefCell::new(preflight_result)));
|
||||
}
|
||||
}
|
||||
// Substep 2
|
||||
|
@ -288,31 +333,24 @@ impl Request {
|
|||
// Substep 3
|
||||
let credentials = match self.credentials_mode {
|
||||
CredentialsMode::Include => true,
|
||||
CredentialsMode::CredentialsSameOrigin if !cors_flag => true,
|
||||
CredentialsMode::CredentialsSameOrigin if (!cors_flag ||
|
||||
self.response_tainting == ResponseTainting::Opaque)
|
||||
=> true,
|
||||
_ => false
|
||||
};
|
||||
// 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);
|
||||
// Substep 6
|
||||
// Substep 5
|
||||
if cors_flag && self.cors_check(&fetch_result).is_err() {
|
||||
return Response::network_error();
|
||||
}
|
||||
response = Some(fetch_result);
|
||||
response = Some(Rc::new(RefCell::new(fetch_result)));
|
||||
actual_response = response.clone();
|
||||
}
|
||||
// Step 4
|
||||
let mut response = response.unwrap();
|
||||
match response.status.unwrap() {
|
||||
// Code 304
|
||||
StatusCode::NotModified => match self.cache_mode {
|
||||
CacheMode::Default | CacheMode::NoCache => {
|
||||
// TODO: Check HTTP cache for request and response entry
|
||||
}
|
||||
_ => { }
|
||||
},
|
||||
// Step 5
|
||||
let mut actual_response = Rc::try_unwrap(actual_response.unwrap()).ok().unwrap().into_inner();
|
||||
let mut response = Rc::try_unwrap(response.unwrap()).ok().unwrap();
|
||||
match actual_response.status.unwrap() {
|
||||
// Code 301, 302, 303, 307, 308
|
||||
StatusCode::MovedPermanently | StatusCode::Found | StatusCode::SeeOther |
|
||||
StatusCode::TemporaryRedirect | StatusCode::PermanentRedirect => {
|
||||
|
@ -321,19 +359,20 @@ impl Request {
|
|||
return Response::network_error();
|
||||
}
|
||||
// Step 2-4
|
||||
if !response.headers.has::<Location>() {
|
||||
return response;
|
||||
if !actual_response.headers.has::<Location>() {
|
||||
return actual_response;
|
||||
}
|
||||
let location = match response.headers.get::<Location>() {
|
||||
None => return Response::network_error(),
|
||||
Some(location) => location,
|
||||
let location = match actual_response.headers.get::<Location>() {
|
||||
Some(&Location(ref location)) => location.clone(),
|
||||
_ => return Response::network_error(),
|
||||
};
|
||||
// Step 5
|
||||
let location_url = Url::parse(location);
|
||||
let location_url = UrlParser::new().base_url(self.url_list.last().unwrap()).parse(&*location);
|
||||
// Step 6
|
||||
let location_url = match location_url {
|
||||
Ok(ref url) if url.scheme == "data" => { return Response::network_error(); }
|
||||
Ok(url) => url,
|
||||
Err(_) => return Response::network_error()
|
||||
_ => { return Response::network_error(); }
|
||||
};
|
||||
// Step 7
|
||||
if self.redirect_count == 20 {
|
||||
|
@ -341,62 +380,79 @@ impl Request {
|
|||
}
|
||||
// Step 8
|
||||
self.redirect_count += 1;
|
||||
// Step 9
|
||||
self.same_origin_data = false;
|
||||
// Step 10
|
||||
if self.redirect_mode == RedirectMode::Follow {
|
||||
// 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();
|
||||
match self.redirect_mode {
|
||||
// Step 9
|
||||
RedirectMode::Manual => {
|
||||
*response.borrow_mut() = actual_response.to_filtered(ResponseType::Opaque);
|
||||
}
|
||||
// Substep 3
|
||||
if response.status.unwrap() == StatusCode::MovedPermanently ||
|
||||
response.status.unwrap() == StatusCode::SeeOther ||
|
||||
(response.status.unwrap() == StatusCode::Found && self.method == Method::Post) {
|
||||
self.method = Method::Get;
|
||||
// Step 10
|
||||
RedirectMode::Follow => {
|
||||
// Substep 1
|
||||
// FIXME: Use Url::origin
|
||||
// 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
|
||||
self.url = location_url;
|
||||
// Substep 5
|
||||
return self.fetch(cors_flag);
|
||||
RedirectMode::Error => { panic!("RedirectMode is Error after step 8") }
|
||||
}
|
||||
}
|
||||
// Code 401
|
||||
StatusCode::Unauthorized => {
|
||||
// Step 1
|
||||
if !self.authentication || cors_flag {
|
||||
return response;
|
||||
// FIXME: Figure out what to do with request window objects
|
||||
if cors_flag {
|
||||
return response.into_inner();
|
||||
}
|
||||
// Step 2
|
||||
// TODO: Spec says requires testing
|
||||
// TODO: Spec says requires testing on multiple WWW-Authenticate headers
|
||||
// Step 3
|
||||
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);
|
||||
}
|
||||
// Code 407
|
||||
StatusCode::ProxyAuthenticationRequired => {
|
||||
// Step 1
|
||||
// TODO: Spec says requires testing
|
||||
// TODO: Figure out what to do with request window objects
|
||||
// Step 2
|
||||
// TODO: Prompt the user for proxy authentication credentials
|
||||
// TODO: Spec says requires testing on Proxy-Authenticate headers
|
||||
// Step 3
|
||||
// TODO: Prompt the user for proxy authentication credentials
|
||||
// Step 4
|
||||
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 {
|
||||
// TODO: Create authentication entry for this request
|
||||
}
|
||||
// Step 6
|
||||
// Step 7
|
||||
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 {
|
||||
headers.has::<IfModifiedSince>() | headers.has::<IfNoneMatch>() |
|
||||
headers.has::<IfUnmodifiedSince>() | headers.has::<IfMatch>() |
|
||||
|
|
|
@ -6,6 +6,8 @@ use hyper::header::Headers;
|
|||
use hyper::status::StatusCode;
|
||||
use net_traits::{Response, ResponseBody, ResponseType};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use url::Url;
|
||||
|
||||
|
@ -20,6 +22,7 @@ impl ResponseMethods for Response {
|
|||
response_type: ResponseType::Default,
|
||||
termination_reason: None,
|
||||
url: None,
|
||||
url_list: Vec::new(),
|
||||
status: Some(StatusCode::Ok),
|
||||
headers: Headers::new(),
|
||||
body: ResponseBody::Empty,
|
||||
|
@ -37,7 +40,7 @@ impl ResponseMethods for Response {
|
|||
}
|
||||
let old_headers = self.headers.clone();
|
||||
let mut response = self.clone();
|
||||
response.internal_response = Some(box self);
|
||||
response.internal_response = Some(Rc::new(RefCell::new(self)));
|
||||
match filter_type {
|
||||
ResponseType::Default | ResponseType::Error => unreachable!(),
|
||||
ResponseType::Basic => {
|
||||
|
@ -62,7 +65,8 @@ impl ResponseMethods for Response {
|
|||
response.headers = headers;
|
||||
response.response_type = filter_type;
|
||||
},
|
||||
ResponseType::Opaque => {
|
||||
ResponseType::Opaque |
|
||||
ResponseType::OpaqueRedirect => {
|
||||
response.headers = Headers::new();
|
||||
response.status = None;
|
||||
response.body = ResponseBody::Empty;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue