mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 16:00:45 +00:00
fix(fetch): honor sandboxed opaque request origins
This commit is contained in:
@@ -14,9 +14,7 @@ use moli_cookie_jar::{
|
||||
NetworkCookieRequestContext, SharedBrowserCookieStore, StoredCookieQueryReport,
|
||||
StoredCookieSetReport, same_site_urls,
|
||||
};
|
||||
use moli_url::{
|
||||
is_potentially_trustworthy_url, origin_ascii_serialization, same_origin, tuple_origin_url,
|
||||
};
|
||||
use moli_url::{WebOrigin, is_potentially_trustworthy_url, same_origin};
|
||||
use moli_url_policy::ensure_http_network_transport_url;
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
@@ -418,21 +416,17 @@ fn request_origin_header_value(
|
||||
if !request_needs_origin_header(request, request_url, redirect_chain) {
|
||||
return None;
|
||||
}
|
||||
let Some(initiator_url) = request.cookie_context.initiator_url.as_ref() else {
|
||||
let Some(request_origin) = request.request_origin() else {
|
||||
return Some("null".to_owned());
|
||||
};
|
||||
let Some(initiator_origin_url) = tuple_origin_url(initiator_url) else {
|
||||
if request_origin.is_opaque() {
|
||||
return Some("null".to_owned());
|
||||
};
|
||||
}
|
||||
Some(
|
||||
if request_has_redirect_tainted_origin(
|
||||
initiator_origin_url.as_ref(),
|
||||
&request.url,
|
||||
redirect_chain,
|
||||
) {
|
||||
if request_has_redirect_tainted_origin(&request_origin, &request.url, redirect_chain) {
|
||||
"null".to_owned()
|
||||
} else {
|
||||
origin_ascii_serialization(initiator_origin_url.as_ref())
|
||||
request_origin.ascii_serialization().to_owned()
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -452,22 +446,18 @@ fn request_needs_origin_header(
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(initiator_url) = request.cookie_context.initiator_url.as_ref() else {
|
||||
let Some(request_origin) = request.request_origin() else {
|
||||
return true;
|
||||
};
|
||||
let Some(initiator_origin_url) = tuple_origin_url(initiator_url) else {
|
||||
if request_origin.is_opaque() {
|
||||
return true;
|
||||
};
|
||||
!same_origin(initiator_origin_url.as_ref(), request_url)
|
||||
|| request_has_redirect_tainted_origin(
|
||||
initiator_origin_url.as_ref(),
|
||||
&request.url,
|
||||
redirect_chain,
|
||||
)
|
||||
}
|
||||
!request_origin.same_origin_url(request_url)
|
||||
|| request_has_redirect_tainted_origin(&request_origin, &request.url, redirect_chain)
|
||||
}
|
||||
|
||||
fn request_has_redirect_tainted_origin(
|
||||
request_origin_url: &Url,
|
||||
request_origin: &WebOrigin,
|
||||
original_request_url: &Url,
|
||||
redirect_chain: &[RedirectInfo],
|
||||
) -> bool {
|
||||
@@ -475,7 +465,7 @@ fn request_has_redirect_tainted_origin(
|
||||
|
||||
for redirect in redirect_chain {
|
||||
let next_url = &redirect.to_url;
|
||||
if !same_origin(next_url, last_url) && !same_origin(request_origin_url, last_url) {
|
||||
if !same_origin(next_url, last_url) && !request_origin.same_origin_url(last_url) {
|
||||
return true;
|
||||
}
|
||||
last_url = next_url;
|
||||
@@ -531,14 +521,18 @@ fn append_browser_storage_access_header(
|
||||
}
|
||||
|
||||
fn request_sec_fetch_site(request: &Request, request_url: &Url) -> String {
|
||||
let Some(initiator_url) = request.cookie_context.initiator_url.as_ref() else {
|
||||
let Some(request_origin) = request.request_origin() else {
|
||||
return "none".to_owned();
|
||||
};
|
||||
let initiator_url =
|
||||
tuple_origin_url(initiator_url).unwrap_or(std::borrow::Cow::Borrowed(initiator_url));
|
||||
if same_origin(initiator_url.as_ref(), request_url) {
|
||||
if request_origin.is_opaque() {
|
||||
return "cross-site".to_owned();
|
||||
}
|
||||
let Ok(origin_url) = Url::parse(request_origin.ascii_serialization()) else {
|
||||
return "cross-site".to_owned();
|
||||
};
|
||||
if same_origin(&origin_url, request_url) {
|
||||
"same-origin".to_owned()
|
||||
} else if same_site_urls(initiator_url.as_ref(), request_url, true) {
|
||||
} else if same_site_urls(&origin_url, request_url, true) {
|
||||
"same-site".to_owned()
|
||||
} else {
|
||||
"cross-site".to_owned()
|
||||
@@ -1361,6 +1355,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opaque_request_origin_is_cross_origin_without_hiding_referrer_url() {
|
||||
let config = FetchConfig::default();
|
||||
let request_url = url("https://app.test/data");
|
||||
let request = Request::new("GET", request_url.as_str(), None, Vec::new())
|
||||
.unwrap()
|
||||
.with_initiator_url(&url("https://app.test/sandboxed-frame"))
|
||||
.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);
|
||||
|
||||
assert_eq!(header_value(&headers, "origin").as_deref(), Some("null"));
|
||||
assert_eq!(
|
||||
header_value(&headers, "sec-fetch-site").as_deref(),
|
||||
Some("cross-site")
|
||||
);
|
||||
assert_eq!(
|
||||
header_value(&headers, "referer").as_deref(),
|
||||
Some("https://app.test/sandboxed-frame")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post_navigation_without_an_initiator_serializes_opaque_origin() {
|
||||
let config = FetchConfig::default();
|
||||
|
||||
@@ -9,7 +9,7 @@ use moli_cookie_jar::{
|
||||
NetworkSiteContextMetadata, NetworkSiteContextTrackMetadata, redirect_types_for_request,
|
||||
site_context_downgrade_type,
|
||||
};
|
||||
use moli_url::same_origin;
|
||||
use moli_url::{WebOrigin, same_origin};
|
||||
use url::Url;
|
||||
|
||||
use crate::{FetchConfig, network_fetch_result::NetworkObservationRecorder};
|
||||
@@ -33,6 +33,7 @@ pub struct Request {
|
||||
pub request_mode: RequestMode,
|
||||
pub redirect_mode: RequestRedirectMode,
|
||||
pub credentials_mode: RequestCredentialsMode,
|
||||
request_origin: Option<WebOrigin>,
|
||||
network_partition_key: Option<String>,
|
||||
auth: Option<RequestAuth>,
|
||||
pub cookie_context: NetworkCookieRequestContext,
|
||||
@@ -383,6 +384,7 @@ impl Request {
|
||||
request_mode: RequestMode::Navigate,
|
||||
redirect_mode: RequestRedirectMode::Follow,
|
||||
credentials_mode: RequestCredentialsMode::Include,
|
||||
request_origin: None,
|
||||
network_partition_key: None,
|
||||
auth: None,
|
||||
cookie_context: NetworkCookieRequestContext::top_level_navigation("GET"),
|
||||
@@ -410,6 +412,7 @@ impl Request {
|
||||
request_mode: RequestMode::Navigate,
|
||||
redirect_mode: RequestRedirectMode::Follow,
|
||||
credentials_mode: RequestCredentialsMode::Include,
|
||||
request_origin: None,
|
||||
network_partition_key: None,
|
||||
auth: None,
|
||||
cookie_context: NetworkCookieRequestContext::top_level_navigation("GET"),
|
||||
@@ -458,6 +461,7 @@ impl Request {
|
||||
request_mode: RequestMode::Cors,
|
||||
redirect_mode: RequestRedirectMode::Follow,
|
||||
credentials_mode: RequestCredentialsMode::Include,
|
||||
request_origin: None,
|
||||
network_partition_key: None,
|
||||
auth: None,
|
||||
cookie_context: NetworkCookieRequestContext::subresource(method),
|
||||
@@ -655,10 +659,8 @@ impl Request {
|
||||
RequestCredentialsMode::Include => true,
|
||||
RequestCredentialsMode::Omit => false,
|
||||
RequestCredentialsMode::SameOrigin => self
|
||||
.cookie_context
|
||||
.initiator_url
|
||||
.as_ref()
|
||||
.is_none_or(|initiator_url| same_origin(initiator_url, request_url)),
|
||||
.request_origin()
|
||||
.is_none_or(|origin| origin.same_origin_url(request_url)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,6 +759,26 @@ impl Request {
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the Fetch request's client origin without changing the
|
||||
/// initiator URL used for referrer and cookie-site calculations.
|
||||
pub fn with_request_origin(mut self, request_origin: WebOrigin) -> Self {
|
||||
self.request_origin = Some(request_origin);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn request_origin(&self) -> Option<WebOrigin> {
|
||||
self.request_origin.clone().or_else(|| {
|
||||
self.cookie_context
|
||||
.initiator_url
|
||||
.as_ref()
|
||||
.map(WebOrigin::from_url)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn explicit_request_origin(&self) -> Option<&WebOrigin> {
|
||||
self.request_origin.as_ref()
|
||||
}
|
||||
|
||||
pub fn with_site_for_cookies_url(mut self, site_for_cookies_url: &Url) -> Self {
|
||||
self.cookie_context = self
|
||||
.cookie_context
|
||||
|
||||
@@ -105,6 +105,19 @@ fn request_credentials_mode_controls_cross_origin_cookie_access() {
|
||||
assert!(!omit_request.allows_credentials_for_url(&same_origin_url));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opaque_request_origin_disallows_same_origin_credentials_for_same_url_origin() {
|
||||
let document_url = Url::parse("https://example.com/app/page.html").unwrap();
|
||||
let request_url = Url::parse("https://example.com/api/data").unwrap();
|
||||
let request = Request::new("GET", request_url.as_str(), None, vec![])
|
||||
.unwrap()
|
||||
.with_initiator_url(&document_url)
|
||||
.with_request_origin(moli_url::WebOrigin::Opaque)
|
||||
.with_credentials_mode(RequestCredentialsMode::SameOrigin);
|
||||
|
||||
assert!(!request.allows_credentials_for_url(&request_url));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_same_site_override_sets_both_site_context_tracks() {
|
||||
let context = NetworkCookieRequestContext::subresource("GET")
|
||||
|
||||
@@ -151,6 +151,14 @@ impl JsContextHost {
|
||||
Some((entry.frame_id().to_owned(), document_url))
|
||||
}
|
||||
|
||||
pub(crate) fn child_browsing_context_request_origin(
|
||||
&self,
|
||||
handle: DomHandle,
|
||||
) -> Option<String> {
|
||||
let document_url = self.child_browsing_context_current_url(handle)?;
|
||||
self.child_browsing_context_document_origin_for_url(handle, &document_url)
|
||||
}
|
||||
|
||||
pub(crate) fn active_child_subresource_request_scope(
|
||||
&self,
|
||||
) -> Option<(DomHandle, String, Url)> {
|
||||
|
||||
@@ -562,6 +562,7 @@ impl JsContextHost {
|
||||
keepalive: bool,
|
||||
connect_policy: crate::document_runtime::DocumentConnectPolicySnapshot,
|
||||
csp_report_context: crate::network_host::WindowCspReportRequestContext,
|
||||
request_origin: moli_url::WebOrigin,
|
||||
credentials_mode: moli_fetch::RequestCredentialsMode,
|
||||
request_mode: moli_fetch::RequestMode,
|
||||
network_partition_key: Option<String>,
|
||||
@@ -597,6 +598,7 @@ impl JsContextHost {
|
||||
keepalive,
|
||||
connect_policy,
|
||||
csp_report_context,
|
||||
request_origin,
|
||||
),
|
||||
),
|
||||
deferred_request_started: false,
|
||||
@@ -823,6 +825,7 @@ impl JsContextHost {
|
||||
keepalive: bool,
|
||||
connect_policy: crate::document_runtime::DocumentConnectPolicySnapshot,
|
||||
csp_report_context: crate::network_host::WindowCspReportRequestContext,
|
||||
request_origin: moli_url::WebOrigin,
|
||||
cancel_handle: Option<moli_fetch::FetchCancelHandle>,
|
||||
credentials_mode: moli_fetch::RequestCredentialsMode,
|
||||
request_mode: moli_fetch::RequestMode,
|
||||
@@ -863,6 +866,7 @@ impl JsContextHost {
|
||||
keepalive,
|
||||
connect_policy,
|
||||
csp_report_context,
|
||||
request_origin,
|
||||
),
|
||||
),
|
||||
deferred_request_started: defer_request_started,
|
||||
|
||||
@@ -135,7 +135,9 @@ pub(crate) use self::request_scope::effective_subresource_policy_context;
|
||||
pub(in crate::network_host) use self::request_scope::{
|
||||
XHR_CHILD_CONTEXT_HANDLE_SLOT, active_subresource_network_partition_key,
|
||||
effective_subresource_referrer_policy, effective_subresource_request_scope,
|
||||
observe_subresource_request_cookie_report, subresource_request_scope_for_owner,
|
||||
observe_subresource_request_cookie_report,
|
||||
observe_subresource_request_cookie_report_for_origin, subresource_request_origin_for_owner,
|
||||
subresource_request_scope_for_owner,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::response::materialize_response_object;
|
||||
@@ -148,16 +150,21 @@ pub(crate) use self::response::{
|
||||
build_fetch_response_object_from_subresource_body_for_request_mode,
|
||||
build_filtered_cached_response_object,
|
||||
build_navigation_preload_response_object_from_stream_for_request_mode,
|
||||
cors_preflight_request_headers, filter_cors_exposed_response_headers,
|
||||
is_cors_policy_failure_message, materialize_response_object_body,
|
||||
materialize_response_object_body_with_chunk_callback, materialize_response_object_head,
|
||||
cors_preflight_request_headers_for_origin, 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,
|
||||
materialize_response_object_head,
|
||||
materialize_response_object_head_for_service_worker_respond_with,
|
||||
materialized_body_bytes_from_value, response_constructor_callback,
|
||||
validate_cors_preflight_response, validate_cors_response,
|
||||
validate_cors_preflight_response_for_origin, validate_cors_response,
|
||||
validate_cors_response_for_origin,
|
||||
validate_cross_origin_embedder_and_document_isolation_policy,
|
||||
validate_cross_origin_resource_policy, 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,
|
||||
validate_fetch_response_security_policy_with_body_classified_for_origin,
|
||||
validate_fetch_response_security_policy_with_body_for_origin,
|
||||
};
|
||||
pub(crate) use self::stylesheet_subresource::{
|
||||
StylesheetSubresourceFetchStart, start_stylesheet_subresource_fetch,
|
||||
|
||||
@@ -324,14 +324,14 @@ fn validate_actual_cors_response_parts(
|
||||
response_url: &url::Url,
|
||||
response_headers: &[(String, String)],
|
||||
) -> Result<(), String> {
|
||||
let Some(initiator_url) = request.cookie_context.initiator_url.as_ref() else {
|
||||
let Some(request_origin) = request.request_origin() else {
|
||||
return Ok(());
|
||||
};
|
||||
if request.request_mode == RequestMode::NoCors {
|
||||
return Ok(());
|
||||
}
|
||||
validate_cors_response(
|
||||
initiator_url,
|
||||
validate_cors_response_for_origin(
|
||||
&request_origin,
|
||||
response_url,
|
||||
response_headers,
|
||||
request.credentials_mode,
|
||||
@@ -420,10 +420,10 @@ async fn run_cors_preflight_if_needed(
|
||||
preflight_request_headers: &[(String, String)],
|
||||
preflight_observer: Option<&CorsPreflightNetworkObserver>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(initiator_url) = request.cookie_context.initiator_url.clone()
|
||||
if let Some(request_origin) = request.request_origin()
|
||||
&& request.request_mode != RequestMode::NoCors
|
||||
&& let Some(preflight_headers) = cors_preflight_request_headers(
|
||||
&initiator_url,
|
||||
&& let Some(preflight_headers) = cors_preflight_request_headers_for_origin(
|
||||
&request_origin,
|
||||
&request.url,
|
||||
&request.method,
|
||||
preflight_request_headers,
|
||||
@@ -433,9 +433,12 @@ async fn run_cors_preflight_if_needed(
|
||||
let mut preflight_request =
|
||||
Request::new("OPTIONS", request.url.as_str(), None, preflight_headers)
|
||||
.map_err(|error| format!("cors preflight: failed to build request: {error}"))?
|
||||
.with_initiator_url(&initiator_url)
|
||||
.with_credentials_mode(RequestCredentialsMode::SameOrigin)
|
||||
.with_network_partition_key(request.network_partition_key().map(str::to_owned));
|
||||
if let Some(initiator_url) = request.cookie_context.initiator_url.as_ref() {
|
||||
preflight_request = preflight_request.with_initiator_url(initiator_url);
|
||||
}
|
||||
preflight_request = preflight_request.with_request_origin(request_origin.clone());
|
||||
if let Some(metadata) = request.browser_request_metadata() {
|
||||
preflight_request = preflight_request.with_browser_request_metadata(metadata);
|
||||
} else {
|
||||
@@ -475,8 +478,8 @@ async fn run_cors_preflight_if_needed(
|
||||
preflight_response.final_url
|
||||
));
|
||||
}
|
||||
validate_cors_preflight_response(
|
||||
&initiator_url,
|
||||
validate_cors_preflight_response_for_origin(
|
||||
&request_origin,
|
||||
&preflight_response.final_url,
|
||||
&request.method,
|
||||
preflight_request_headers,
|
||||
|
||||
@@ -11,9 +11,10 @@ pub(super) fn record_intercepted_fetch(
|
||||
resolver: v8::Local<'_, v8::PromiseResolver>,
|
||||
prepared: PreparedWindowFetchRequest,
|
||||
) {
|
||||
let request_cookie_report = observe_subresource_request_cookie_report(
|
||||
let request_cookie_report = observe_subresource_request_cookie_report_for_origin(
|
||||
prepared.resource_loader.request_client(),
|
||||
&prepared.document_url,
|
||||
&prepared.request_origin,
|
||||
&prepared.resolved_url,
|
||||
&prepared.method,
|
||||
prepared.credentials_mode,
|
||||
@@ -24,6 +25,7 @@ pub(super) fn record_intercepted_fetch(
|
||||
prepared.keepalive,
|
||||
prepared.connect_policy,
|
||||
prepared.csp_report_context,
|
||||
prepared.request_origin,
|
||||
prepared.credentials_mode,
|
||||
prepared.request_mode,
|
||||
prepared.network_partition_key,
|
||||
@@ -203,6 +205,7 @@ pub(super) fn spawn_network_fetch(
|
||||
)
|
||||
.map_err(|error| error.to_string())?
|
||||
.with_initiator_url(&prepared.document_url)
|
||||
.with_request_origin(prepared.request_origin.clone())
|
||||
.with_request_mode(prepared.request_mode)
|
||||
.with_credentials_mode(prepared.credentials_mode)
|
||||
.with_network_partition_key(prepared.network_partition_key.clone())
|
||||
@@ -219,9 +222,10 @@ pub(super) fn spawn_network_fetch(
|
||||
.with_browser_request_metadata(BrowserRequestMetadata::Fetch)
|
||||
.with_subframe_context(prepared.frame_id.is_some());
|
||||
|
||||
let request_cookie_report = observe_subresource_request_cookie_report(
|
||||
let request_cookie_report = observe_subresource_request_cookie_report_for_origin(
|
||||
prepared.resource_loader.request_client(),
|
||||
&prepared.document_url,
|
||||
&prepared.request_origin,
|
||||
&prepared.resolved_url,
|
||||
&prepared.method,
|
||||
prepared.credentials_mode,
|
||||
@@ -234,8 +238,8 @@ pub(super) fn spawn_network_fetch(
|
||||
};
|
||||
let cancel_handle = FetchCancelHandle::new();
|
||||
let requires_preflight = prepared.request_mode != moli_fetch::RequestMode::NoCors
|
||||
&& crate::network_host::cors_preflight_request_headers(
|
||||
&prepared.document_url,
|
||||
&& crate::network_host::cors_preflight_request_headers_for_origin(
|
||||
&prepared.request_origin,
|
||||
&prepared.resolved_url,
|
||||
&prepared.method,
|
||||
&prepared.cors_preflight_request_headers,
|
||||
@@ -247,6 +251,7 @@ pub(super) fn spawn_network_fetch(
|
||||
prepared.keepalive,
|
||||
prepared.connect_policy,
|
||||
prepared.csp_report_context,
|
||||
prepared.request_origin.clone(),
|
||||
Some(cancel_handle.clone()),
|
||||
prepared.credentials_mode,
|
||||
prepared.request_mode,
|
||||
|
||||
@@ -8,6 +8,7 @@ pub(super) struct PreparedWindowFetchRequest {
|
||||
pub(super) connect_policy: crate::document_runtime::DocumentConnectPolicySnapshot,
|
||||
pub(super) csp_report_context: crate::network_host::WindowCspReportRequestContext,
|
||||
pub(super) document_url: url::Url,
|
||||
pub(super) request_origin: moli_url::WebOrigin,
|
||||
pub(super) network_partition_key: Option<String>,
|
||||
pub(super) document_referrer_policy: Option<String>,
|
||||
pub(super) policy_context: crate::types::SubresourcePolicyContext,
|
||||
@@ -53,6 +54,8 @@ pub(super) fn prepare_window_fetch_request<'s>(
|
||||
.ok_or_else(|| "fetch: Document resource loader is unavailable".to_owned())?;
|
||||
let (frame_id, document_url) = subresource_request_scope_for_owner(scope, host, request_scope)
|
||||
.ok_or_else(|| "fetch: Window execution context owner is retired".to_owned())?;
|
||||
let request_origin = subresource_request_origin_for_owner(scope, host, request_scope)
|
||||
.ok_or_else(|| "fetch: Window request origin is unavailable".to_owned())?;
|
||||
let connect_policy = host
|
||||
.document_connect_policy_snapshot_for_owner(request_scope)
|
||||
.ok_or_else(|| "fetch: document policy context is unavailable".to_owned())?;
|
||||
@@ -78,6 +81,7 @@ pub(super) fn prepare_window_fetch_request<'s>(
|
||||
connect_policy,
|
||||
csp_report_context,
|
||||
document_url,
|
||||
request_origin,
|
||||
network_partition_key,
|
||||
document_referrer_policy,
|
||||
policy_context,
|
||||
|
||||
@@ -26,9 +26,10 @@ pub(super) fn dispatch_service_worker_fetch(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let request_cookie_report = observe_subresource_request_cookie_report(
|
||||
let request_cookie_report = observe_subresource_request_cookie_report_for_origin(
|
||||
prepared.resource_loader.request_client(),
|
||||
&prepared.document_url,
|
||||
&prepared.request_origin,
|
||||
&prepared.resolved_url,
|
||||
&prepared.method,
|
||||
prepared.credentials_mode,
|
||||
@@ -41,8 +42,8 @@ pub(super) fn dispatch_service_worker_fetch(
|
||||
policy_context: prepared.policy_context,
|
||||
};
|
||||
let requires_preflight = prepared.request_mode != moli_fetch::RequestMode::NoCors
|
||||
&& crate::network_host::cors_preflight_request_headers(
|
||||
&prepared.document_url,
|
||||
&& crate::network_host::cors_preflight_request_headers_for_origin(
|
||||
&prepared.request_origin,
|
||||
&prepared.resolved_url,
|
||||
&prepared.method,
|
||||
&prepared.cors_preflight_request_headers,
|
||||
@@ -55,6 +56,7 @@ pub(super) fn dispatch_service_worker_fetch(
|
||||
prepared.keepalive,
|
||||
prepared.connect_policy.clone(),
|
||||
prepared.csp_report_context.clone(),
|
||||
prepared.request_origin.clone(),
|
||||
Some(cancel_handle.clone()),
|
||||
prepared.credentials_mode,
|
||||
prepared.request_mode,
|
||||
@@ -93,6 +95,7 @@ pub(super) fn dispatch_service_worker_fetch(
|
||||
referrer_policy: prepared.referrer_policy.clone(),
|
||||
integrity: prepared.integrity.clone(),
|
||||
keepalive: prepared.keepalive,
|
||||
request_origin: Some(prepared.request_origin.clone()),
|
||||
},
|
||||
);
|
||||
let dispatch = ServiceWorkerFetchDispatch {
|
||||
|
||||
@@ -9,10 +9,29 @@ pub(in crate::network_host) fn observe_subresource_request_cookie_report(
|
||||
request_url: &url::Url,
|
||||
method: &str,
|
||||
credentials_mode: moli_fetch::RequestCredentialsMode,
|
||||
) -> Option<moli_cookie_jar::StoredCookieQueryReport> {
|
||||
observe_subresource_request_cookie_report_for_origin(
|
||||
loader,
|
||||
document_url,
|
||||
&moli_url::WebOrigin::from_url(document_url),
|
||||
request_url,
|
||||
method,
|
||||
credentials_mode,
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::network_host) fn observe_subresource_request_cookie_report_for_origin(
|
||||
loader: &crate::network::ResourceRequestClient,
|
||||
document_url: &url::Url,
|
||||
request_origin: &moli_url::WebOrigin,
|
||||
request_url: &url::Url,
|
||||
method: &str,
|
||||
credentials_mode: moli_fetch::RequestCredentialsMode,
|
||||
) -> Option<moli_cookie_jar::StoredCookieQueryReport> {
|
||||
let request = Request::new(method, request_url.as_str(), None, Vec::new())
|
||||
.ok()?
|
||||
.with_initiator_url(document_url)
|
||||
.with_request_origin(request_origin.clone())
|
||||
.with_credentials_mode(credentials_mode);
|
||||
if !request.allows_credentials_for_url(request_url) {
|
||||
return None;
|
||||
@@ -150,6 +169,28 @@ pub(in crate::network_host) fn subresource_request_scope_for_owner(
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::network_host) fn subresource_request_origin_for_owner(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host: &JsContextHost,
|
||||
owner: crate::native_bridge::OwnerDispatchScope,
|
||||
) -> Option<moli_url::WebOrigin> {
|
||||
match owner {
|
||||
crate::native_bridge::OwnerDispatchScope::Top => {
|
||||
Some(moli_url::WebOrigin::from_url(host.document_url()))
|
||||
}
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle) => host
|
||||
.child_browsing_context_request_origin(handle)
|
||||
.map(|origin| moli_url::WebOrigin::from_ascii_serialization(&origin)),
|
||||
crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id) => host
|
||||
.lightweight_popup_origin(popup_id)
|
||||
.map(|origin| moli_url::WebOrigin::from_ascii_serialization(&origin))
|
||||
.or_else(|| {
|
||||
host.lightweight_popup_request_base_url(scope, popup_id)
|
||||
.map(|document_url| moli_url::WebOrigin::from_url(&document_url))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::network_host) fn effective_subresource_referrer_policy(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host: &JsContextHost,
|
||||
|
||||
@@ -13,13 +13,17 @@ pub(crate) use self::bindings::response_constructor_callback;
|
||||
pub(in crate::network_host) use self::bindings::{ParsedResponseInit, parse_response_init};
|
||||
pub(super) use self::body_methods::install_response_body_methods;
|
||||
pub(crate) use self::cors::{
|
||||
FetchResponseSecurityViolation, cors_preflight_request_headers,
|
||||
filter_cors_exposed_response_headers, is_cors_policy_failure_message,
|
||||
validate_cors_preflight_response, validate_cors_response,
|
||||
FetchResponseSecurityViolation, cors_preflight_request_headers_for_origin,
|
||||
filter_cors_exposed_response_headers, filter_cors_exposed_response_headers_for_origin,
|
||||
is_cors_policy_failure_message, validate_cors_preflight_response_for_origin,
|
||||
validate_cors_response, validate_cors_response_for_origin,
|
||||
validate_cross_origin_embedder_and_document_isolation_policy,
|
||||
validate_cross_origin_resource_policy, 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,
|
||||
validate_fetch_response_security_policy_with_body_classified_for_origin,
|
||||
validate_fetch_response_security_policy_with_body_for_origin,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::materialize::materialize_response_object;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use moli_cookie_jar::same_site_urls;
|
||||
use moli_fetch::{RequestCredentialsMode, RequestMode};
|
||||
use moli_url::{origin_ascii_serialization, same_origin};
|
||||
use moli_url::{WebOrigin, origin_ascii_serialization, same_origin};
|
||||
use moli_web_mime::{
|
||||
response_header_value, response_header_values, should_opaque_response_be_blocked_by_orb,
|
||||
should_opaque_response_be_blocked_by_orb_with_body,
|
||||
@@ -42,14 +42,28 @@ pub(crate) fn validate_cors_response(
|
||||
response_headers: &[(String, String)],
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
) -> Result<(), String> {
|
||||
if same_origin(document_url, response_url) {
|
||||
validate_cors_response_for_origin(
|
||||
&WebOrigin::from_url(document_url),
|
||||
response_url,
|
||||
response_headers,
|
||||
credentials_mode,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_cors_response_for_origin(
|
||||
request_origin: &WebOrigin,
|
||||
response_url: &url::Url,
|
||||
response_headers: &[(String, String)],
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
) -> Result<(), String> {
|
||||
if request_origin.same_origin_url(response_url) {
|
||||
return Ok(());
|
||||
}
|
||||
if !matches!(response_url.scheme(), "http" | "https") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let origin = origin_ascii_serialization(document_url);
|
||||
let origin = request_origin.ascii_serialization();
|
||||
let Some(allow_origin) = response_header_value(response_headers, "access-control-allow-origin")
|
||||
else {
|
||||
return Err(format!(
|
||||
@@ -117,6 +131,34 @@ pub(crate) fn validate_fetch_response_security_policy(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_fetch_response_security_policy_for_origin(
|
||||
document_url: &url::Url,
|
||||
request_origin: &WebOrigin,
|
||||
response_url: &url::Url,
|
||||
response_headers: &[(String, String)],
|
||||
request_mode: RequestMode,
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
policy_context: crate::types::SubresourcePolicyContext,
|
||||
) -> Result<(), String> {
|
||||
if request_mode == RequestMode::NoCors {
|
||||
validate_fetch_response_security_policy(
|
||||
document_url,
|
||||
response_url,
|
||||
response_headers,
|
||||
request_mode,
|
||||
credentials_mode,
|
||||
policy_context,
|
||||
)
|
||||
} else {
|
||||
validate_cors_response_for_origin(
|
||||
request_origin,
|
||||
response_url,
|
||||
response_headers,
|
||||
credentials_mode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_fetch_response_security_policy_with_body(
|
||||
document_url: &url::Url,
|
||||
response_url: &url::Url,
|
||||
@@ -178,6 +220,60 @@ pub(crate) fn validate_fetch_response_security_policy_with_body_classified(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_fetch_response_security_policy_with_body_for_origin(
|
||||
document_url: &url::Url,
|
||||
request_origin: &WebOrigin,
|
||||
response_url: &url::Url,
|
||||
response_headers: &[(String, String)],
|
||||
response_body: &[u8],
|
||||
request_mode: RequestMode,
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
policy_context: crate::types::SubresourcePolicyContext,
|
||||
) -> Result<(), String> {
|
||||
validate_fetch_response_security_policy_with_body_classified_for_origin(
|
||||
document_url,
|
||||
request_origin,
|
||||
response_url,
|
||||
response_headers,
|
||||
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(
|
||||
document_url: &url::Url,
|
||||
request_origin: &WebOrigin,
|
||||
response_url: &url::Url,
|
||||
response_headers: &[(String, String)],
|
||||
response_body: &[u8],
|
||||
request_mode: RequestMode,
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
policy_context: crate::types::SubresourcePolicyContext,
|
||||
) -> Result<(), FetchResponseSecurityViolation> {
|
||||
if request_mode == RequestMode::NoCors {
|
||||
validate_fetch_response_security_policy_with_body_classified(
|
||||
document_url,
|
||||
response_url,
|
||||
response_headers,
|
||||
response_body,
|
||||
request_mode,
|
||||
credentials_mode,
|
||||
policy_context,
|
||||
)
|
||||
} else {
|
||||
validate_cors_response_for_origin(
|
||||
request_origin,
|
||||
response_url,
|
||||
response_headers,
|
||||
credentials_mode,
|
||||
)
|
||||
.map_err(FetchResponseSecurityViolation::Rejected)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_opaque_response_blocking(
|
||||
document_url: &url::Url,
|
||||
response_url: &url::Url,
|
||||
@@ -360,13 +456,28 @@ pub(crate) fn validate_cross_origin_resource_policy(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn cors_preflight_request_headers(
|
||||
document_url: &url::Url,
|
||||
request_url: &url::Url,
|
||||
method: &str,
|
||||
request_headers: &[(String, String)],
|
||||
) -> Option<Vec<(String, String)>> {
|
||||
if same_origin(document_url, request_url) {
|
||||
cors_preflight_request_headers_for_origin(
|
||||
&WebOrigin::from_url(document_url),
|
||||
request_url,
|
||||
method,
|
||||
request_headers,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn cors_preflight_request_headers_for_origin(
|
||||
request_origin: &WebOrigin,
|
||||
request_url: &url::Url,
|
||||
method: &str,
|
||||
request_headers: &[(String, String)],
|
||||
) -> Option<Vec<(String, String)>> {
|
||||
if request_origin.same_origin_url(request_url) {
|
||||
return None;
|
||||
}
|
||||
if !matches!(request_url.scheme(), "http" | "https") {
|
||||
@@ -392,6 +503,7 @@ pub(crate) fn cors_preflight_request_headers(
|
||||
Some(headers)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn validate_cors_preflight_response(
|
||||
document_url: &url::Url,
|
||||
response_url: &url::Url,
|
||||
@@ -399,14 +511,32 @@ pub(crate) fn validate_cors_preflight_response(
|
||||
request_headers: &[(String, String)],
|
||||
response_status: u16,
|
||||
response_headers: &[(String, String)],
|
||||
) -> Result<(), String> {
|
||||
validate_cors_preflight_response_for_origin(
|
||||
&WebOrigin::from_url(document_url),
|
||||
response_url,
|
||||
requested_method,
|
||||
request_headers,
|
||||
response_status,
|
||||
response_headers,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_cors_preflight_response_for_origin(
|
||||
request_origin: &WebOrigin,
|
||||
response_url: &url::Url,
|
||||
requested_method: &str,
|
||||
request_headers: &[(String, String)],
|
||||
response_status: u16,
|
||||
response_headers: &[(String, String)],
|
||||
) -> Result<(), String> {
|
||||
if !(200..300).contains(&response_status) {
|
||||
return Err(format!(
|
||||
"CORS preflight failed: response status {response_status}"
|
||||
));
|
||||
}
|
||||
validate_cors_response(
|
||||
document_url,
|
||||
validate_cors_response_for_origin(
|
||||
request_origin,
|
||||
response_url,
|
||||
response_headers,
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
@@ -464,7 +594,21 @@ pub(crate) fn filter_cors_exposed_response_headers(
|
||||
response_headers: &[(String, String)],
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
) -> Vec<(String, String)> {
|
||||
if same_origin(document_url, response_url) {
|
||||
filter_cors_exposed_response_headers_for_origin(
|
||||
&WebOrigin::from_url(document_url),
|
||||
response_url,
|
||||
response_headers,
|
||||
credentials_mode,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn filter_cors_exposed_response_headers_for_origin(
|
||||
request_origin: &WebOrigin,
|
||||
response_url: &url::Url,
|
||||
response_headers: &[(String, String)],
|
||||
credentials_mode: RequestCredentialsMode,
|
||||
) -> Vec<(String, String)> {
|
||||
if request_origin.same_origin_url(response_url) {
|
||||
return response_headers.to_vec();
|
||||
}
|
||||
if !matches!(response_url.scheme(), "http" | "https") {
|
||||
@@ -590,6 +734,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opaque_request_origin_requires_null_cors_opt_in_for_same_url_origin() {
|
||||
let response_url = url("https://example.test/data");
|
||||
|
||||
assert!(
|
||||
validate_cors_response_for_origin(
|
||||
&WebOrigin::Opaque,
|
||||
&response_url,
|
||||
&[],
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
validate_cors_response_for_origin(
|
||||
&WebOrigin::Opaque,
|
||||
&response_url,
|
||||
&[("Access-Control-Allow-Origin".to_owned(), "null".to_owned(),)],
|
||||
RequestCredentialsMode::SameOrigin,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_cors_preflight_response_checks_method_and_headers() {
|
||||
let request_headers = vec![
|
||||
|
||||
@@ -1400,6 +1400,7 @@ impl ScriptVm {
|
||||
// up the ambient Page loader here would silently rebind policy/backend
|
||||
// to a newer Document identity.
|
||||
let loader = pending.load.request_client();
|
||||
let request_origin = pending.request_origin();
|
||||
let mut request = moli_fetch::Request::new(
|
||||
&request_method,
|
||||
request_url.as_str(),
|
||||
@@ -1407,6 +1408,7 @@ impl ScriptVm {
|
||||
request_headers.clone(),
|
||||
)?
|
||||
.with_initiator_url(&pending.info.document_url)
|
||||
.with_request_origin(request_origin)
|
||||
.with_request_mode(pending.request_mode)
|
||||
.with_credentials_mode(pending.credentials_mode)
|
||||
.with_network_partition_key(pending.network_partition_key.clone())
|
||||
@@ -1668,6 +1670,7 @@ impl ScriptVm {
|
||||
));
|
||||
}
|
||||
let loader = pending_fetch.load.request_client();
|
||||
let request_origin = pending_fetch.request_origin();
|
||||
let mut request = moli_fetch::Request::new(
|
||||
&request_method,
|
||||
request_url.as_str(),
|
||||
@@ -1675,6 +1678,7 @@ impl ScriptVm {
|
||||
original_request_headers.clone(),
|
||||
)?
|
||||
.with_initiator_url(&pending_fetch.info.document_url)
|
||||
.with_request_origin(request_origin)
|
||||
.with_request_mode(pending_fetch.request_mode)
|
||||
.with_credentials_mode(pending_fetch.credentials_mode)
|
||||
.with_auth(auth.into())
|
||||
@@ -3134,8 +3138,9 @@ impl ScriptVm {
|
||||
return Err(message);
|
||||
}
|
||||
if !skip_fetch_security_validation {
|
||||
crate::network_host::validate_fetch_response_security_policy_with_body(
|
||||
crate::network_host::validate_fetch_response_security_policy_with_body_for_origin(
|
||||
&pending.info.document_url,
|
||||
&pending.request_origin(),
|
||||
&response.final_url,
|
||||
&response.headers,
|
||||
response.body_bytes(),
|
||||
@@ -3307,8 +3312,9 @@ impl ScriptVm {
|
||||
.or_else(|| {
|
||||
(!skip_fetch_security_validation)
|
||||
.then(|| {
|
||||
crate::network_host::validate_fetch_response_security_policy_with_body(
|
||||
crate::network_host::validate_fetch_response_security_policy_with_body_for_origin(
|
||||
&pending.info.document_url,
|
||||
&pending.request_origin(),
|
||||
&response.final_url,
|
||||
&response.headers,
|
||||
response.body_bytes(),
|
||||
@@ -3591,8 +3597,9 @@ impl ScriptVm {
|
||||
| SubresourceResourceType::Video
|
||||
| SubresourceResourceType::Xhr
|
||||
) {
|
||||
let validation = crate::network_host::validate_fetch_response_security_policy_with_body_classified(
|
||||
let validation = crate::network_host::validate_fetch_response_security_policy_with_body_classified_for_origin(
|
||||
&pending.info.document_url,
|
||||
&pending.request_origin(),
|
||||
&response.final_url,
|
||||
&response.headers,
|
||||
response.body_bytes(),
|
||||
@@ -3689,8 +3696,8 @@ impl ScriptVm {
|
||||
SubresourceResourceType::Fetch | SubresourceResourceType::Xhr
|
||||
) {
|
||||
observable_response.headers =
|
||||
crate::network_host::filter_cors_exposed_response_headers(
|
||||
&pending.info.document_url,
|
||||
crate::network_host::filter_cors_exposed_response_headers_for_origin(
|
||||
&pending.request_origin(),
|
||||
&observable_response.final_url,
|
||||
&observable_response.headers,
|
||||
pending.credentials_mode,
|
||||
@@ -4387,8 +4394,9 @@ impl ScriptVm {
|
||||
None
|
||||
}
|
||||
.or_else(|| {
|
||||
crate::network_host::validate_fetch_response_security_policy(
|
||||
crate::network_host::validate_fetch_response_security_policy_for_origin(
|
||||
&pending.info.document_url,
|
||||
&pending.request_origin(),
|
||||
&started.head.final_url,
|
||||
&started.head.headers,
|
||||
pending.request_mode,
|
||||
@@ -4588,8 +4596,9 @@ impl ScriptVm {
|
||||
| SubresourceResourceType::Xhr
|
||||
)
|
||||
.then(|| {
|
||||
crate::network_host::validate_fetch_response_security_policy(
|
||||
crate::network_host::validate_fetch_response_security_policy_for_origin(
|
||||
&pending.info.document_url,
|
||||
&pending.request_origin(),
|
||||
&started.head.final_url,
|
||||
&started.head.headers,
|
||||
pending.request_mode,
|
||||
@@ -4780,8 +4789,8 @@ impl ScriptVm {
|
||||
pending.info.resource_type,
|
||||
SubresourceResourceType::Fetch | SubresourceResourceType::Xhr
|
||||
) {
|
||||
observable_head.headers = crate::network_host::filter_cors_exposed_response_headers(
|
||||
&pending.info.document_url,
|
||||
observable_head.headers = crate::network_host::filter_cors_exposed_response_headers_for_origin(
|
||||
&pending.request_origin(),
|
||||
&observable_head.final_url,
|
||||
&observable_head.headers,
|
||||
pending.credentials_mode,
|
||||
@@ -5687,6 +5696,7 @@ impl ScriptVm {
|
||||
trace_fields,
|
||||
record_started,
|
||||
);
|
||||
let request_origin = streaming.pending.request_origin();
|
||||
if let PendingSubresourceContinuation::Xhr(xhr) =
|
||||
streaming.pending.continuation
|
||||
&& let Some(response_body) = xhr_delivery_body
|
||||
@@ -5706,8 +5716,8 @@ impl ScriptVm {
|
||||
let xhr = v8::Local::new(scope, &xhr);
|
||||
let mut observable_head = streaming.head;
|
||||
observable_head.headers =
|
||||
crate::network_host::filter_cors_exposed_response_headers(
|
||||
&streaming.pending.info.document_url,
|
||||
crate::network_host::filter_cors_exposed_response_headers_for_origin(
|
||||
&request_origin,
|
||||
&observable_head.final_url,
|
||||
&observable_head.headers,
|
||||
streaming.pending.credentials_mode,
|
||||
|
||||
@@ -167,6 +167,7 @@ fn pending_fetch_continuation<'s>(
|
||||
dispatch_scope,
|
||||
)
|
||||
.expect("test Fetch should capture its CSP report context"),
|
||||
moli_url::WebOrigin::from_url(host.document_url()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -305,6 +305,7 @@ fn register_pending_window_fetch_for_test(
|
||||
keepalive,
|
||||
connect_policy,
|
||||
csp_report_context,
|
||||
moli_url::WebOrigin::from_url(&url),
|
||||
Some(cancel_handle.clone()),
|
||||
moli_fetch::RequestCredentialsMode::SameOrigin,
|
||||
moli_fetch::RequestMode::Cors,
|
||||
@@ -465,6 +466,7 @@ fn register_pending_window_fetch_with_connect_policy_for_test(
|
||||
keepalive,
|
||||
crate::document_runtime::DocumentConnectPolicySnapshot::from_policy_container(&policy),
|
||||
csp_report_context,
|
||||
moli_url::WebOrigin::from_url(&document_url),
|
||||
Some(cancel_handle.clone()),
|
||||
moli_fetch::RequestCredentialsMode::SameOrigin,
|
||||
moli_fetch::RequestMode::Cors,
|
||||
@@ -1096,6 +1098,14 @@ async fn opaque_child_isolated_world_projects_only_its_own_document() {
|
||||
.child_browsing_context_has_opaque_origin(child_handle),
|
||||
"sandbox without allow-same-origin must create an opaque child origin"
|
||||
);
|
||||
assert_eq!(
|
||||
vm._context_host
|
||||
.borrow()
|
||||
.child_browsing_context_request_origin(child_handle)
|
||||
.as_deref(),
|
||||
Some("null"),
|
||||
"sandboxed child subresource requests must use an opaque client origin"
|
||||
);
|
||||
assert_eq!(
|
||||
vm.eval("document.getElementById('opaque-isolated-frame').contentDocument === null")
|
||||
.expect("top opaque contentDocument visibility should evaluate"),
|
||||
|
||||
@@ -488,6 +488,7 @@ pub(crate) struct ServiceWorkerFetchRequestMetadata {
|
||||
pub(crate) referrer_policy: String,
|
||||
pub(crate) integrity: String,
|
||||
pub(crate) keepalive: bool,
|
||||
pub(crate) request_origin: Option<moli_url::WebOrigin>,
|
||||
}
|
||||
|
||||
impl Default for ServiceWorkerFetchRequestMetadata {
|
||||
@@ -498,6 +499,7 @@ impl Default for ServiceWorkerFetchRequestMetadata {
|
||||
referrer_policy: String::new(),
|
||||
integrity: String::new(),
|
||||
keepalive: false,
|
||||
request_origin: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -520,6 +522,7 @@ pub(crate) fn service_worker_fetch_request_metadata(
|
||||
.and_then(|metadata| metadata.integrity.clone())
|
||||
.unwrap_or_default(),
|
||||
keepalive: false,
|
||||
request_origin: request.explicit_request_origin().cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ fn navigation_preload_request_for_job(
|
||||
} else {
|
||||
request.with_top_level_navigation_cookie_context()
|
||||
};
|
||||
request
|
||||
let mut request = request
|
||||
.with_initiator_url(&job.network_context.document_url)
|
||||
.with_request_mode(job.request_mode)
|
||||
.with_credentials_mode(job.credentials_mode)
|
||||
@@ -70,7 +70,11 @@ fn navigation_preload_request_for_job(
|
||||
} else {
|
||||
moli_fetch::BrowserNavigationRequestKind::Navigate
|
||||
})
|
||||
.with_page_network_policy()
|
||||
.with_page_network_policy();
|
||||
if let Some(request_origin) = job.metadata.request_origin.clone() {
|
||||
request = request.with_request_origin(request_origin);
|
||||
}
|
||||
request
|
||||
})
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ fn configure_service_worker_network_fallback_request(
|
||||
job.network_context.resource_type,
|
||||
))
|
||||
.with_subframe_context(job.network_context.frame_id.is_some());
|
||||
if let Some(request_origin) = job.metadata.request_origin.clone() {
|
||||
request = request.with_request_origin(request_origin);
|
||||
}
|
||||
if service_worker_fetch_is_navigation_request(job) {
|
||||
request = if job.network_context.frame_id.is_some() {
|
||||
request.with_subframe_navigation_cookie_context()
|
||||
|
||||
@@ -217,6 +217,7 @@ pub(super) struct PendingWindowFetchContinuation {
|
||||
keepalive: bool,
|
||||
connect_policy: crate::document_runtime::DocumentConnectPolicySnapshot,
|
||||
csp_report_context: crate::network_host::WindowCspReportRequestContext,
|
||||
request_origin: moli_url::WebOrigin,
|
||||
}
|
||||
|
||||
enum PendingWindowFetchPromise {
|
||||
@@ -230,12 +231,14 @@ impl PendingWindowFetchContinuation {
|
||||
keepalive: bool,
|
||||
connect_policy: crate::document_runtime::DocumentConnectPolicySnapshot,
|
||||
csp_report_context: crate::network_host::WindowCspReportRequestContext,
|
||||
request_origin: moli_url::WebOrigin,
|
||||
) -> Self {
|
||||
Self {
|
||||
promise: PendingWindowFetchPromise::Active(resolver),
|
||||
keepalive,
|
||||
connect_policy,
|
||||
csp_report_context,
|
||||
request_origin,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +279,10 @@ impl PendingWindowFetchContinuation {
|
||||
pub(super) fn csp_report_context(&self) -> &crate::network_host::WindowCspReportRequestContext {
|
||||
&self.csp_report_context
|
||||
}
|
||||
|
||||
pub(super) fn request_origin(&self) -> &moli_url::WebOrigin {
|
||||
&self.request_origin
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -518,6 +525,13 @@ pub(super) struct PendingSubresourceFetchState {
|
||||
}
|
||||
|
||||
impl PendingSubresourceFetchState {
|
||||
pub(super) fn request_origin(&self) -> moli_url::WebOrigin {
|
||||
self.continuation
|
||||
.window_fetch()
|
||||
.map(|fetch| fetch.request_origin().clone())
|
||||
.unwrap_or_else(|| moli_url::WebOrigin::from_url(&self.info.document_url))
|
||||
}
|
||||
|
||||
pub(super) fn detach_keepalive_window_fetch(&mut self) -> bool {
|
||||
let PendingSubresourceExecutionContext::WindowFetch(context) = &self.execution_context
|
||||
else {
|
||||
|
||||
@@ -1833,6 +1833,7 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
referrer_policy: init.referrer_policy.unwrap_or(inherited.referrer_policy),
|
||||
integrity: init.integrity.unwrap_or(inherited.integrity),
|
||||
keepalive: init.keepalive.unwrap_or(inherited.keepalive),
|
||||
request_origin: None,
|
||||
};
|
||||
(
|
||||
url,
|
||||
@@ -1872,6 +1873,7 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
referrer_policy: init.referrer_policy.unwrap_or_default(),
|
||||
integrity: init.integrity.unwrap_or_default(),
|
||||
keepalive: init.keepalive.unwrap_or(false),
|
||||
request_origin: None,
|
||||
};
|
||||
(
|
||||
url,
|
||||
|
||||
@@ -2624,6 +2624,7 @@ async fn service_worker_fetch_event_request_exposes_destination_metadata() {
|
||||
referrer_policy: "origin".to_owned(),
|
||||
integrity: "sha256-test".to_owned(),
|
||||
keepalive: true,
|
||||
request_origin: None,
|
||||
},
|
||||
};
|
||||
let completion =
|
||||
|
||||
+32
-1
@@ -17,7 +17,7 @@ impl TupleOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum WebOrigin {
|
||||
Tuple(TupleOrigin),
|
||||
Opaque,
|
||||
@@ -39,6 +39,12 @@ impl WebOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_ascii_serialization(serialized: &str) -> Self {
|
||||
Url::parse(serialized)
|
||||
.ok()
|
||||
.map_or(Self::Opaque, |url| Self::from_url(&url))
|
||||
}
|
||||
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
matches!(self, Self::Opaque)
|
||||
}
|
||||
@@ -63,6 +69,10 @@ impl WebOrigin {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn same_origin_url(&self, url: &Url) -> bool {
|
||||
self.same_origin(&Self::from_url(url))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tuple_origin_url(url: &Url) -> Option<Cow<'_, Url>> {
|
||||
@@ -188,6 +198,27 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_origin_compares_directly_with_urls() {
|
||||
let origin = WebOrigin::from_url(&url("https://example.test/document"));
|
||||
|
||||
assert!(origin.same_origin_url(&url("https://example.test/resource")));
|
||||
assert!(!origin.same_origin_url(&url("https://other.test/resource")));
|
||||
assert!(!WebOrigin::Opaque.same_origin_url(&url("https://example.test/resource")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_origin_rehydrates_tuple_serializations_and_keeps_null_opaque() {
|
||||
assert_eq!(
|
||||
WebOrigin::from_ascii_serialization("https://example.test:8443"),
|
||||
WebOrigin::from_url(&url("https://example.test:8443/path"))
|
||||
);
|
||||
assert_eq!(
|
||||
WebOrigin::from_ascii_serialization("null"),
|
||||
WebOrigin::Opaque
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blob_url_path_fallback_only_accepts_http_https_and_file_schemes() {
|
||||
let blob = url("blob:https://example.test/object-1");
|
||||
|
||||
Reference in New Issue
Block a user