mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 16:01:00 +00:00
fix(fetch): integrate shared redirect and response metadata
Validate Window and Worker requests with the original client origin and complete ResponseHead URL list. Preserve response-filter ownership, remove duplicate origin helpers, and include Script requests in per-hop checks. Retain first-hop memory caching while isolating transport redirect handling and client origins. Keep network ExtraInfo off synthetic redirects, and adapt response-document tests to process ICU defaults. Validation: cargo fmt --all; cargo clippy --workspace --all-targets --all-features -- -D warnings; cargo nextest run --no-fail-fast (18,276 passed, 13 skipped). The four targeted cache, redirect and Service Worker regressions also pass.
This commit is contained in:
@@ -1061,20 +1061,18 @@ async fn service_worker_redirect_preserves_worker_response_filter() -> Result<()
|
||||
}})()"#, cross = servers.cross_origin), true,
|
||||
).await?;
|
||||
let results: serde_json::Value = serde_json::from_str(result["value"].as_str().unwrap())?;
|
||||
// A synthesized Response has an empty URL list and remains basic after
|
||||
// the intercepted redirect, including its ordinary response headers.
|
||||
for kind in ["basic", "basic-buffered", "cors", "default"] {
|
||||
assert_eq!(results[kind]["body"], SCRIPT, "{kind}: {results}");
|
||||
assert_eq!(
|
||||
results[kind]["type"],
|
||||
if kind.starts_with("basic") {
|
||||
"basic"
|
||||
} else {
|
||||
"cors"
|
||||
},
|
||||
if kind != "cors" { "basic" } else { "cors" },
|
||||
"{kind}: {results}"
|
||||
);
|
||||
assert_eq!(
|
||||
results[kind]["privateHeader"],
|
||||
if kind.starts_with("basic") {
|
||||
if kind != "cors" {
|
||||
serde_json::json!("yes")
|
||||
} else {
|
||||
serde_json::Value::Null
|
||||
|
||||
@@ -355,8 +355,7 @@ fn append_browser_subresource_headers(
|
||||
request.request_mode.as_ref().to_owned(),
|
||||
);
|
||||
append_header_if_missing(outgoing, "Sec-Fetch-Dest", "script".to_owned());
|
||||
if let Some(origin) = request_origin_header_value(request, request_url, redirect_chain)
|
||||
{
|
||||
if let Some(origin) = request_origin_header_value(request, request_url) {
|
||||
append_header_if_missing(outgoing, "Origin", origin);
|
||||
}
|
||||
append_browser_client_hints(outgoing, config);
|
||||
@@ -1308,8 +1307,7 @@ mod tests {
|
||||
.with_request_origin(moli_url::WebOrigin::Opaque)
|
||||
.with_browser_request_metadata(BrowserRequestMetadata::Fetch);
|
||||
|
||||
let headers =
|
||||
outgoing_request_headers_for_url(&config, &request, &request_url, &Vec::new(), None);
|
||||
let headers = outgoing_request_headers_for_url(&config, &request, &request_url, None);
|
||||
|
||||
assert_eq!(header_value(&headers, "origin").as_deref(), Some("null"));
|
||||
assert_eq!(
|
||||
|
||||
@@ -62,6 +62,7 @@ pub(in crate::network) struct RawSubresourceCacheKey {
|
||||
page_cache_partition_id: u64,
|
||||
url: String,
|
||||
resource_type: &'static str,
|
||||
follow_redirects: bool,
|
||||
credentials_mode: String,
|
||||
request_origin: Option<String>,
|
||||
request_mode: String,
|
||||
@@ -635,6 +636,7 @@ pub(in crate::network) fn raw_subresource_memory_cache_key(
|
||||
page_cache_partition_id: 0,
|
||||
url: request.url.as_str().to_owned(),
|
||||
resource_type: raw_subresource_cache_resource_type_key(request.resource_type),
|
||||
follow_redirects: request.follow_redirects,
|
||||
credentials_mode: request.credentials_mode.as_ref().to_owned(),
|
||||
request_origin: request
|
||||
.request_origin()
|
||||
@@ -668,7 +670,10 @@ fn raw_subresource_request_is_memory_cacheable(request: &Request) -> bool {
|
||||
&& request.method.eq_ignore_ascii_case("GET")
|
||||
&& request.body.is_none()
|
||||
&& request.auth().is_none()
|
||||
&& request.follow_redirects
|
||||
// The browser's CORS loop follows redirects itself. Its individual
|
||||
// transport requests can still reuse cached responses with an empty
|
||||
// URL list, in a separate bucket from automatically followed fetches.
|
||||
&& request.redirect_mode == moli_fetch::RequestRedirectMode::Follow
|
||||
&& raw_subresource_memory_cacheable_headers(request)
|
||||
}
|
||||
|
||||
@@ -870,6 +875,45 @@ mod tests {
|
||||
load
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn per_hop_cache_keeps_transport_redirects_and_client_origins_separate() {
|
||||
let automatic = Request::get("https://cache.test/resource")
|
||||
.unwrap()
|
||||
.with_resource_type(RequestResourceType::Raw)
|
||||
.with_browser_request_metadata(BrowserRequestMetadata::Fetch)
|
||||
.with_initiator_url(&Url::parse("https://cache.test/page").unwrap());
|
||||
let per_hop = automatic.clone().with_follow_redirects(false);
|
||||
let key = raw_subresource_memory_cache_key(&per_hop).expect("cacheable first CORS hop");
|
||||
let mut cache = SharedMemoryResourceCache::with_limits(usize::MAX, usize::MAX);
|
||||
cache.insert_raw_subresource(
|
||||
key.clone(),
|
||||
raw_response(per_hop.url.as_str(), b"cached"),
|
||||
u64::MAX,
|
||||
);
|
||||
assert!(cache.lookup_raw_subresource(&key).is_some());
|
||||
for isolated in [
|
||||
automatic,
|
||||
per_hop
|
||||
.clone()
|
||||
.with_request_origin(moli_url::WebOrigin::Opaque),
|
||||
] {
|
||||
let isolated_key = raw_subresource_memory_cache_key(&isolated).unwrap();
|
||||
assert!(
|
||||
cache.lookup_raw_subresource(&isolated_key).is_none(),
|
||||
"a cached hop cannot replace an automatic redirect or another client's response"
|
||||
);
|
||||
}
|
||||
for mode in [
|
||||
moli_fetch::RequestRedirectMode::Manual,
|
||||
moli_fetch::RequestRedirectMode::Error,
|
||||
] {
|
||||
assert!(
|
||||
raw_subresource_memory_cache_key(&per_hop.clone().with_redirect_mode(mode))
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_one_shared_script_consumer_preserves_its_sibling() {
|
||||
let load = ScriptTextLoad::pending();
|
||||
|
||||
@@ -99,6 +99,7 @@ impl RendererNetworkResourceLoadResponse {
|
||||
moli_fetch::ResponseHead {
|
||||
final_url: self.final_url.clone(),
|
||||
status: self.status,
|
||||
status_text: None,
|
||||
headers: self.headers.clone(),
|
||||
request_cookie_report: self.request_cookie_report.clone(),
|
||||
cookie_set_reports: self.cookie_set_reports.clone(),
|
||||
|
||||
@@ -152,37 +152,19 @@ pub(crate) use self::response::{
|
||||
build_fetch_response_object_from_subresource_body_for_request_mode_with_filter,
|
||||
build_filtered_cached_response_object,
|
||||
build_navigation_preload_response_object_from_stream_for_request_mode,
|
||||
cors_preflight_request_headers,
|
||||
cors_preflight_request_headers_for_origin,
|
||||
cors_request_origin_after_redirects,
|
||||
fetch_response_needs_orb_body_validation,
|
||||
filter_cors_exposed_response_headers,
|
||||
filter_cors_exposed_response_headers_for_origin,
|
||||
is_cors_policy_failure_message,
|
||||
materialize_response_object_body,
|
||||
materialize_response_object_body_with_chunk_callback,
|
||||
materialized_body_bytes_from_value,
|
||||
network_response_filter,
|
||||
response_constructor_callback,
|
||||
validate_cors_preflight_response,
|
||||
validate_cors_preflight_response_for_origin,
|
||||
validate_cors_response_chain,
|
||||
validate_cors_response_chain_for_origin,
|
||||
response_has_null_body,
|
||||
materialize_response_object_internal_head,
|
||||
set_filtered_response_internal_head,
|
||||
materialize_cache_response_object_head,
|
||||
validate_cors_response_for_origin,
|
||||
validate_cross_origin_embedder_and_document_isolation_policy,
|
||||
validate_cross_origin_resource_policy,
|
||||
validate_fetch_response_headers_for_origin,
|
||||
validate_fetch_response_security_policy,
|
||||
validate_fetch_response_security_policy_for_origin,
|
||||
cors_preflight_request_headers, cors_preflight_request_headers_for_origin,
|
||||
fetch_response_needs_orb_body_validation, filter_cors_exposed_response_headers_for_origin,
|
||||
is_cors_policy_failure_message, materialize_cache_response_object_head,
|
||||
materialize_response_object_body, materialize_response_object_body_with_chunk_callback,
|
||||
materialize_response_object_internal_head, materialized_body_bytes_from_value,
|
||||
network_response_filter, response_constructor_callback, response_has_null_body,
|
||||
set_filtered_response_internal_head, validate_cors_preflight_response,
|
||||
validate_cors_response_chain, validate_cross_origin_embedder_and_document_isolation_policy,
|
||||
validate_cross_origin_resource_policy, validate_fetch_response_headers_for_origin,
|
||||
validate_fetch_response_security_policy, validate_fetch_response_security_policy_for_origin,
|
||||
validate_fetch_response_security_policy_with_body,
|
||||
validate_fetch_response_security_policy_with_body_classified_for_origin,
|
||||
validate_fetch_response_security_policy_with_body_for_origin,
|
||||
validate_opaque_response_blocking_with_body,
|
||||
validated_opaque_response_body,
|
||||
validate_fetch_response_security_policy_with_body_for_origin, validated_opaque_response_body,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::response::{materialize_response_object, materialize_response_object_head};
|
||||
|
||||
@@ -128,6 +128,7 @@ fn browser_request_needs_cors_redirect_checks(request: &Request) -> bool {
|
||||
| BrowserRequestMetadata::EventSource
|
||||
| BrowserRequestMetadata::JsonModule
|
||||
| BrowserRequestMetadata::Manifest
|
||||
| BrowserRequestMetadata::Script
|
||||
| BrowserRequestMetadata::StyleModule
|
||||
| BrowserRequestMetadata::Xhr,
|
||||
)
|
||||
@@ -198,7 +199,9 @@ impl ManualCorsRedirectState {
|
||||
) -> Result<ManualCorsRedirectTransition, String> {
|
||||
// Redirect request records describe the next hop's cookie decision,
|
||||
// just as they do when the transport follows redirects internally.
|
||||
if let Some(previous) = self.request.redirect_chain_mut().last_mut() {
|
||||
if let Some(previous) = self.request.redirect_chain_mut().last_mut()
|
||||
&& previous.source == moli_fetch::RedirectSource::Network
|
||||
{
|
||||
previous.request_cookie_report = head.request_cookie_report.clone();
|
||||
previous.request_extra_info = request_extra_info.cloned();
|
||||
}
|
||||
@@ -236,7 +239,8 @@ impl ManualCorsRedirectState {
|
||||
.password()
|
||||
.is_some_and(|password| !password.is_empty()))
|
||||
&& self.request.request_origin().is_some_and(|origin| {
|
||||
self.request.has_cross_origin_url(&head.final_url) || !origin.same_origin_url(&next_url)
|
||||
self.request.has_cross_origin_url(&head.final_url)
|
||||
|| !origin.same_origin_url(&next_url)
|
||||
})
|
||||
{
|
||||
return Err("CORS redirect URL must not include credentials".to_owned());
|
||||
|
||||
@@ -18,30 +18,16 @@ pub(in crate::network_host) use self::bindings::{ParsedResponseInit, parse_respo
|
||||
pub(crate) use self::bindings::{build_error_response_object, response_constructor_callback};
|
||||
pub(super) use self::body_methods::install_response_body_methods;
|
||||
pub(crate) use self::cors::{
|
||||
FetchResponseSecurityViolation,
|
||||
cors_preflight_request_headers,
|
||||
cors_preflight_request_headers_for_origin,
|
||||
cors_request_origin_after_redirects,
|
||||
fetch_response_needs_orb_body_validation,
|
||||
filter_cors_exposed_response_headers,
|
||||
filter_cors_exposed_response_headers_for_origin,
|
||||
is_cors_policy_failure_message,
|
||||
validate_cors_preflight_response,
|
||||
validate_cors_preflight_response_for_origin,
|
||||
validate_cors_response_chain,
|
||||
validate_cors_response_chain_for_origin,
|
||||
validate_cors_response_for_origin,
|
||||
validate_cors_response_headers,
|
||||
FetchResponseSecurityViolation, cors_preflight_request_headers,
|
||||
cors_preflight_request_headers_for_origin, fetch_response_needs_orb_body_validation,
|
||||
filter_cors_exposed_response_headers_for_origin, is_cors_policy_failure_message,
|
||||
validate_cors_preflight_response, validate_cors_response_chain, validate_cors_response_headers,
|
||||
validate_cross_origin_embedder_and_document_isolation_policy,
|
||||
validate_cross_origin_resource_policy,
|
||||
validate_fetch_response_headers_for_origin,
|
||||
validate_fetch_response_security_policy,
|
||||
validate_fetch_response_security_policy_for_origin,
|
||||
validate_cross_origin_resource_policy, validate_fetch_response_headers_for_origin,
|
||||
validate_fetch_response_security_policy, validate_fetch_response_security_policy_for_origin,
|
||||
validate_fetch_response_security_policy_with_body,
|
||||
validate_fetch_response_security_policy_with_body_classified_for_origin,
|
||||
validate_fetch_response_security_policy_with_body_for_origin,
|
||||
validate_opaque_response_blocking_with_body,
|
||||
validated_opaque_response_body,
|
||||
validate_fetch_response_security_policy_with_body_for_origin, validated_opaque_response_body,
|
||||
};
|
||||
pub(crate) use self::materialize::{
|
||||
FetchResponseRequest, MaterializedResponseBody, MaterializedResponseHead,
|
||||
@@ -54,8 +40,7 @@ pub(crate) use self::materialize::{
|
||||
materialize_cache_response_object_head, materialize_response_object_body,
|
||||
materialize_response_object_body_with_chunk_callback,
|
||||
materialize_response_object_internal_head, materialized_body_bytes_from_value,
|
||||
set_filtered_response_internal_head,
|
||||
network_response_filter,
|
||||
network_response_filter, set_filtered_response_internal_head,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::materialize::{materialize_response_object, materialize_response_object_head};
|
||||
|
||||
@@ -38,20 +38,6 @@ pub(crate) fn is_cors_policy_failure_message(message: &str) -> bool {
|
||||
message.contains("CORS check failed:") || message.contains("CORS preflight failed:")
|
||||
}
|
||||
|
||||
pub(crate) fn cors_request_origin_after_redirects<'a>(
|
||||
request_origin: &WebOrigin,
|
||||
redirects: impl IntoIterator<Item = (&'a url::Url, &'a url::Url)>,
|
||||
) -> WebOrigin {
|
||||
if redirects
|
||||
.into_iter()
|
||||
.any(|(from, to)| !same_origin(from, to) && !request_origin.same_origin_url(from))
|
||||
{
|
||||
WebOrigin::Opaque
|
||||
} else {
|
||||
request_origin.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates an already fetched network response and its network redirects.
|
||||
/// Service worker and browser-generated redirects still contribute to taint,
|
||||
/// but their synthetic response headers are not subject to the CORS check.
|
||||
@@ -95,7 +81,8 @@ pub(crate) fn validate_cors_response_chain_for_origin(
|
||||
validate_cors_response_chain(request_origin, head, credentials_mode)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_cors_response_for_origin(
|
||||
#[cfg(test)]
|
||||
fn validate_cors_response_for_origin(
|
||||
request_origin: &WebOrigin,
|
||||
response_url: &url::Url,
|
||||
response_headers: &[(String, String)],
|
||||
@@ -106,7 +93,11 @@ pub(crate) fn validate_cors_response_for_origin(
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
validate_cors_response_headers(request_origin.ascii_serialization(), response_headers, credentials_mode)
|
||||
validate_cors_response_headers(
|
||||
request_origin.ascii_serialization(),
|
||||
response_headers,
|
||||
credentials_mode,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_fetch_response_security_policy(
|
||||
@@ -271,8 +262,15 @@ pub(crate) fn validate_fetch_response_security_policy_with_body_for_origin(
|
||||
policy_context: crate::types::SubresourcePolicyContext,
|
||||
) -> Result<(), String> {
|
||||
validate_fetch_response_security_policy_with_body_classified_for_origin(
|
||||
document_url, request_origin, head, response_body, request_mode, credentials_mode, policy_context,
|
||||
).map_err(FetchResponseSecurityViolation::into_message)
|
||||
document_url,
|
||||
request_origin,
|
||||
head,
|
||||
response_body,
|
||||
request_mode,
|
||||
credentials_mode,
|
||||
policy_context,
|
||||
)
|
||||
.map_err(FetchResponseSecurityViolation::into_message)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_fetch_response_security_policy_with_body_classified_for_origin(
|
||||
@@ -518,7 +516,13 @@ pub(crate) fn cors_preflight_request_headers_for_origin(
|
||||
request_headers: &[(String, String)],
|
||||
use_cors_preflight: bool,
|
||||
) -> Option<Vec<(String, String)>> {
|
||||
cors_preflight_request_headers(!request_origin.same_origin_url(request_url), request_url, method, request_headers, use_cors_preflight)
|
||||
cors_preflight_request_headers(
|
||||
!request_origin.same_origin_url(request_url),
|
||||
request_url,
|
||||
method,
|
||||
request_headers,
|
||||
use_cors_preflight,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_cors_preflight_response(
|
||||
@@ -595,7 +599,8 @@ pub(crate) fn validate_cors_preflight_response(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_cors_preflight_response_for_origin(
|
||||
#[cfg(test)]
|
||||
fn validate_cors_preflight_response_for_origin(
|
||||
request_origin: &WebOrigin,
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
requested_method: &str,
|
||||
@@ -604,7 +609,15 @@ pub(crate) fn validate_cors_preflight_response_for_origin(
|
||||
response_headers: &[(String, String)],
|
||||
use_cors_preflight: bool,
|
||||
) -> Result<(), String> {
|
||||
validate_cors_preflight_response(request_origin.ascii_serialization(), credentials_mode, requested_method, request_headers, response_status, response_headers, use_cors_preflight)
|
||||
validate_cors_preflight_response(
|
||||
request_origin.ascii_serialization(),
|
||||
credentials_mode,
|
||||
requested_method,
|
||||
request_headers,
|
||||
response_status,
|
||||
response_headers,
|
||||
use_cors_preflight,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn filter_cors_exposed_response_headers_for_origin(
|
||||
@@ -721,6 +734,7 @@ mod tests {
|
||||
moli_fetch::ResponseHead {
|
||||
final_url,
|
||||
status: 200,
|
||||
status_text: None,
|
||||
headers,
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
@@ -737,6 +751,7 @@ mod tests {
|
||||
let mut head = moli_fetch::ResponseHead {
|
||||
final_url: url("https://final.test/script.js"),
|
||||
status: 200,
|
||||
status_text: None,
|
||||
headers: vec![("Access-Control-Allow-Origin".to_owned(), "*".to_owned())],
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
@@ -864,12 +879,24 @@ mod tests {
|
||||
RequestCredentialsMode::Include,
|
||||
] {
|
||||
assert_eq!(
|
||||
validate_cors_response_for_origin(&WebOrigin::from_url(&document_url), &response_url, &headers, mode).is_ok(),
|
||||
validate_cors_response_for_origin(
|
||||
&WebOrigin::from_url(&document_url),
|
||||
&response_url,
|
||||
&headers,
|
||||
mode
|
||||
)
|
||||
.is_ok(),
|
||||
mode != RequestCredentialsMode::Include || values == ["true"],
|
||||
"mode={mode:?}, values={values:?}"
|
||||
);
|
||||
assert!(
|
||||
validate_cors_response_for_origin(&WebOrigin::from_url(&document_url), &document_url, &headers, mode).is_ok(),
|
||||
validate_cors_response_for_origin(
|
||||
&WebOrigin::from_url(&document_url),
|
||||
&document_url,
|
||||
&headers,
|
||||
mode
|
||||
)
|
||||
.is_ok(),
|
||||
"same-origin responses do not require CORS permission"
|
||||
);
|
||||
}
|
||||
@@ -881,36 +908,75 @@ mod tests {
|
||||
let home = url("https://page.test/a");
|
||||
let away = url("https://script.test/b");
|
||||
let elsewhere = url("https://other.test/c");
|
||||
let origin = WebOrigin::from_url(&home);
|
||||
assert_eq!(
|
||||
cors_request_origin_after_redirects(&origin, [(&home, &away)]),
|
||||
origin
|
||||
);
|
||||
for redirects in [
|
||||
vec![(&away, &elsewhere)],
|
||||
vec![(&home, &away), (&away, &home)],
|
||||
] {
|
||||
let tainted = cors_request_origin_after_redirects(&origin, redirects);
|
||||
assert!(tainted.is_opaque());
|
||||
assert!(
|
||||
validate_cors_response_for_origin(
|
||||
&tainted,
|
||||
&home,
|
||||
&[("access-control-allow-origin".into(), "null".into())],
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
validate_cors_response_for_origin(
|
||||
&tainted,
|
||||
&home,
|
||||
&[],
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
)
|
||||
.is_err(),
|
||||
"returning to the initial origin must not bypass CORS"
|
||||
);
|
||||
for origin in [WebOrigin::from_url(&home), WebOrigin::Opaque] {
|
||||
for source in [
|
||||
RedirectSource::Network,
|
||||
RedirectSource::ServiceWorker,
|
||||
RedirectSource::Internal,
|
||||
] {
|
||||
for (urls, tuple_origin) in [
|
||||
(vec![&home, &away], "https://page.test"),
|
||||
(vec![&away, &elsewhere], "null"),
|
||||
(vec![&home, &away, &home], "null"),
|
||||
] {
|
||||
let expected_origin = if origin.is_opaque() {
|
||||
"null"
|
||||
} else {
|
||||
tuple_origin
|
||||
};
|
||||
let mut head = header_response(
|
||||
(*urls.last().unwrap()).clone(),
|
||||
vec![("Access-Control-Allow-Origin".into(), expected_origin.into())],
|
||||
);
|
||||
head.redirect_chain = urls
|
||||
.windows(2)
|
||||
.map(|pair| moli_fetch::RedirectInfo {
|
||||
source,
|
||||
from_url: pair[0].clone(),
|
||||
to_url: pair[1].clone(),
|
||||
status: 302,
|
||||
// The response to each hop is checked before its redirect
|
||||
// changes the serialized origin for the following request.
|
||||
headers: if source == RedirectSource::Network {
|
||||
vec![(
|
||||
"Access-Control-Allow-Origin".into(),
|
||||
origin.ascii_serialization().into(),
|
||||
)]
|
||||
} else {
|
||||
Vec::new()
|
||||
},
|
||||
network_extra_info_available: false,
|
||||
request_extra_info: None,
|
||||
response_extra_info: None,
|
||||
redirect_has_extra_info: false,
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
from_cache: false,
|
||||
negotiated_http_version: None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
head.url_list().serialized_origin_for_origin(&origin),
|
||||
expected_origin
|
||||
);
|
||||
assert!(head.url_list().has_cross_origin_url_for_origin(&origin));
|
||||
validate_cors_response_chain_for_origin(
|
||||
&origin,
|
||||
&head,
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
)
|
||||
.expect("authorize network hops using their individual request origins");
|
||||
head.headers.clear();
|
||||
validate_cors_response_chain_for_origin(
|
||||
&origin,
|
||||
&head,
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
)
|
||||
.expect_err(
|
||||
"returning to the client origin cannot bypass final CORS validation",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,8 +993,13 @@ mod tests {
|
||||
("X-Other".to_owned(), "ok".to_owned()),
|
||||
];
|
||||
|
||||
let preflight =
|
||||
cors_preflight_request_headers(true, &url("http://other.test/data"), "PUT", &headers, false);
|
||||
let preflight = cors_preflight_request_headers(
|
||||
true,
|
||||
&url("http://other.test/data"),
|
||||
"PUT",
|
||||
&headers,
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
preflight,
|
||||
@@ -954,7 +1025,13 @@ mod tests {
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
cors_preflight_request_headers(true, &url("http://other.test/data"), "POST", &headers, false),
|
||||
cors_preflight_request_headers(
|
||||
true,
|
||||
&url("http://other.test/data"),
|
||||
"POST",
|
||||
&headers,
|
||||
false
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ pub(crate) async fn load_service_worker_aware_external_script_source_outcome(
|
||||
ClassicScriptResponseProvenance::Network
|
||||
} else {
|
||||
ClassicScriptResponseProvenance::ServiceWorker {
|
||||
filter: response.response_filter,
|
||||
filter: response.response_filter.clone(),
|
||||
}
|
||||
};
|
||||
external_script_source_load_outcome_from_response_inner(
|
||||
@@ -586,8 +586,12 @@ pub(crate) fn external_script_source_load_outcome_from_response(
|
||||
) -> PreparedScriptSourceLoadOutcome {
|
||||
let request_mode = external_script_request_mode(script.kind, &script.fetch_metadata);
|
||||
let head = response.head();
|
||||
let response_filter =
|
||||
crate::network_host::network_response_filter(&script.initiator_url, &head, request_mode, moli_fetch::RequestRedirectMode::Follow);
|
||||
let response_filter = crate::network_host::network_response_filter(
|
||||
&script.initiator_url,
|
||||
&head,
|
||||
request_mode,
|
||||
moli_fetch::RequestRedirectMode::Follow,
|
||||
);
|
||||
let cors_error = if request_mode == RequestMode::Cors {
|
||||
crate::network_host::validate_cors_response_chain(
|
||||
&script.initiator_url,
|
||||
|
||||
@@ -3721,8 +3721,9 @@ impl ScriptVm {
|
||||
trace_fields,
|
||||
record_started,
|
||||
);
|
||||
let request_origin = pending.request_origin();
|
||||
let mut observable_response = response;
|
||||
if pending.info.resource_type == SubresourceResourceType::Xhr && !response_filter.is_some_and(|filter| filter.is_readable()) {
|
||||
if pending.info.resource_type == SubresourceResourceType::Xhr && !response_filter.as_ref().is_some_and(|filter| filter.is_readable()) {
|
||||
observable_response.headers =
|
||||
crate::network_host::filter_cors_exposed_response_headers(
|
||||
&pending.request_origin,
|
||||
@@ -3747,7 +3748,7 @@ impl ScriptVm {
|
||||
};
|
||||
let response_filter = response_filter.or_else(|| Some(response_request.network_response_filter(
|
||||
&pending.info.document_url,
|
||||
&pending.request_origin(),
|
||||
&request_origin,
|
||||
&head,
|
||||
pending.credentials_mode,
|
||||
)));
|
||||
@@ -4881,7 +4882,7 @@ impl ScriptVm {
|
||||
}
|
||||
|
||||
let mut observable_head = started.head.clone();
|
||||
if pending.info.resource_type == SubresourceResourceType::Xhr && !started.response_filter.is_some_and(|filter| filter.is_readable()) {
|
||||
if pending.info.resource_type == SubresourceResourceType::Xhr && !started.response_filter.as_ref().is_some_and(|filter| filter.is_readable()) {
|
||||
observable_head.headers = crate::network_host::filter_cors_exposed_response_headers_for_origin(
|
||||
&pending.request_origin(),
|
||||
&observable_head,
|
||||
@@ -5852,9 +5853,7 @@ impl ScriptVm {
|
||||
trace_fields,
|
||||
record_started,
|
||||
);
|
||||
let request_origin = streaming
|
||||
.pending
|
||||
.response_request_origin(streaming.head.redirect_chain.iter().map(|redirect| (&redirect.from_url, &redirect.to_url)));
|
||||
let request_origin = streaming.pending.request_origin();
|
||||
if let PendingSubresourceContinuation::Xhr { xhr, .. } =
|
||||
streaming.pending.continuation
|
||||
&& let Some(response_body) = xhr_delivery_body
|
||||
|
||||
@@ -1284,6 +1284,7 @@ fn xml_http_request_default_response_type_parses_response_xml_for_document_mime(
|
||||
|
||||
#[test]
|
||||
fn xml_http_request_response_document_uses_response_url_and_requester_origin() {
|
||||
let environment = moli_v8_platform::ProcessEnvironmentOwner::default();
|
||||
for child_realm in [false, true] {
|
||||
for (mime, response_type) in [
|
||||
("application/xml", ""),
|
||||
@@ -1294,8 +1295,7 @@ fn xml_http_request_response_document_uses_response_url_and_requester_origin() {
|
||||
"https://requester.example/page/index.html",
|
||||
"<!doctype html><html><body></body></html>",
|
||||
);
|
||||
vm.set_timezone_override_and_sync_surface(Some("UTC"))
|
||||
.unwrap();
|
||||
environment.set_timezone(Some("UTC")).unwrap();
|
||||
vm.eval(&format!(
|
||||
r#"(() => {{
|
||||
const frame = document.createElement('iframe');
|
||||
@@ -1512,6 +1512,7 @@ fn xml_http_request_document_response_requires_an_eligible_mime_and_well_formed_
|
||||
|
||||
#[test]
|
||||
fn xhr_streamed_response_documents_keep_distinct_source_modification_times() {
|
||||
let environment = moli_v8_platform::ProcessEnvironmentOwner::default();
|
||||
for (mime, response_type) in [
|
||||
("application/xml", ""),
|
||||
("application/xml", "document"),
|
||||
@@ -1520,8 +1521,7 @@ fn xhr_streamed_response_documents_keep_distinct_source_modification_times() {
|
||||
let mut vm = new_storage_test_vm("https://xhr-document-modified.test/");
|
||||
vm.document_runtime
|
||||
.set_document_source_last_modified(Some(5_025_000.0));
|
||||
vm.set_timezone_override_and_sync_surface(Some("UTC"))
|
||||
.unwrap();
|
||||
environment.set_timezone(Some("UTC")).unwrap();
|
||||
vm.set_fetch_subresource_interception(
|
||||
true,
|
||||
Some(crate::types::SubresourceResourceType::Xhr),
|
||||
@@ -1633,8 +1633,7 @@ fn xhr_streamed_response_documents_keep_distinct_source_modification_times() {
|
||||
.unwrap(),
|
||||
r#"["11/06/1994 08:49:37","01/01/1970 00:00:00","01/01/1970 01:23:45",true]"#
|
||||
);
|
||||
vm.set_timezone_override_and_sync_surface(Some("Asia/Shanghai"))
|
||||
.unwrap();
|
||||
environment.set_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
assert_eq!(
|
||||
vm.eval("__responseDocuments[0].lastModified").unwrap(),
|
||||
"11/06/1994 16:49:37"
|
||||
|
||||
@@ -561,17 +561,6 @@ impl PendingSubresourceFetchState {
|
||||
self.request_origin.clone()
|
||||
}
|
||||
|
||||
pub(super) fn response_request_origin<'a>(
|
||||
&self,
|
||||
redirects: impl IntoIterator<Item = (&'a Url, &'a Url)>,
|
||||
) -> moli_url::WebOrigin {
|
||||
let origin = self.request_origin();
|
||||
if self.request_mode != moli_fetch::RequestMode::Cors {
|
||||
return origin;
|
||||
}
|
||||
crate::network_host::cors_request_origin_after_redirects(&origin, redirects)
|
||||
}
|
||||
|
||||
pub(super) fn detach_keepalive_window_fetch(&mut self) -> bool {
|
||||
let PendingSubresourceExecutionContext::WindowFetch(context) = &self.execution_context
|
||||
else {
|
||||
|
||||
@@ -240,8 +240,7 @@ pub(super) fn fetch_worker_import_source_blocking(
|
||||
validate_fetch_response_security_policy_for_origin(
|
||||
initiator_url,
|
||||
&WebOrigin::from_url(initiator_url),
|
||||
&response.final_url,
|
||||
&response.headers,
|
||||
&response.head(),
|
||||
RequestMode::NoCors,
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
policy_context,
|
||||
|
||||
@@ -72,8 +72,7 @@ use crate::network_host::{
|
||||
XHR_SEND_FLAG_SLOT, XHR_TIMEOUT_SLOT, XHR_TIMEOUT_START_MS_SLOT, XHR_TIMEOUT_TIMER_SLOT,
|
||||
XHR_URL_SLOT, XHR_WITH_CREDENTIALS_SLOT, append_default_body_content_type, apply_xhr_failure,
|
||||
apply_xhr_response, apply_xhr_response_body_source, apply_xhr_timeout, apply_xhr_upload_event,
|
||||
capture_xhr_upload_listener_flag, close_pending_network_body_stream,
|
||||
cors_request_origin_after_redirects, dispatch_xhr_loadstart,
|
||||
capture_xhr_upload_listener_flag, close_pending_network_body_stream, dispatch_xhr_loadstart,
|
||||
enqueue_pending_network_body_chunk, error_pending_network_body_stream_with_reason,
|
||||
extract_subresource_auth_challenge,
|
||||
fetch_browser_subresource_raw_stream_with_preflight_headers_and_network_metadata,
|
||||
@@ -82,8 +81,7 @@ use crate::network_host::{
|
||||
is_cors_policy_failure_message, parse_fetch_init, prepare_xhr_send_body_from_args,
|
||||
request_input_snapshot, request_object_credentials_mode, resolve_context_url,
|
||||
set_xhr_state_bool, set_xhr_state_number, throw_synchronous_xhr_failure,
|
||||
validate_cors_response_for_origin, validate_fetch_response_headers_for_origin,
|
||||
validate_fetch_response_security_policy_for_origin,
|
||||
validate_fetch_response_headers_for_origin, validate_fetch_response_security_policy_for_origin,
|
||||
validate_fetch_response_security_policy_with_body_classified_for_origin,
|
||||
xhr_author_request_headers, xhr_ensure_send_allowed, xhr_state_bool_property,
|
||||
xhr_state_number_property, xhr_state_string_property,
|
||||
|
||||
@@ -718,6 +718,7 @@ fn send_synchronous_worker_xhr(
|
||||
throw_synchronous_xhr_failure(scope, xhr, &request_url_text, "NetworkError");
|
||||
return;
|
||||
}
|
||||
let request_origin = WebOrigin::from_url(&prepared.document_url);
|
||||
record_worker_subresource_success(
|
||||
&state.borrow(),
|
||||
prepared.document_url,
|
||||
@@ -731,8 +732,7 @@ fn send_synchronous_worker_xhr(
|
||||
);
|
||||
response.headers = filter_cors_exposed_response_headers_for_origin(
|
||||
&request_origin,
|
||||
&response.final_url,
|
||||
&response.headers,
|
||||
&response.head(),
|
||||
prepared.credentials_mode,
|
||||
);
|
||||
apply_xhr_response(scope, xhr, response);
|
||||
|
||||
@@ -672,6 +672,7 @@ fn start_worker_module_graph_fetch(
|
||||
.with_credentials_mode(request.credentials_mode());
|
||||
let requested_url = request.url().clone();
|
||||
let request_initiator_url = request.initiator_url().clone();
|
||||
let request_credentials_mode = request.credentials_mode();
|
||||
let requested_module_type = request.module_type().map(str::to_owned);
|
||||
let requested_kind = request.kind();
|
||||
let response_content_security_policies = content_security_policies.clone();
|
||||
|
||||
Reference in New Issue
Block a user