Auto merge of #17521 - KiChjang:update-fetch, r=jdm

Update fetch methods

Includes updates to main fetch, scheme fetch, HTTP fetch, HTTP-redirect fetch, HTTP-network fetch, HTTP-network-or-cache fetch and CORS preflight fetch.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17521)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-08-18 18:39:02 -05:00 committed by GitHub
commit 9f64630eaa
9 changed files with 259 additions and 261 deletions

View file

@ -103,6 +103,7 @@ impl NetworkListener {
self.res_init = Some(ResponseInit { self.res_init = Some(ResponseInit {
url: metadata.final_url.clone(), url: metadata.final_url.clone(),
location_url: metadata.location_url.clone(),
headers: headers.clone().into_inner(), headers: headers.clone().into_inner(),
referrer: metadata.referrer.clone(), referrer: metadata.referrer.clone(),
}); });

View file

@ -10,14 +10,14 @@ use filemanager_thread::FileManager;
use http_loader::{HttpState, determine_request_referrer, http_fetch}; use http_loader::{HttpState, determine_request_referrer, http_fetch};
use http_loader::{set_default_accept, set_default_accept_language}; use http_loader::{set_default_accept, set_default_accept_language};
use hyper::{Error, Result as HyperResult}; use hyper::{Error, Result as HyperResult};
use hyper::header::{Accept, AcceptLanguage, ContentLanguage, ContentType}; use hyper::header::{Accept, AcceptLanguage, AccessControlExposeHeaders, ContentLanguage, ContentType};
use hyper::header::{Header, HeaderFormat, HeaderView, Headers, Referer as RefererHeader}; use hyper::header::{Header, HeaderFormat, HeaderView, Headers, Referer as RefererHeader};
use hyper::method::Method; use hyper::method::Method;
use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::mime::{Mime, SubLevel, TopLevel};
use hyper::status::StatusCode; use hyper::status::StatusCode;
use mime_guess::guess_mime_type; use mime_guess::guess_mime_type;
use net_traits::{FetchTaskTarget, NetworkError, ReferrerPolicy}; use net_traits::{FetchTaskTarget, NetworkError, ReferrerPolicy};
use net_traits::request::{Referrer, Request, RequestMode, ResponseTainting}; use net_traits::request::{CredentialsMode, Referrer, Request, RequestMode, ResponseTainting};
use net_traits::request::{Type, Origin, Window}; use net_traits::request::{Type, Origin, Window};
use net_traits::response::{Response, ResponseBody, ResponseType}; use net_traits::response::{Response, ResponseBody, ResponseType};
use servo_url::ServoUrl; use servo_url::ServoUrl;
@ -107,9 +107,8 @@ pub fn main_fetch(request: &mut Request,
// Step 2. // Step 2.
if request.local_urls_only { if request.local_urls_only {
match request.current_url().scheme() { if !matches!(request.current_url().scheme(), "about" | "blob" | "data" | "filesystem") {
"about" | "blob" | "data" | "filesystem" => (), // Ok, the URL is local. response = Some(Response::network_error(NetworkError::Internal("Non-local scheme".into())));
_ => response = Some(Response::network_error(NetworkError::Internal("Non-local scheme".into())))
} }
} }
@ -130,8 +129,7 @@ pub fn main_fetch(request: &mut Request,
// TODO: handle request's client's referrer policy. // TODO: handle request's client's referrer policy.
// Step 7. // Step 7.
let referrer_policy = request.referrer_policy.unwrap_or(ReferrerPolicy::NoReferrerWhenDowngrade); request.referrer_policy = request.referrer_policy.or(Some(ReferrerPolicy::NoReferrerWhenDowngrade));
request.referrer_policy = Some(referrer_policy);
// Step 8. // Step 8.
{ {
@ -147,7 +145,7 @@ pub fn main_fetch(request: &mut Request,
request.headers.remove::<RefererHeader>(); request.headers.remove::<RefererHeader>();
let current_url = request.current_url().clone(); let current_url = request.current_url().clone();
determine_request_referrer(&mut request.headers, determine_request_referrer(&mut request.headers,
referrer_policy, request.referrer_policy.unwrap(),
url, url,
current_url) current_url)
} }
@ -168,7 +166,7 @@ pub fn main_fetch(request: &mut Request,
// Not applicable: see fetch_async. // Not applicable: see fetch_async.
// Step 12. // Step 12.
let response = response.unwrap_or_else(|| { let mut response = response.unwrap_or_else(|| {
let current_url = request.current_url(); let current_url = request.current_url();
let same_origin = if let Origin::Origin(ref origin) = request.origin { let same_origin = if let Origin::Origin(ref origin) = request.origin {
*origin == current_url.origin() *origin == current_url.origin()
@ -178,35 +176,50 @@ pub fn main_fetch(request: &mut Request,
if (same_origin && !cors_flag ) || if (same_origin && !cors_flag ) ||
current_url.scheme() == "data" || current_url.scheme() == "data" ||
current_url.scheme() == "file" || current_url.scheme() == "file" || // FIXME: Fetch spec has already dropped filtering against file:
// and about: schemes, but CSS tests will break on loading Ahem
// since we load them through a file: URL.
current_url.scheme() == "about" || current_url.scheme() == "about" ||
request.mode == RequestMode::Navigate { request.mode == RequestMode::Navigate {
basic_fetch(request, cache, target, done_chan, context) // Substep 1.
request.response_tainting = ResponseTainting::Basic;
// Substep 2.
scheme_fetch(request, cache, target, done_chan, context)
} else if request.mode == RequestMode::SameOrigin { } else if request.mode == RequestMode::SameOrigin {
Response::network_error(NetworkError::Internal("Cross-origin response".into())) Response::network_error(NetworkError::Internal("Cross-origin response".into()))
} else if request.mode == RequestMode::NoCors { } else if request.mode == RequestMode::NoCors {
// Substep 1.
request.response_tainting = ResponseTainting::Opaque; request.response_tainting = ResponseTainting::Opaque;
basic_fetch(request, cache, target, done_chan, context)
// Substep 2.
scheme_fetch(request, cache, target, done_chan, context)
} else if !matches!(current_url.scheme(), "http" | "https") { } else if !matches!(current_url.scheme(), "http" | "https") {
Response::network_error(NetworkError::Internal("Non-http scheme".into())) Response::network_error(NetworkError::Internal("Non-http scheme".into()))
} else if request.use_cors_preflight || } else if request.use_cors_preflight ||
(request.unsafe_request && (request.unsafe_request &&
(!is_simple_method(&request.method) || (!is_cors_safelisted_method(&request.method) ||
request.headers.iter().any(|h| !is_simple_header(&h)))) { request.headers.iter().any(|h| !is_cors_safelisted_request_header(&h)))) {
// Substep 1.
request.response_tainting = ResponseTainting::CorsTainting; request.response_tainting = ResponseTainting::CorsTainting;
// Substep 2.
let response = http_fetch(request, cache, true, true, false, let response = http_fetch(request, cache, true, true, false,
target, done_chan, context); target, done_chan, context);
// Substep 3.
if response.is_network_error() { if response.is_network_error() {
// TODO clear cache entries using request // TODO clear cache entries using request
} }
// Substep 4.
response response
} else { } else {
// Substep 1.
request.response_tainting = ResponseTainting::CorsTainting; request.response_tainting = ResponseTainting::CorsTainting;
// Substep 2.
http_fetch(request, cache, true, false, false, target, done_chan, context) http_fetch(request, cache, true, false, false, target, done_chan, context)
} }
}); });
@ -217,9 +230,28 @@ pub fn main_fetch(request: &mut Request,
} }
// Step 14. // Step 14.
// We don't need to check whether response is a network error, let mut response = if !response.is_network_error() && response.internal_response.is_none() {
// given its type would not be `Default` in this case. // Substep 1.
let mut response = if response.response_type == ResponseType::Default { if request.response_tainting == ResponseTainting::CorsTainting {
// Subsubstep 1.
let header_names = response.headers.get::<AccessControlExposeHeaders>();
match header_names {
// Subsubstep 2.
Some(list) if request.credentials_mode != CredentialsMode::Include => {
if list.len() == 1 && list[0] == "*" {
response.cors_exposed_header_name_list =
response.headers.iter().map(|h| h.name().to_owned()).collect();
}
},
// Subsubstep 3.
Some(list) => {
response.cors_exposed_header_name_list = list.iter().map(|h| (**h).clone()).collect();
},
_ => (),
}
}
// Substep 2.
let response_type = match request.response_tainting { let response_type = match request.response_tainting {
ResponseTainting::Basic => ResponseType::Basic, ResponseTainting::Basic => ResponseType::Basic,
ResponseTainting::CorsTainting => ResponseType::Cors, ResponseTainting::CorsTainting => ResponseType::Cors,
@ -365,7 +397,7 @@ fn wait_for_response(response: &Response, target: Target, done_chan: &mut DoneCh
let body = response.body.lock().unwrap(); let body = response.body.lock().unwrap();
if let ResponseBody::Done(ref vec) = *body { if let ResponseBody::Done(ref vec) = *body {
// in case there was no channel to wait for, the body was // in case there was no channel to wait for, the body was
// obtained synchronously via basic_fetch for data/file/about/etc // obtained synchronously via scheme_fetch for data/file/about/etc
// We should still send the body across as a chunk // We should still send the body across as a chunk
target.process_response_chunk(vec.clone()); target.process_response_chunk(vec.clone());
} else { } else {
@ -374,8 +406,8 @@ fn wait_for_response(response: &Response, target: Target, done_chan: &mut DoneCh
} }
} }
/// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch) /// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch)
fn basic_fetch(request: &mut Request, fn scheme_fetch(request: &mut Request,
cache: &mut CorsCache, cache: &mut CorsCache,
target: Target, target: Target,
done_chan: &mut DoneChannel, done_chan: &mut DoneChannel,
@ -463,7 +495,7 @@ fn basic_fetch(request: &mut Request,
} }
/// https://fetch.spec.whatwg.org/#cors-safelisted-request-header /// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
pub fn is_simple_header(h: &HeaderView) -> bool { pub fn is_cors_safelisted_request_header(h: &HeaderView) -> bool {
if h.is::<ContentType>() { if h.is::<ContentType>() {
match h.value() { match h.value() {
Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) | Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) |
@ -477,7 +509,8 @@ pub fn is_simple_header(h: &HeaderView) -> bool {
} }
} }
pub fn is_simple_method(m: &Method) -> bool { /// https://fetch.spec.whatwg.org/#cors-safelisted-method
pub fn is_cors_safelisted_method(m: &Method) -> bool {
match *m { match *m {
Method::Get | Method::Head | Method::Post => true, Method::Get | Method::Head | Method::Post => true,
_ => false _ => false

View file

@ -9,7 +9,8 @@ use cookie_storage::CookieStorage;
use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest}; use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest};
use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent}; use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent};
use fetch::cors_cache::CorsCache; use fetch::cors_cache::CorsCache;
use fetch::methods::{Data, DoneChannel, FetchContext, Target, is_simple_header, is_simple_method, main_fetch}; use fetch::methods::{Data, DoneChannel, FetchContext, Target};
use fetch::methods::{is_cors_safelisted_request_header, is_cors_safelisted_method, main_fetch};
use flate2::read::{DeflateDecoder, GzDecoder}; use flate2::read::{DeflateDecoder, GzDecoder};
use hsts::HstsList; use hsts::HstsList;
use hyper::Error as HttpError; use hyper::Error as HttpError;
@ -38,6 +39,7 @@ use net_traits::request::{ResponseTainting, ServiceWorkersMode, Type};
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType}; use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
use resource_thread::AuthCache; use resource_thread::AuthCache;
use servo_url::{ImmutableOrigin, ServoUrl}; use servo_url::{ImmutableOrigin, ServoUrl};
use std::ascii::AsciiExt;
use std::collections::HashSet; use std::collections::HashSet;
use std::error::Error; use std::error::Error;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
@ -529,7 +531,7 @@ pub fn http_fetch(request: &mut Request,
// Substep 2 // Substep 2
if response.is_none() && request.is_subresource_request() && match request.origin { if response.is_none() && request.is_subresource_request() && match request.origin {
Origin::Origin(ref origin) if *origin == request.url().origin() => true, Origin::Origin(ref origin) => *origin == request.url().origin(),
_ => false, _ => false,
} { } {
// TODO (handle foreign fetch unimplemented) // TODO (handle foreign fetch unimplemented)
@ -559,24 +561,16 @@ pub fn http_fetch(request: &mut Request,
} }
// Step 4 // Step 4
let credentials = match request.credentials_mode {
CredentialsMode::Include => true,
CredentialsMode::CredentialsSameOrigin if request.response_tainting == ResponseTainting::Basic
=> true,
_ => false
};
// Step 5
if response.is_none() { if response.is_none() {
// Substep 1 // Substep 1
if cors_preflight_flag { if cors_preflight_flag {
let method_cache_match = cache.match_method(&*request, let method_cache_match = cache.match_method(&*request,
request.method.clone()); request.method.clone());
let method_mismatch = !method_cache_match && (!is_simple_method(&request.method) || let method_mismatch = !method_cache_match && (!is_cors_safelisted_method(&request.method) ||
request.use_cors_preflight); request.use_cors_preflight);
let header_mismatch = request.headers.iter().any(|view| let header_mismatch = request.headers.iter().any(|view|
!cache.match_header(&*request, view.name()) && !is_simple_header(&view) !cache.match_header(&*request, view.name()) && !is_cors_safelisted_request_header(&view)
); );
// Sub-substep 1 // Sub-substep 1
@ -611,81 +605,37 @@ pub fn http_fetch(request: &mut Request,
let mut response = response.unwrap(); let mut response = response.unwrap();
// Step 5 // Step 5
match response.actual_response().status { if response.actual_response().status.map_or(false, is_redirect_status) {
// Code 301, 302, 303, 307, 308 // Substep 1.
status if status.map_or(false, is_redirect_status) => { if response.actual_response().status.map_or(true, |s| s != StatusCode::SeeOther) {
response = match request.redirect_mode { // TODO: send RST_STREAM frame
RedirectMode::Error => Response::network_error(NetworkError::Internal("Redirect mode error".into())),
RedirectMode::Manual => {
response.to_filtered(ResponseType::OpaqueRedirect)
},
RedirectMode::Follow => {
// set back to default
response.return_internal = true;
http_redirect_fetch(request, cache, response,
cors_flag, target, done_chan, context)
}
}
},
// Code 401
Some(StatusCode::Unauthorized) => {
// Step 1
// FIXME: Figure out what to do with request window objects
if cors_flag || !credentials {
return response;
}
// Step 2
// TODO: Spec says requires testing on multiple WWW-Authenticate headers
// Step 3
if !request.use_url_credentials || authentication_fetch_flag {
// TODO: Prompt the user for username and password from the window
// Wrong, but will have to do until we are able to prompt the user
// otherwise this creates an infinite loop
// We basically pretend that the user declined to enter credentials
return response;
}
// Step 4
return http_fetch(request, cache, cors_flag, cors_preflight_flag,
true, target, done_chan, context);
} }
// Code 407 // Substep 2-3.
Some(StatusCode::ProxyAuthenticationRequired) => { let location = response.actual_response().headers.get::<Location>().map(
// Step 1 |l| ServoUrl::parse_with_base(response.actual_response().url(), l)
// TODO: Figure out what to do with request window objects .map_err(|err| err.description().into()));
// Step 2 // Substep 4.
// TODO: Spec says requires testing on Proxy-Authenticate headers response.actual_response_mut().location_url = location;
// Step 3 // Substep 5.
// TODO: Prompt the user for proxy authentication credentials response = match request.redirect_mode {
// Wrong, but will have to do until we are able to prompt the user RedirectMode::Error => Response::network_error(NetworkError::Internal("Redirect mode error".into())),
// otherwise this creates an infinite loop RedirectMode::Manual => {
// We basically pretend that the user declined to enter credentials response.to_filtered(ResponseType::OpaqueRedirect)
return response; },
RedirectMode::Follow => {
// Step 4 // set back to default
// return http_fetch(request, cache, response.return_internal = true;
// cors_flag, cors_preflight_flag, http_redirect_fetch(request, cache, response,
// authentication_fetch_flag, target, cors_flag, target, done_chan, context)
// done_chan, context); }
} };
_ => { }
} }
// Step 6
if authentication_fetch_flag {
// TODO: Create authentication entry for this request
}
// set back to default // set back to default
response.return_internal = true; response.return_internal = true;
// Step 7 // Step 6
response response
} }
@ -701,29 +651,20 @@ pub fn http_redirect_fetch(request: &mut Request,
// Step 1 // Step 1
assert!(response.return_internal); assert!(response.return_internal);
// Step 2 let location_url = response.actual_response().location_url.clone();
if !response.actual_response().headers.has::<Location>() {
return response;
}
// Step 3
let location = match response.actual_response().headers.get::<Location>() {
Some(&Location(ref location)) => location.clone(),
_ => return Response::network_error(NetworkError::Internal("Location header parsing failure".into()))
};
let response_url = response.actual_response().url().unwrap();
let location_url = response_url.join(&*location);
let location_url = match location_url { let location_url = match location_url {
Ok(url) => url, // Step 2
_ => return Response::network_error(NetworkError::Internal("Location URL parsing failure".into())) None => return response,
// Step 3
Some(Err(err)) =>
return Response::network_error(
NetworkError::Internal("Location URL parse failure: ".to_owned() + &err)),
// Step 4
Some(Ok(ref url)) if !matches!(url.scheme(), "http" | "https") =>
return Response::network_error(NetworkError::Internal("Location URL not an HTTP(S) scheme".into())),
Some(Ok(url)) => url,
}; };
// Step 4
match location_url.scheme() {
"http" | "https" => { },
_ => return Response::network_error(NetworkError::Internal("Not an HTTP(S) Scheme".into()))
}
// Step 5 // Step 5
if request.redirect_count >= 20 { if request.redirect_count >= 20 {
return Response::network_error(NetworkError::Internal("Too many redirects".into())); return Response::network_error(NetworkError::Internal("Too many redirects".into()));
@ -733,7 +674,8 @@ pub fn http_redirect_fetch(request: &mut Request,
request.redirect_count += 1; request.redirect_count += 1;
// Step 7 // Step 7
let same_origin = location_url.origin()== request.current_url().origin(); // FIXME: Correctly use request's origin
let same_origin = location_url.origin() == request.current_url().origin();
let has_credentials = has_credentials(&location_url); let has_credentials = has_credentials(&location_url);
if request.mode == RequestMode::CorsMode && !same_origin && has_credentials { if request.mode == RequestMode::CorsMode && !same_origin && has_credentials {
@ -746,28 +688,38 @@ pub fn http_redirect_fetch(request: &mut Request,
} }
// Step 9 // Step 9
if response.actual_response().status.map_or(true, |s| s != StatusCode::SeeOther) &&
request.body.as_ref().map_or(false, |b| b.is_empty()) {
return Response::network_error(NetworkError::Internal("Request body is not done".into()));
}
// Step 10
if cors_flag && !same_origin { if cors_flag && !same_origin {
request.origin = Origin::Origin(ImmutableOrigin::new_opaque()); request.origin = Origin::Origin(ImmutableOrigin::new_opaque());
} }
// Step 10 // Step 11
let status_code = response.actual_response().status.unwrap(); if response.actual_response().status.map_or(false, |code|
if ((status_code == StatusCode::MovedPermanently || status_code == StatusCode::Found) && ((code == StatusCode::MovedPermanently || code == StatusCode::Found) && request.method == Method::Post) ||
request.method == Method::Post) || code == StatusCode::SeeOther) {
status_code == StatusCode::SeeOther {
request.method = Method::Get; request.method = Method::Get;
request.body = None; request.body = None;
} }
// Step 11
request.url_list.push(location_url);
// Step 12 // Step 12
// TODO implement referrer policy if let Some(_) = request.body {
// TODO: extract request's body's source
let recursive_flag = request.redirect_mode != RedirectMode::Manual; }
// Step 13 // Step 13
request.url_list.push(location_url);
// Step 14
// TODO implement referrer policy
// Step 15
let recursive_flag = request.redirect_mode != RedirectMode::Manual;
main_fetch(request, cache, cors_flag, recursive_flag, target, done_chan, context) main_fetch(request, cache, cors_flag, recursive_flag, target, done_chan, context)
} }
@ -826,9 +778,11 @@ fn http_network_or_cache_fetch(request: &mut Request,
// Step 8 // Step 8
if let Some(content_length_value) = content_length_value { if let Some(content_length_value) = content_length_value {
http_request.headers.set(ContentLength(content_length_value)); http_request.headers.set(ContentLength(content_length_value));
if http_request.keep_alive {
// Step 9 TODO: needs request's client object
}
} }
// Step 9 TODO: needs request's client object
// Step 10 // Step 10
match http_request.referrer { match http_request.referrer {
@ -940,7 +894,7 @@ fn http_network_or_cache_fetch(request: &mut Request,
let mut response: Option<Response> = None; let mut response: Option<Response> = None;
// Step 20 // Step 20
let mut revalidation_needed = false; let mut revalidating_flag = false;
// Step 21 // Step 21
// TODO have a HTTP cache to check for a completed response // TODO have a HTTP cache to check for a completed response
@ -952,7 +906,7 @@ fn http_network_or_cache_fetch(request: &mut Request,
// Substep 3 // Substep 3
if let Some(ref response) = response { if let Some(ref response) = response {
revalidation_needed = response_needs_revalidation(&response); revalidating_flag = response_needs_revalidation(&response);
}; };
// Substep 4 // Substep 4
@ -962,7 +916,7 @@ fn http_network_or_cache_fetch(request: &mut Request,
// response = http_request // response = http_request
} }
if revalidation_needed { if revalidating_flag {
// Substep 5 // Substep 5
// TODO set If-None-Match and If-Modified-Since according to cached // TODO set If-None-Match and If-Modified-Since according to cached
// response headers. // response headers.
@ -984,82 +938,76 @@ fn http_network_or_cache_fetch(request: &mut Request,
// Substep 2 // Substep 2
let forward_response = http_network_fetch(http_request, credentials_flag, let forward_response = http_network_fetch(http_request, credentials_flag,
done_chan, context); done_chan, context);
match forward_response.raw_status { // Substep 3
// Substep 3 if let Some((200...399, _)) = forward_response.raw_status {
Some((200...303, _)) | if !http_request.method.safe() {
Some((305...399, _)) => { // TODO Invalidate HTTP cache response
if !http_request.method.safe() { }
// TODO Invalidate HTTP cache response }
} // Substep 4
}, if revalidating_flag && forward_response.status.map_or(false, |s| s == StatusCode::NotModified) {
// Substep 4 // TODO update forward_response headers with cached response headers
Some((304, _)) => {
if revalidation_needed {
// TODO update forward_response headers with cached response
// headers
}
},
_ => {}
} }
// Substep 5 // Substep 5
if response.is_none() { if response.is_none() {
// Subsubstep 1
response = Some(forward_response); response = Some(forward_response);
// Subsubstep 2
// TODO: store http_request and forward_response in cache
} }
} }
let response = response.unwrap(); let mut response = response.unwrap();
match response.status { // Step 23
Some(StatusCode::Unauthorized) => { // FIXME: Figure out what to do with request window objects
// Step 23 if let (Some(StatusCode::Unauthorized), false, true) = (response.status, cors_flag, credentials_flag) {
// FIXME: Figure out what to do with request window objects // Substep 1
if cors_flag && !credentials_flag { // TODO: Spec says requires testing on multiple WWW-Authenticate headers
return response;
}
// Substep 1 // Substep 2
// TODO: Spec says requires testing on multiple WWW-Authenticate headers if http_request.body.is_some() {
// TODO Implement body source
}
// Substep 2 // Substep 3
if http_request.body.is_some() { if !http_request.use_url_credentials || authentication_fetch_flag {
// TODO Implement body source // FIXME: Prompt the user for username and password from the window
}
// Substep 3
if !http_request.use_url_credentials || authentication_fetch_flag {
// TODO: Prompt the user for username and password from the window
// Wrong, but will have to do until we are able to prompt the user
// otherwise this creates an infinite loop
// We basically pretend that the user declined to enter credentials
return response;
}
// Substep 4
return http_network_or_cache_fetch(http_request,
true /* authentication flag */,
cors_flag, done_chan, context);
},
Some(StatusCode::ProxyAuthenticationRequired) => {
// Step 24
// Step 1
// TODO: Figure out what to do with request window objects
// Step 2
// TODO: Spec says requires testing on Proxy-Authenticate headers
// Step 3
// TODO: Prompt the user for proxy authentication credentials
// Wrong, but will have to do until we are able to prompt the user // Wrong, but will have to do until we are able to prompt the user
// otherwise this creates an infinite loop // otherwise this creates an infinite loop
// We basically pretend that the user declined to enter credentials // We basically pretend that the user declined to enter credentials
return response; return response;
}
// Step 4 // Substep 4
// return http_network_or_cache_fetch(request, authentication_fetch_flag, response = http_network_or_cache_fetch(http_request,
// cors_flag, done_chan, context); true /* authentication flag */,
}, cors_flag, done_chan, context);
_ => {} }
// Step 24
if let Some(StatusCode::ProxyAuthenticationRequired) = response.status {
// Step 1
if request_has_no_window {
return Response::network_error(NetworkError::Internal("Can't find Window object".into()));
}
// Step 2
// TODO: Spec says requires testing on Proxy-Authenticate headers
// Step 3
// FIXME: Prompt the user for proxy authentication credentials
// Wrong, but will have to do until we are able to prompt the user
// otherwise this creates an infinite loop
// We basically pretend that the user declined to enter credentials
return response;
// Step 4
// return http_network_or_cache_fetch(request, authentication_fetch_flag,
// cors_flag, done_chan, context);
} }
// Step 25 // Step 25
@ -1077,8 +1025,6 @@ fn http_network_fetch(request: &Request,
done_chan: &mut DoneChannel, done_chan: &mut DoneChannel,
context: &FetchContext) context: &FetchContext)
-> Response { -> Response {
// TODO: Implement HTTP network fetch spec
// Step 1 // Step 1
// nothing to do here, since credentials_flag is already a boolean // nothing to do here, since credentials_flag is already a boolean
@ -1089,6 +1035,9 @@ fn http_network_fetch(request: &Request,
// TODO be able to tell if the connection is a failure // TODO be able to tell if the connection is a failure
// Step 4 // Step 4
// TODO: check whether the connection is HTTP/2
// Step 5
let url = request.current_url(); let url = request.current_url();
let request_id = context.devtools_chan.as_ref().map(|_| { let request_id = context.devtools_chan.as_ref().map(|_| {
@ -1203,10 +1152,10 @@ fn http_network_fetch(request: &Request,
// TODO Read request // TODO Read request
// Step 5-9 // Step 6-11
// (needs stream bodies) // (needs stream bodies)
// Step 10 // Step 12
// TODO when https://bugzilla.mozilla.org/show_bug.cgi?id=1030660 // TODO when https://bugzilla.mozilla.org/show_bug.cgi?id=1030660
// is resolved, this step will become uneccesary // is resolved, this step will become uneccesary
// TODO this step // TODO this step
@ -1218,24 +1167,22 @@ fn http_network_fetch(request: &Request,
} }
}; };
// Step 11 // Step 13
// TODO this step isn't possible yet (CSP) // TODO this step isn't possible yet (CSP)
// Step 12 // Step 14
if response.is_network_error() && request.cache_mode == CacheMode::NoStore { if !response.is_network_error() && request.cache_mode != CacheMode::NoStore {
// TODO update response in the HTTP cache for request // TODO update response in the HTTP cache for request
} }
// TODO this step isn't possible yet // TODO this step isn't possible yet
// Step 13 // Step 15
// Step 14.
if credentials_flag { if credentials_flag {
set_cookies_from_headers(&url, &response.headers, &context.state.cookie_jar); set_cookies_from_headers(&url, &response.headers, &context.state.cookie_jar);
} }
// TODO these steps // TODO these steps
// Step 15 // Step 16
// Substep 1 // Substep 1
// Substep 2 // Substep 2
// Sub-substep 1 // Sub-substep 1
@ -1259,6 +1206,7 @@ fn cors_preflight_fetch(request: &Request,
preflight.initiator = request.initiator.clone(); preflight.initiator = request.initiator.clone();
preflight.type_ = request.type_.clone(); preflight.type_ = request.type_.clone();
preflight.destination = request.destination.clone(); preflight.destination = request.destination.clone();
preflight.origin = request.origin.clone();
preflight.referrer = request.referrer.clone(); preflight.referrer = request.referrer.clone();
preflight.referrer_policy = request.referrer_policy; preflight.referrer_policy = request.referrer_policy;
@ -1266,86 +1214,107 @@ fn cors_preflight_fetch(request: &Request,
preflight.headers.set::<AccessControlRequestMethod>( preflight.headers.set::<AccessControlRequestMethod>(
AccessControlRequestMethod(request.method.clone())); AccessControlRequestMethod(request.method.clone()));
// Step 3, 4 // Step 3
let mut value = request.headers let mut headers = request.headers
.iter() .iter()
.filter(|view| !is_simple_header(view)) .filter(|view| !is_cors_safelisted_request_header(view))
.map(|view| UniCase(view.name().to_owned())) .map(|view| UniCase(view.name().to_ascii_lowercase().to_owned()))
.collect::<Vec<UniCase<String>>>(); .collect::<Vec<UniCase<String>>>();
value.sort(); headers.sort();
// Step 4
if !headers.is_empty() {
preflight.headers.set::<AccessControlRequestHeaders>(AccessControlRequestHeaders(headers));
}
// Step 5 // Step 5
preflight.headers.set::<AccessControlRequestHeaders>(
AccessControlRequestHeaders(value));
// Step 6
let response = http_network_or_cache_fetch(&mut preflight, false, false, &mut None, context); let response = http_network_or_cache_fetch(&mut preflight, false, false, &mut None, context);
// Step 7 // Step 6
if cors_check(&request, &response).is_ok() && if cors_check(&request, &response).is_ok() &&
response.status.map_or(false, |status| status.is_success()) { response.status.map_or(false, |status| status.is_success()) {
// Substep 1 // Substep 1, 2
let mut methods = if response.headers.has::<AccessControlAllowMethods>() { let mut methods = if response.headers.has::<AccessControlAllowMethods>() {
match response.headers.get::<AccessControlAllowMethods>() { match response.headers.get::<AccessControlAllowMethods>() {
Some(&AccessControlAllowMethods(ref m)) => m.clone(), Some(&AccessControlAllowMethods(ref m)) => m.clone(),
// Substep 3 // Substep 4
None => return Response::network_error(NetworkError::Internal("CORS ACAM check failed".into())) None => return Response::network_error(NetworkError::Internal("CORS ACAM check failed".into()))
} }
} else { } else {
vec![] vec![]
}; };
// Substep 2 // Substep 3
let header_names = if response.headers.has::<AccessControlAllowHeaders>() { let header_names = if response.headers.has::<AccessControlAllowHeaders>() {
match response.headers.get::<AccessControlAllowHeaders>() { match response.headers.get::<AccessControlAllowHeaders>() {
Some(&AccessControlAllowHeaders(ref hn)) => hn.clone(), Some(&AccessControlAllowHeaders(ref hn)) => hn.clone(),
// Substep 3 // Substep 4
None => return Response::network_error(NetworkError::Internal("CORS ACAH check failed".into())) None => return Response::network_error(NetworkError::Internal("CORS ACAH check failed".into()))
} }
} else { } else {
vec![] vec![]
}; };
// Substep 4 // Substep 5
if (methods.iter().any(|m| m.as_ref() == "*") ||
header_names.iter().any(|hn| &**hn == "*")) &&
request.credentials_mode == CredentialsMode::Include {
return Response::network_error(
NetworkError::Internal("CORS ACAH/ACAM and request credentials mode mismatch".into()));
}
// Substep 6
if methods.is_empty() && request.use_cors_preflight { if methods.is_empty() && request.use_cors_preflight {
methods = vec![request.method.clone()]; methods = vec![request.method.clone()];
} }
// Substep 5 // Substep 7
debug!("CORS check: Allowed methods: {:?}, current method: {:?}", debug!("CORS check: Allowed methods: {:?}, current method: {:?}",
methods, request.method); methods, request.method);
if methods.iter().all(|method| *method != request.method) && if methods.iter().all(|method| *method != request.method) &&
!is_simple_method(&request.method) { !is_cors_safelisted_method(&request.method) &&
methods.iter().all(|m| m.as_ref() != "*") {
return Response::network_error(NetworkError::Internal("CORS method check failed".into())); return Response::network_error(NetworkError::Internal("CORS method check failed".into()));
} }
// Substep 6 // Substep 8
if request.headers.iter().any(
|header| header.name() == "authorization" &&
header_names.iter().all(|hn| *hn != UniCase(header.name()))) {
return Response::network_error(NetworkError::Internal("CORS authorization check failed".into()));
}
// Substep 9
debug!("CORS check: Allowed headers: {:?}, current headers: {:?}", header_names, request.headers); debug!("CORS check: Allowed headers: {:?}, current headers: {:?}", header_names, request.headers);
let set: HashSet<&UniCase<String>> = HashSet::from_iter(header_names.iter()); let set: HashSet<&UniCase<String>> = HashSet::from_iter(header_names.iter());
if request.headers.iter().any(|ref hv| !set.contains(&UniCase(hv.name().to_owned())) && !is_simple_header(hv)) { if request.headers.iter().any(
|ref hv| !set.contains(&UniCase(hv.name().to_owned())) && !is_cors_safelisted_request_header(hv)) {
return Response::network_error(NetworkError::Internal("CORS headers check failed".into())); return Response::network_error(NetworkError::Internal("CORS headers check failed".into()));
} }
// Substep 7, 8 // Substep 10, 11
let max_age = response.headers.get::<AccessControlMaxAge>().map(|acma| acma.0).unwrap_or(0); let max_age = response.headers.get::<AccessControlMaxAge>().map(|acma| acma.0).unwrap_or(0);
// TODO: Substep 9 - Need to define what an imposed limit on max-age is // Substep 12
// TODO: Need to define what an imposed limit on max-age is
// Substep 11, 12 // Substep 13 ignored, we do have a CORS cache
// Substep 14, 15
for method in &methods { for method in &methods {
cache.match_method_and_update(&*request, method.clone(), max_age); cache.match_method_and_update(&*request, method.clone(), max_age);
} }
// Substep 13, 14 // Substep 16, 17
for header_name in &header_names { for header_name in &header_names {
cache.match_header_and_update(&*request, &*header_name, max_age); cache.match_header_and_update(&*request, &*header_name, max_age);
} }
// Substep 15 // Substep 18
return response; return response;
} }
// Step 8 // Step 7
Response::network_error(NetworkError::Internal("CORS check failed".into())) Response::network_error(NetworkError::Internal("CORS check failed".into()))
} }

View file

@ -352,7 +352,7 @@ fn main_fetch(url: ServoUrl,
// doesn't need to be filtered at all. // doesn't need to be filtered at all.
// Step 12.2. // Step 12.2.
basic_fetch(&url, origin, &mut headers, http_state) scheme_fetch(&url, origin, &mut headers, http_state)
}); });
// Step 13. // Step 13.
@ -386,8 +386,8 @@ fn main_fetch(url: ServoUrl,
response response
} }
// https://fetch.spec.whatwg.org/#concept-basic-fetch // https://fetch.spec.whatwg.org/#concept-scheme-fetch
fn basic_fetch(url: &ServoUrl, fn scheme_fetch(url: &ServoUrl,
origin: String, origin: String,
headers: &mut Headers, headers: &mut Headers,
http_state: &HttpState) http_state: &HttpState)

View file

@ -411,6 +411,9 @@ pub struct Metadata {
/// Final URL after redirects. /// Final URL after redirects.
pub final_url: ServoUrl, pub final_url: ServoUrl,
/// Location URL from the response headers.
pub location_url: Option<Result<ServoUrl, String>>,
#[ignore_heap_size_of = "Defined in hyper"] #[ignore_heap_size_of = "Defined in hyper"]
/// MIME type / subtype. /// MIME type / subtype.
pub content_type: Option<Serde<ContentType>>, pub content_type: Option<Serde<ContentType>>,
@ -440,6 +443,7 @@ impl Metadata {
pub fn default(url: ServoUrl) -> Self { pub fn default(url: ServoUrl) -> Self {
Metadata { Metadata {
final_url: url, final_url: url,
location_url: None,
content_type: None, content_type: None,
charset: None, charset: None,
headers: None, headers: None,

View file

@ -220,7 +220,7 @@ pub struct Request {
// TODO: target browsing context // TODO: target browsing context
/// https://fetch.spec.whatwg.org/#request-keepalive-flag /// https://fetch.spec.whatwg.org/#request-keepalive-flag
pub keep_alive: bool, pub keep_alive: bool,
// https://fetch.spec.whatwg.org/#request-service-workers-mode /// https://fetch.spec.whatwg.org/#request-service-workers-mode
pub service_workers_mode: ServiceWorkersMode, pub service_workers_mode: ServiceWorkersMode,
/// https://fetch.spec.whatwg.org/#concept-request-initiator /// https://fetch.spec.whatwg.org/#concept-request-initiator
pub initiator: Initiator, pub initiator: Initiator,

View file

@ -82,6 +82,7 @@ pub struct ResponseInit {
#[ignore_heap_size_of = "Defined in hyper"] #[ignore_heap_size_of = "Defined in hyper"]
pub headers: Headers, pub headers: Headers,
pub referrer: Option<ServoUrl>, pub referrer: Option<ServoUrl>,
pub location_url: Option<Result<ServoUrl, String>>,
} }
/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec /// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
@ -102,12 +103,16 @@ pub struct Response {
pub cache_state: CacheState, pub cache_state: CacheState,
pub https_state: HttpsState, pub https_state: HttpsState,
pub referrer: Option<ServoUrl>, pub referrer: Option<ServoUrl>,
pub referrer_policy: Option<ReferrerPolicy>,
/// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list)
pub cors_exposed_header_name_list: Vec<String>,
/// [Location URL](https://fetch.spec.whatwg.org/#concept-response-location-url)
pub location_url: Option<Result<ServoUrl, String>>,
/// [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<Box<Response>>,
/// whether or not to try to return the internal_response when asked for actual_response /// whether or not to try to return the internal_response when asked for actual_response
pub return_internal: bool, pub return_internal: bool,
pub referrer_policy: Option<ReferrerPolicy>,
} }
impl Response { impl Response {
@ -125,6 +130,8 @@ impl Response {
https_state: HttpsState::None, https_state: HttpsState::None,
referrer: None, referrer: None,
referrer_policy: None, referrer_policy: None,
cors_exposed_header_name_list: vec![],
location_url: None,
internal_response: None, internal_response: None,
return_internal: true, return_internal: true,
} }
@ -132,6 +139,7 @@ impl Response {
pub fn from_init(init: ResponseInit) -> Response { pub fn from_init(init: ResponseInit) -> Response {
let mut res = Response::new(init.url); let mut res = Response::new(init.url);
res.location_url = init.location_url;
res.headers = init.headers; res.headers = init.headers;
res.referrer = init.referrer; res.referrer = init.referrer;
res res
@ -151,6 +159,8 @@ impl Response {
https_state: HttpsState::None, https_state: HttpsState::None,
referrer: None, referrer: None,
referrer_policy: None, referrer_policy: None,
cors_exposed_header_name_list: vec![],
location_url: None,
internal_response: None, internal_response: None,
return_internal: true, return_internal: true,
} }
@ -279,6 +289,7 @@ impl Response {
Some(&ContentType(ref mime)) => Some(mime), Some(&ContentType(ref mime)) => Some(mime),
None => None, None => None,
}); });
metadata.location_url = response.location_url.clone();
metadata.headers = Some(Serde(response.headers.clone())); metadata.headers = Some(Serde(response.headers.clone()));
metadata.status = response.raw_status.clone(); metadata.status = response.raw_status.clone();
metadata.https_state = response.https_state; metadata.https_state = response.https_state;

View file

@ -208,7 +208,7 @@ fn test_cors_preflight_fetch() {
let handler = move |request: HyperRequest, mut response: HyperResponse| { let handler = move |request: HyperRequest, mut response: HyperResponse| {
if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 {
assert!(request.headers.has::<AccessControlRequestMethod>()); assert!(request.headers.has::<AccessControlRequestMethod>());
assert!(request.headers.has::<AccessControlRequestHeaders>()); assert!(!request.headers.has::<AccessControlRequestHeaders>());
assert!(!request.headers.get::<HyperReferer>().unwrap().contains("a.html")); assert!(!request.headers.get::<HyperReferer>().unwrap().contains("a.html"));
response.headers_mut().set(AccessControlAllowOrigin::Any); response.headers_mut().set(AccessControlAllowOrigin::Any);
response.headers_mut().set(AccessControlAllowCredentials); response.headers_mut().set(AccessControlAllowCredentials);
@ -247,7 +247,7 @@ fn test_cors_preflight_cache_fetch() {
let handler = move |request: HyperRequest, mut response: HyperResponse| { let handler = move |request: HyperRequest, mut response: HyperResponse| {
if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 {
assert!(request.headers.has::<AccessControlRequestMethod>()); assert!(request.headers.has::<AccessControlRequestMethod>());
assert!(request.headers.has::<AccessControlRequestHeaders>()); assert!(!request.headers.has::<AccessControlRequestHeaders>());
response.headers_mut().set(AccessControlAllowOrigin::Any); response.headers_mut().set(AccessControlAllowOrigin::Any);
response.headers_mut().set(AccessControlAllowCredentials); response.headers_mut().set(AccessControlAllowCredentials);
response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get])); response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get]));
@ -297,7 +297,7 @@ fn test_cors_preflight_fetch_network_error() {
let handler = move |request: HyperRequest, mut response: HyperResponse| { let handler = move |request: HyperRequest, mut response: HyperResponse| {
if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 {
assert!(request.headers.has::<AccessControlRequestMethod>()); assert!(request.headers.has::<AccessControlRequestMethod>());
assert!(request.headers.has::<AccessControlRequestHeaders>()); assert!(!request.headers.has::<AccessControlRequestHeaders>());
response.headers_mut().set(AccessControlAllowOrigin::Any); response.headers_mut().set(AccessControlAllowOrigin::Any);
response.headers_mut().set(AccessControlAllowCredentials); response.headers_mut().set(AccessControlAllowCredentials);
response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get])); response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get]));

View file

@ -1,11 +1,5 @@
[cors-preflight.any.html] [cors-preflight.any.html]
type: testharness type: testharness
[CORS [DELETE\], server allows]
expected: FAIL
[CORS [PUT\], server allows]
expected: FAIL
[CORS [PATCH\], server allows] [CORS [PATCH\], server allows]
expected: FAIL expected: FAIL
@ -27,18 +21,8 @@
[CORS [PUT\] [several headers\], server allows] [CORS [PUT\] [several headers\], server allows]
expected: FAIL expected: FAIL
[CORS [PUT\] [only safe headers\], server allows]
expected: FAIL
[cors-preflight.any.worker.html] [cors-preflight.any.worker.html]
type: testharness type: testharness
[CORS [DELETE\], server allows]
expected: FAIL
[CORS [PUT\], server allows]
expected: FAIL
[CORS [PATCH\], server allows] [CORS [PATCH\], server allows]
expected: FAIL expected: FAIL
@ -59,7 +43,3 @@
[CORS [PUT\] [several headers\], server allows] [CORS [PUT\] [several headers\], server allows]
expected: FAIL expected: FAIL
[CORS [PUT\] [only safe headers\], server allows]
expected: FAIL