mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Bring http_network_or_cache_fetch
closer to the spec (#33531)
* Document "fetch" method Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Bring http_network_or_cache_fetch closer to the spec Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * fix test-tidy errors Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Move all code into http_loader.rs Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Don't panic if hyper/servo disagree about valid origins Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Add "otherwise" to spec comment Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Convert FIXME's to TODOs when appropriate Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Remove TODO about No-Store cache directive Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Remove indentation from multiline spec comments Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Add fetch assertions This is from a spec update where assertions about requests origin not being client were added. Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Add note about serializing headers Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Add TODO about partitioning http cache Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Convert FIXME to TODO in script/ Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Link to relevant issue for TODO comments Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> --------- Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
parent
fc0d4d8157
commit
457d8a8a5c
3 changed files with 320 additions and 188 deletions
|
@ -22,7 +22,7 @@ use headers::{
|
|||
AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowMethods,
|
||||
AccessControlAllowOrigin, AccessControlMaxAge, AccessControlRequestHeaders,
|
||||
AccessControlRequestMethod, Authorization, CacheControl, ContentLength, HeaderMapExt,
|
||||
IfModifiedSince, LastModified, Origin as HyperOrigin, Pragma, Referer, UserAgent,
|
||||
IfModifiedSince, LastModified, Pragma, Referer, UserAgent,
|
||||
};
|
||||
use http::header::{
|
||||
self, HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LOCATION,
|
||||
|
@ -1082,19 +1082,6 @@ pub async fn http_redirect_fetch(
|
|||
fetch_response
|
||||
}
|
||||
|
||||
fn try_immutable_origin_to_hyper_origin(url_origin: &ImmutableOrigin) -> Option<HyperOrigin> {
|
||||
match *url_origin {
|
||||
ImmutableOrigin::Opaque(_) => Some(HyperOrigin::NULL),
|
||||
ImmutableOrigin::Tuple(ref scheme, ref host, ref port) => {
|
||||
let port = match (scheme.as_ref(), port) {
|
||||
("http", 80) | ("https", 443) => None,
|
||||
_ => Some(*port),
|
||||
};
|
||||
HyperOrigin::try_from_parts(scheme, &host.to_string(), port).ok()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch)
|
||||
#[async_recursion]
|
||||
async fn http_network_or_cache_fetch(
|
||||
|
@ -1104,25 +1091,32 @@ async fn http_network_or_cache_fetch(
|
|||
done_chan: &mut DoneChannel,
|
||||
context: &FetchContext,
|
||||
) -> Response {
|
||||
// Step 3: Let httpRequest be null.
|
||||
// Step 1. Let request be fetchParams’s request.
|
||||
// NOTE: We get request as an argument (Fetchparams are not implemented, see #33616)
|
||||
|
||||
// Step 3. Let httpRequest be null.
|
||||
let mut http_request;
|
||||
|
||||
// Step 4: Let response be null.
|
||||
// Step 4. Let response be null.
|
||||
let mut response: Option<Response> = None;
|
||||
|
||||
// Step 7: Let the revalidatingFlag be unset.
|
||||
// Step 7. Let the revalidatingFlag be unset.
|
||||
let mut revalidating_flag = false;
|
||||
|
||||
// TODO(#33616): Step 8. Run these steps, but abort when fetchParams is canceled:
|
||||
// Step 8.1: If request’s window is "no-window" and request’s redirect mode is "error", then set
|
||||
// httpFetchParams to fetchParams and httpRequest to request.
|
||||
let request_has_no_window = request.window == RequestWindow::NoWindow;
|
||||
|
||||
let http_request = if request_has_no_window && request.redirect_mode == RedirectMode::Error {
|
||||
request
|
||||
} else {
|
||||
}
|
||||
// Step 8.2 Otherwise:
|
||||
else {
|
||||
// Step 8.2.1: Set httpRequest to a clone of request.
|
||||
http_request = request.clone();
|
||||
|
||||
// TODO(#33616): Step 8.2.2-8.2.3
|
||||
&mut http_request
|
||||
};
|
||||
|
||||
|
@ -1141,34 +1135,47 @@ async fn http_network_or_cache_fetch(
|
|||
|
||||
// Step 8.4: If Cross-Origin-Embedder-Policy allows credentials with request returns false, then
|
||||
// set includeCredentials to false.
|
||||
// TODO: Requires request's client object
|
||||
// TODO(#33616): Requires request's client object
|
||||
|
||||
// Step 8.5: Let contentLength be httpRequest’s body’s length, if httpRequest’s body is
|
||||
// non-null; otherwise null.
|
||||
let content_length_value = match http_request.body {
|
||||
Some(ref http_request_body) => http_request_body.len().map(|size| size as u64),
|
||||
// Step 8.7: If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`, then
|
||||
// set contentLengthHeaderValue to `0`.
|
||||
None => match http_request.method {
|
||||
Method::POST | Method::PUT => Some(0),
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
// Step 8.5 Let contentLength be httpRequest’s body’s length, if httpRequest’s body is non-null;
|
||||
// otherwise null.
|
||||
let content_length = http_request
|
||||
.body
|
||||
.as_ref()
|
||||
.map(|body| body.len().map(|size| size as u64))
|
||||
.flatten();
|
||||
|
||||
// Step 8.9: If contentLengthHeaderValue is non-null, then append (`Content-Length`,
|
||||
// contentLengthHeaderValue) to httpRequest’s header list.
|
||||
if let Some(content_length_value) = content_length_value {
|
||||
http_request
|
||||
.headers
|
||||
.typed_insert(ContentLength(content_length_value));
|
||||
// Step 8.6 Let contentLengthHeaderValue be null.
|
||||
let mut content_length_header_value = None;
|
||||
|
||||
// Step 8.7 If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`,
|
||||
// then set contentLengthHeaderValue to `0`.
|
||||
if http_request.body.is_none() && matches!(http_request.method, Method::POST | Method::PUT) {
|
||||
content_length_header_value = Some(0);
|
||||
}
|
||||
|
||||
// Step 8.10: If contentLength is non-null and httpRequest’s keepalive is true, then:
|
||||
// TODO Keepalive requires request's client object's fetch group
|
||||
// Step 8.8 If contentLength is non-null, then set contentLengthHeaderValue to contentLength,
|
||||
// serialized and isomorphic encoded.
|
||||
// NOTE: The header will later be serialized using HeaderMap::typed_insert
|
||||
if let Some(content_length) = content_length {
|
||||
content_length_header_value = Some(content_length);
|
||||
};
|
||||
|
||||
// Step 8.9 If contentLengthHeaderValue is non-null, then append (`Content-Length`, contentLengthHeaderValue)
|
||||
// to httpRequest’s header list.
|
||||
if let Some(content_length_header_value) = content_length_header_value {
|
||||
http_request
|
||||
.headers
|
||||
.typed_insert(ContentLength(content_length_header_value));
|
||||
}
|
||||
|
||||
// Step 8.10 If contentLength is non-null and httpRequest’s keepalive is true, then:
|
||||
if content_length.is_some() && http_request.keep_alive {
|
||||
// TODO(#33616) Keepalive requires request's client object's fetch group
|
||||
}
|
||||
|
||||
// Step 8.11: If httpRequest’s referrer is a URL, then:
|
||||
match http_request.referrer {
|
||||
Referrer::NoReferrer => (),
|
||||
Referrer::ReferrerUrl(ref http_request_referrer) |
|
||||
Referrer::Client(ref http_request_referrer) => {
|
||||
// Step 8.11.1: Let referrerValue be httpRequest’s referrer, serialized and isomorphic
|
||||
|
@ -1180,23 +1187,17 @@ async fn http_network_or_cache_fetch(
|
|||
// This error should only happen in cases where hyper and rust-url disagree
|
||||
// about how to parse a referer.
|
||||
// https://github.com/servo/servo/issues/24175
|
||||
error!("Failed to parse {} as referer", http_request_referrer);
|
||||
error!("Failed to parse {} as referrer", http_request_referrer);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
|
||||
// Step 8.12: Append a request `Origin` header for httpRequest.
|
||||
if cors_flag || (http_request.method != Method::GET && http_request.method != Method::HEAD) {
|
||||
debug_assert_ne!(http_request.origin, Origin::Client);
|
||||
if let Origin::Origin(ref url_origin) = http_request.origin {
|
||||
if let Some(hyper_origin) = try_immutable_origin_to_hyper_origin(url_origin) {
|
||||
http_request.headers.typed_insert(hyper_origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Step 8.12 Append a request `Origin` header for httpRequest.
|
||||
append_a_request_origin_header(http_request);
|
||||
|
||||
// Step 8.13: Append the Fetch metadata headers for httpRequest.
|
||||
// TODO Implement Sec-Fetch-* headers
|
||||
// Step 8.13 Append the Fetch metadata headers for httpRequest.
|
||||
// TODO(#33616) Implement Sec-Fetch-* headers
|
||||
|
||||
// Step 8.14: If httpRequest’s initiator is "prefetch", then set a structured field value given
|
||||
// (`Sec-Purpose`, the token "prefetch") in httpRequest’s header list.
|
||||
|
@ -1317,8 +1318,7 @@ async fn http_network_or_cache_fetch(
|
|||
}
|
||||
}
|
||||
|
||||
// Step 5.18
|
||||
// TODO If there’s a proxy-authentication entry, use it as appropriate.
|
||||
// TODO(#33616) Step 8.22 If there’s a proxy-authentication entry, use it as appropriate.
|
||||
|
||||
// If the cache is not ready to construct a response, wait.
|
||||
//
|
||||
|
@ -1355,11 +1355,16 @@ async fn http_network_or_cache_fetch(
|
|||
}
|
||||
}
|
||||
|
||||
// Step 5.19
|
||||
// TODO(#33616): Step 8.23 Set httpCache to the result of determining the
|
||||
// HTTP cache partition, given httpRequest.
|
||||
if let Ok(http_cache) = context.state.http_cache.read() {
|
||||
if let Some(response_from_cache) =
|
||||
http_cache.construct_response(http_request, done_chan)
|
||||
{
|
||||
// Step 8.25.1 Set storedResponse to the result of selecting a response from the httpCache,
|
||||
// possibly needing validation, as per the "Constructing Responses from Caches"
|
||||
// chapter of HTTP Caching, if any.
|
||||
let stored_response = http_cache.construct_response(http_request, done_chan);
|
||||
|
||||
// Step 8.25.2 If storedResponse is non-null, then:
|
||||
if let Some(response_from_cache) = stored_response {
|
||||
let response_headers = response_from_cache.response.headers.clone();
|
||||
// Substep 1, 2, 3, 4
|
||||
let (cached_response, needs_revalidation) =
|
||||
|
@ -1376,6 +1381,7 @@ async fn http_network_or_cache_fetch(
|
|||
response_from_cache.needs_validation,
|
||||
),
|
||||
};
|
||||
|
||||
if needs_revalidation {
|
||||
revalidating_flag = true;
|
||||
// Substep 5
|
||||
|
@ -1472,12 +1478,11 @@ async fn http_network_or_cache_fetch(
|
|||
|
||||
wait_for_cached_response(done_chan, &mut response).await;
|
||||
|
||||
// Step 6
|
||||
// TODO: https://infra.spec.whatwg.org/#if-aborted
|
||||
// TODO(#33616): Step 9. If aborted, then return the appropriate network error for fetchParams.
|
||||
|
||||
// Step 7
|
||||
// Step 10. If response is null, then:
|
||||
if response.is_none() {
|
||||
// Substep 1
|
||||
// Step 10.1 If httpRequest’s cache mode is "only-if-cached", then return a network error.
|
||||
if http_request.cache_mode == CacheMode::OnlyIfCached {
|
||||
// The cache will not be updated,
|
||||
// set its state to ready to construct.
|
||||
|
@ -1486,20 +1491,22 @@ async fn http_network_or_cache_fetch(
|
|||
"Couldn't find response in cache".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// More Step 7
|
||||
if response.is_none() {
|
||||
// Substep 2
|
||||
|
||||
// Step 10.2 Let forwardResponse be the result of running HTTP-network fetch given httpFetchParams,
|
||||
// includeCredentials, and isNewConnectionFetch.
|
||||
let forward_response =
|
||||
http_network_fetch(http_request, include_credentials, done_chan, context).await;
|
||||
// Substep 3
|
||||
|
||||
// Step 10.3 If httpRequest’s method is unsafe and forwardResponse’s status is in the range 200 to 399,
|
||||
// inclusive, invalidate appropriate stored responses in httpCache, as per the
|
||||
// "Invalidating Stored Responses" chapter of HTTP Caching, and set storedResponse to null.
|
||||
if forward_response.status.in_range(200..=399) && !http_request.method.is_safe() {
|
||||
if let Ok(mut http_cache) = context.state.http_cache.write() {
|
||||
http_cache.invalidate(http_request, &forward_response);
|
||||
}
|
||||
}
|
||||
|
||||
// Substep 4
|
||||
// Step 10.4 If the revalidatingFlag is set and forwardResponse’s status is 304, then:
|
||||
if revalidating_flag && forward_response.status == StatusCode::NOT_MODIFIED {
|
||||
if let Ok(mut http_cache) = context.state.http_cache.write() {
|
||||
// Ensure done_chan is None,
|
||||
|
@ -1510,39 +1517,136 @@ async fn http_network_or_cache_fetch(
|
|||
wait_for_cached_response(done_chan, &mut response).await;
|
||||
}
|
||||
|
||||
// Substep 5
|
||||
// Step 10.5 If response is null, then:
|
||||
if response.is_none() {
|
||||
// Step 10.5.1 Set response to forwardResponse.
|
||||
let forward_response = response.insert(forward_response);
|
||||
|
||||
// Per https://httpwg.org/specs/rfc9111.html#response.cacheability we must not cache responses
|
||||
// if the No-Store directive is present
|
||||
if http_request.cache_mode != CacheMode::NoStore {
|
||||
// Subsubstep 2, doing it first to avoid a clone of forward_response.
|
||||
// Step 10.5.2 Store httpRequest and forwardResponse in httpCache, as per the
|
||||
// "Storing Responses in Caches" chapter of HTTP Caching.
|
||||
if let Ok(mut http_cache) = context.state.http_cache.write() {
|
||||
http_cache.store(http_request, &forward_response);
|
||||
http_cache.store(http_request, forward_response);
|
||||
}
|
||||
}
|
||||
// Subsubstep 1
|
||||
response = Some(forward_response);
|
||||
}
|
||||
}
|
||||
|
||||
let mut response = response.unwrap();
|
||||
|
||||
// The cache has been updated, set its state to ready to construct.
|
||||
update_http_cache_state(context, http_request);
|
||||
|
||||
// Step 8
|
||||
// TODO: if necessary set response's range-requested flag
|
||||
let mut response = response.unwrap();
|
||||
|
||||
// Step 9
|
||||
// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check
|
||||
#[derive(PartialEq)]
|
||||
enum CrossOriginResourcePolicy {
|
||||
Allowed,
|
||||
Blocked,
|
||||
// FIXME: The spec doesn't tell us to do this *here*, but if we don't do it then
|
||||
// tests fail. Where should we do it instead? See also #33615
|
||||
if http_request.response_tainting != ResponseTainting::CorsTainting &&
|
||||
cross_origin_resource_policy_check(http_request, &response) ==
|
||||
CrossOriginResourcePolicy::Blocked
|
||||
{
|
||||
return Response::network_error(NetworkError::Internal(
|
||||
"Cross-origin resource policy check failed".into(),
|
||||
));
|
||||
}
|
||||
|
||||
fn cross_origin_resource_policy_check(
|
||||
// TODO(#33616): Step 11. Set response’s URL list to a clone of httpRequest’s URL list.
|
||||
// TODO(#33616): Step 12. If httpRequest’s header list contains `Range`,
|
||||
// then set response’s range-requested flag.
|
||||
// TODO(#33616): Step 13 Set response’s request-includes-credentials to includeCredentials.
|
||||
|
||||
// Step 14. If response’s status is 401, httpRequest’s response tainting is not "cors",
|
||||
// includeCredentials is true, and request’s window is an environment settings object, then:
|
||||
// TODO(#33616): Figure out what to do with request window objects
|
||||
if let (Some(StatusCode::UNAUTHORIZED), false, true) =
|
||||
(response.status.try_code(), cors_flag, include_credentials)
|
||||
{
|
||||
// TODO: Step 14.1 Spec says requires testing on multiple WWW-Authenticate headers
|
||||
|
||||
// Step 14.2 If request’s body is non-null, then:
|
||||
if http_request.body.is_some() {
|
||||
// TODO Implement body source
|
||||
}
|
||||
|
||||
// Step 14.3 If request’s use-URL-credentials flag is unset or isAuthenticationFetch is true, then:
|
||||
if !http_request.use_url_credentials || authentication_fetch_flag {
|
||||
// TODO(#33616, #27439): 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 (#33616)
|
||||
return response;
|
||||
}
|
||||
|
||||
// Make sure this is set to None,
|
||||
// since we're about to start a new `http_network_or_cache_fetch`.
|
||||
*done_chan = None;
|
||||
|
||||
// Step 14.4 Set response to the result of running HTTP-network-or-cache fetch given fetchParams and true.
|
||||
response = http_network_or_cache_fetch(
|
||||
http_request,
|
||||
true, /* authentication flag */
|
||||
cors_flag,
|
||||
done_chan,
|
||||
context,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Step 15. If response’s status is 407, then:
|
||||
if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED {
|
||||
// Step 15.1 If request’s window is "no-window", then return a network error.
|
||||
|
||||
if request_has_no_window {
|
||||
return Response::network_error(NetworkError::Internal(
|
||||
"Can't find Window object".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// (Step 15.2 does not exist, requires testing on Proxy-Authenticate headers)
|
||||
|
||||
// TODO(#33616): Step 15.3 If fetchParams is canceled, then return
|
||||
// the appropriate network error for fetchParams.
|
||||
// TODO(#33616): Step 15.4 Prompt the end user as appropriate in request’s window and store the
|
||||
// result as a proxy-authentication entry.
|
||||
|
||||
// Step 15.5 Set response to the result of running HTTP-network-or-cache fetch given fetchParams.
|
||||
|
||||
// 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 (#33616)
|
||||
return response;
|
||||
}
|
||||
|
||||
// TODO(#33616): Step 16. If all of the following are true:
|
||||
// * response’s status is 421
|
||||
// * isNewConnectionFetch is false
|
||||
// * request’s body is null, or request’s body is non-null and request’s body’s source is non-null
|
||||
// then: [..]
|
||||
|
||||
// Step 17. If isAuthenticationFetch is true, then create an authentication entry for request and the given realm.
|
||||
if authentication_fetch_flag {
|
||||
// TODO(#33616)
|
||||
}
|
||||
|
||||
// Step 18. Return response.
|
||||
response
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check>
|
||||
///
|
||||
/// This is obtained from [cross_origin_resource_policy_check]
|
||||
#[derive(PartialEq)]
|
||||
enum CrossOriginResourcePolicy {
|
||||
Allowed,
|
||||
Blocked,
|
||||
}
|
||||
|
||||
// TODO(#33615): Judging from the name, this appears to be https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check,
|
||||
// but the steps aren't even close to the spec. Perhaps this needs to be rewritten?
|
||||
fn cross_origin_resource_policy_check(
|
||||
request: &Request,
|
||||
response: &Response,
|
||||
) -> CrossOriginResourcePolicy {
|
||||
) -> CrossOriginResourcePolicy {
|
||||
// Step 1
|
||||
if request.mode != RequestMode::NoCors {
|
||||
return CrossOriginResourcePolicy::Allowed;
|
||||
|
@ -1574,8 +1678,7 @@ async fn http_network_or_cache_fetch(
|
|||
|
||||
// Step 5
|
||||
if let Origin::Origin(ref request_origin) = request.origin {
|
||||
let schemeless_same_origin =
|
||||
is_schemelessy_same_site(request_origin, ¤t_url_origin);
|
||||
let schemeless_same_origin = is_schemelessy_same_site(request_origin, ¤t_url_origin);
|
||||
if schemeless_same_origin &&
|
||||
(request_origin.scheme() == Some("https") ||
|
||||
response.https_state == HttpsState::None)
|
||||
|
@ -1590,87 +1693,6 @@ async fn http_network_or_cache_fetch(
|
|||
}
|
||||
|
||||
CrossOriginResourcePolicy::Allowed
|
||||
}
|
||||
|
||||
if http_request.response_tainting != ResponseTainting::CorsTainting &&
|
||||
cross_origin_resource_policy_check(http_request, &response) ==
|
||||
CrossOriginResourcePolicy::Blocked
|
||||
{
|
||||
return Response::network_error(NetworkError::Internal(
|
||||
"Cross-origin resource policy check failed".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Step 10
|
||||
// FIXME: Figure out what to do with request window objects
|
||||
if let (Some(StatusCode::UNAUTHORIZED), false, true) =
|
||||
(response.status.try_code(), cors_flag, include_credentials)
|
||||
{
|
||||
// Substep 1
|
||||
// TODO: Spec says requires testing on multiple WWW-Authenticate headers
|
||||
|
||||
// Substep 2
|
||||
if http_request.body.is_some() {
|
||||
// TODO Implement body source
|
||||
}
|
||||
|
||||
// Substep 3
|
||||
if !http_request.use_url_credentials || authentication_fetch_flag {
|
||||
// FIXME: 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;
|
||||
}
|
||||
|
||||
// Make sure this is set to None,
|
||||
// since we're about to start a new `http_network_or_cache_fetch`.
|
||||
*done_chan = None;
|
||||
|
||||
// Substep 4
|
||||
response = http_network_or_cache_fetch(
|
||||
http_request,
|
||||
true, /* authentication flag */
|
||||
cors_flag,
|
||||
done_chan,
|
||||
context,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Step 11
|
||||
if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED {
|
||||
// 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 12
|
||||
if authentication_fetch_flag {
|
||||
// TODO Create the authentication entry for request and the given realm
|
||||
}
|
||||
|
||||
// Step 13
|
||||
response
|
||||
}
|
||||
|
||||
// Convenience struct that implements Done, for setting responseEnd on function return
|
||||
|
@ -2223,3 +2245,115 @@ pub fn is_redirect_status(status: StatusCode) -> bool {
|
|||
StatusCode::PERMANENT_REDIRECT
|
||||
)
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#concept-request-tainted-origin>
|
||||
fn request_has_redirect_tainted_origin(request: &Request) -> bool {
|
||||
// Step 1. Assert: request’s origin is not "client".
|
||||
let Origin::Origin(request_origin) = &request.origin else {
|
||||
panic!("origin cannot be \"client\" at this point in time");
|
||||
};
|
||||
|
||||
// Step 2. Let lastURL be null.
|
||||
let mut last_url = None;
|
||||
|
||||
// Step 3. For each url of request’s URL list:
|
||||
for url in &request.url_list {
|
||||
// Step 3.1 If lastURL is null, then set lastURL to url and continue.
|
||||
let Some(last_url) = &mut last_url else {
|
||||
last_url = Some(url);
|
||||
continue;
|
||||
};
|
||||
|
||||
// Step 3.2 If url’s origin is not same origin with lastURL’s origin and
|
||||
// request’s origin is not same origin with lastURL’s origin, then return true.
|
||||
if url.origin() != last_url.origin() && *request_origin != last_url.origin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3.3 Set lastURL to url.
|
||||
*last_url = url;
|
||||
}
|
||||
|
||||
// Step 4. Return false.
|
||||
false
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#serializing-a-request-origin>
|
||||
fn serialize_request_origin(request: &Request) -> headers::Origin {
|
||||
// Step 1. Assert: request’s origin is not "client".
|
||||
let Origin::Origin(origin) = &request.origin else {
|
||||
panic!("origin cannot be \"client\" at this point in time");
|
||||
};
|
||||
|
||||
// Step 2. If request has a redirect-tainted origin, then return "null".
|
||||
if request_has_redirect_tainted_origin(request) {
|
||||
return headers::Origin::NULL;
|
||||
}
|
||||
|
||||
// Step 3. Return request’s origin, serialized.
|
||||
match origin {
|
||||
ImmutableOrigin::Opaque(_) => headers::Origin::NULL,
|
||||
ImmutableOrigin::Tuple(scheme, host, port) => {
|
||||
// TODO: Ensure that hyper/servo don't disagree about valid origin headers
|
||||
headers::Origin::try_from_parts(scheme, &host.to_string(), *port)
|
||||
.unwrap_or(headers::Origin::NULL)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#append-a-request-origin-header>
|
||||
pub fn append_a_request_origin_header(request: &mut Request) {
|
||||
// Step 1. Assert: request’s origin is not "client".
|
||||
let Origin::Origin(request_origin) = &request.origin else {
|
||||
panic!("origin cannot be \"client\" at this point in time");
|
||||
};
|
||||
|
||||
// Step 2. Let serializedOrigin be the result of byte-serializing a request origin with request.
|
||||
let mut serialized_origin = serialize_request_origin(request);
|
||||
|
||||
// Step 3. If request’s response tainting is "cors" or request’s mode is "websocket",
|
||||
// then append (`Origin`, serializedOrigin) to request’s header list.
|
||||
if request.response_tainting == ResponseTainting::CorsTainting ||
|
||||
matches!(request.mode, RequestMode::WebSocket { .. })
|
||||
{
|
||||
request.headers.typed_insert(serialized_origin);
|
||||
}
|
||||
// Step 4. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
|
||||
else if !matches!(request.method, Method::GET | Method::HEAD) {
|
||||
// Step 4.1 If request’s mode is not "cors", then switch on request’s referrer policy:
|
||||
if request.mode != RequestMode::CorsMode {
|
||||
match request.referrer_policy {
|
||||
Some(ReferrerPolicy::NoReferrer) => {
|
||||
// Set serializedOrigin to `null`.
|
||||
serialized_origin = headers::Origin::NULL;
|
||||
},
|
||||
Some(
|
||||
ReferrerPolicy::NoReferrerWhenDowngrade |
|
||||
ReferrerPolicy::StrictOrigin |
|
||||
ReferrerPolicy::StrictOriginWhenCrossOrigin,
|
||||
) => {
|
||||
// If request’s origin is a tuple origin, its scheme is "https", and
|
||||
// request’s current URL’s scheme is not "https", then set serializedOrigin to `null`.
|
||||
if let ImmutableOrigin::Tuple(scheme, _, _) = &request_origin {
|
||||
if scheme == "https" && request.current_url().scheme() != "https" {
|
||||
serialized_origin = headers::Origin::NULL;
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(ReferrerPolicy::SameOrigin) => {
|
||||
// If request’s origin is not same origin with request’s current URL’s origin,
|
||||
// then set serializedOrigin to `null`.
|
||||
if *request_origin != request.current_url().origin() {
|
||||
serialized_origin = headers::Origin::NULL;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// Otherwise, do nothing.
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Step 4.2. Append (`Origin`, serializedOrigin) to request’s header list.
|
||||
request.headers.typed_insert(serialized_origin);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#fetch-method
|
||||
/// <https://fetch.spec.whatwg.org/#fetch-method>
|
||||
#[allow(crown::unrooted_must_root, non_snake_case)]
|
||||
pub fn Fetch(
|
||||
global: &GlobalScope,
|
||||
|
@ -148,33 +148,47 @@ pub fn Fetch(
|
|||
) -> Rc<Promise> {
|
||||
let core_resource_thread = global.core_resource_thread();
|
||||
|
||||
// Step 1
|
||||
// Step 1. Let p be a new promise.
|
||||
let promise = Promise::new_in_current_realm(comp);
|
||||
let response = Response::new(global);
|
||||
|
||||
// Step 2
|
||||
// Step 7. Let responseObject be null.
|
||||
// NOTE: We do initialize the object earlier earlier so we can use it to track errors
|
||||
let response = Response::new(global);
|
||||
response.Headers().set_guard(Guard::Immutable);
|
||||
|
||||
// Step 2. Let requestObject be the result of invoking the initial value of Request as constructor
|
||||
// with input and init as arguments. If this throws an exception, reject p with it and return p.
|
||||
let request = match Request::Constructor(global, None, CanGc::note(), input, init) {
|
||||
Err(e) => {
|
||||
response.error_stream(e.clone());
|
||||
promise.reject_error(e);
|
||||
return promise;
|
||||
},
|
||||
Ok(r) => r.get_request(),
|
||||
Ok(r) => {
|
||||
// Step 3. Let request be requestObject’s request.
|
||||
r.get_request()
|
||||
},
|
||||
};
|
||||
let timing_type = request.timing_type();
|
||||
|
||||
let mut request_init = request_init_from_request(request);
|
||||
request_init.csp_list.clone_from(&global.get_csp_list());
|
||||
|
||||
// Step 3
|
||||
// TODO: Step 4. If requestObject’s signal is aborted, then: [..]
|
||||
|
||||
// Step 5. Let globalObject be request’s client’s global object.
|
||||
// NOTE: We already get the global object as an argument
|
||||
|
||||
// Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s
|
||||
// service-workers mode to "none".
|
||||
if global.downcast::<ServiceWorkerGlobalScope>().is_some() {
|
||||
request_init.service_workers_mode = ServiceWorkersMode::None;
|
||||
}
|
||||
|
||||
// Step 4
|
||||
response.Headers().set_guard(Guard::Immutable);
|
||||
// TODO: Steps 8-11, abortcontroller stuff
|
||||
|
||||
// Step 5
|
||||
// Step 12. Set controller to the result of calling fetch given request and
|
||||
// processResponse given response being these steps: [..]
|
||||
let (action_sender, action_receiver) = ipc::channel().unwrap();
|
||||
let fetch_context = Arc::new(Mutex::new(FetchContext {
|
||||
fetch_promise: Some(TrustedPromise::new(promise.clone())),
|
||||
|
@ -200,6 +214,7 @@ pub fn Fetch(
|
|||
))
|
||||
.unwrap();
|
||||
|
||||
// Step 13. Return p.
|
||||
promise
|
||||
}
|
||||
|
||||
|
@ -263,6 +278,7 @@ impl FetchResponseListener for FetchContext {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Step 4.3
|
||||
promise.resolve_native(&self.response_object.root());
|
||||
self.fetch_promise = Some(TrustedPromise::new(promise));
|
||||
|
|
|
@ -1,13 +1,4 @@
|
|||
[assorted.window.html]
|
||||
[Origin header and 308 redirect]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy no-referrer]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST same-origin fetch no-cors mode with Referrer-Policy no-referrer]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy origin-when-cross-origin]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -17,17 +8,8 @@
|
|||
[Origin header and POST navigation]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy same-origin]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy same-origin]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST same-origin navigation with Referrer-Policy no-referrer]
|
||||
expected: FAIL
|
||||
|
||||
[Origin header and POST cross-origin navigation with Referrer-Policy no-referrer-when-downgrade]
|
||||
expected: FAIL
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue