mirror of
https://github.com/lexmount/moli.git
synced 2026-11-14 00:08:12 +00:00
fix(csp): preserve Document response status in violations
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn fixture_response(query: &str) -> (&'static str, String, String) {
|
||||
let status = match query {
|
||||
"201" => "201 Created",
|
||||
"404" => "404 Not Found",
|
||||
_ => "200 OK",
|
||||
};
|
||||
(
|
||||
status,
|
||||
"Content-Type: text/html\r\nContent-Security-Policy: connect-src 'none'\r\nCache-Control: no-store\r\n".into(),
|
||||
"<!doctype html><body>CSP response metadata</body>".into(),
|
||||
)
|
||||
}
|
||||
|
||||
const PROBE: &str = r#"async function probe(view) {
|
||||
const reported = new Promise(resolve => {
|
||||
view.document.addEventListener('securitypolicyviolation', resolve, {once:true});
|
||||
});
|
||||
await view.fetch('/blocked-csp-response-status').then(
|
||||
() => {throw new Error('CSP must prevent transport');},
|
||||
error => {if (!(error instanceof view.TypeError)) throw error;}
|
||||
);
|
||||
const event = await reported;
|
||||
return [event.statusCode, event instanceof view.SecurityPolicyViolationEvent,
|
||||
event.isTrusted, event.target === view.document, event.effectiveDirective];
|
||||
}"#;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn document_csp_response_status_retains_navigation_metadata_through_open() -> Result<()> {
|
||||
let servers = IntegrityServers::spawn().await?;
|
||||
let browser = Browser::new(AppConfig::default())?;
|
||||
for status in [201, 404] {
|
||||
let mut page = browser
|
||||
.fetch_allow_http_error(&format!(
|
||||
"{}/csp-response-status.html?{status}",
|
||||
servers.origin
|
||||
))
|
||||
.await?;
|
||||
let expression = format!(
|
||||
r#"(async () => {{
|
||||
const probe = {PROBE};
|
||||
const before = await probe(window);
|
||||
document.open();
|
||||
document.write('<!doctype html><body>replaced</body>');
|
||||
document.close();
|
||||
return [before, await probe(window)];
|
||||
}})().then(JSON.stringify)"#
|
||||
);
|
||||
let result = tokio::time::timeout(
|
||||
Duration::from_secs(10),
|
||||
page.evaluate_runtime_expression_with_await_async(&expression, true),
|
||||
)
|
||||
.await??;
|
||||
let observed: serde_json::Value =
|
||||
serde_json::from_str(result["value"].as_str().expect("CSP observations"))?;
|
||||
assert_eq!(
|
||||
observed,
|
||||
serde_json::json!([
|
||||
[status, true, true, true, "connect-src"],
|
||||
[status, true, true, true, "connect-src"]
|
||||
])
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
servers
|
||||
.requests
|
||||
.lock()
|
||||
.iter()
|
||||
.all(|request| { !request.path.starts_with("/blocked-csp-response-status") })
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn child_csp_response_status_uses_its_response_without_inheriting_parent_status() -> Result<()>
|
||||
{
|
||||
let servers = IntegrityServers::spawn().await?;
|
||||
let browser = Browser::new(AppConfig::default())?;
|
||||
let mut page = browser
|
||||
.fetch(&format!("{}/csp-response-status.html?201", servers.origin))
|
||||
.await?;
|
||||
let expression = format!(
|
||||
r#"(async () => {{
|
||||
const probe = {PROBE};
|
||||
const rows = [];
|
||||
for (const source of ['/csp-response-status.html?404', 'about:blank', 'srcdoc']) {{
|
||||
const frame = document.createElement('iframe');
|
||||
if (source === 'srcdoc') frame.srcdoc = '<!doctype html><body>srcdoc</body>';
|
||||
else frame.src = source;
|
||||
await new Promise(resolve => {{frame.onload = resolve; document.body.append(frame);}});
|
||||
try {{rows.push(await probe(frame.contentWindow));}}
|
||||
finally {{frame.remove();}}
|
||||
}}
|
||||
const popup = open();
|
||||
try {{rows.push(await probe(popup));}}
|
||||
finally {{popup.close();}}
|
||||
rows.push(await probe(window));
|
||||
return rows;
|
||||
}})().then(JSON.stringify)"#
|
||||
);
|
||||
let result = tokio::time::timeout(
|
||||
Duration::from_secs(10),
|
||||
page.evaluate_runtime_expression_with_await_async(&expression, true),
|
||||
)
|
||||
.await??;
|
||||
let observed: serde_json::Value =
|
||||
serde_json::from_str(result["value"].as_str().expect("child CSP observations"))?;
|
||||
assert_eq!(
|
||||
observed,
|
||||
serde_json::json!([
|
||||
[404, true, true, true, "connect-src"],
|
||||
[0, true, true, true, "connect-src"],
|
||||
[0, true, true, true, "connect-src"],
|
||||
[0, true, true, true, "connect-src"],
|
||||
[201, true, true, true, "connect-src"]
|
||||
])
|
||||
);
|
||||
assert!(
|
||||
servers
|
||||
.requests
|
||||
.lock()
|
||||
.iter()
|
||||
.all(|request| { !request.path.starts_with("/blocked-csp-response-status") })
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -56,6 +56,9 @@ mod local_worker_referrer;
|
||||
#[path = "service_worker_referrer.rs"]
|
||||
mod service_worker_referrer;
|
||||
|
||||
#[path = "csp_response_status.rs"]
|
||||
mod csp_response_status;
|
||||
|
||||
struct IntegrityServers {
|
||||
origin: String,
|
||||
cross_origin: String,
|
||||
@@ -216,6 +219,7 @@ fn fixture_response(
|
||||
"/document-referrer.html" | "/document-referrer.js" => {
|
||||
module_document_referrer::fixture_response(path, query, origin, cross)
|
||||
}
|
||||
"/csp-response-status.html" => csp_response_status::fixture_response(query),
|
||||
"/import-referrer.js" => dynamic_import_referrer::fixture_response(query),
|
||||
"/local-referrer-worker.js" | "/local-referrer-page.html" => {
|
||||
local_worker_referrer::fixture_response(path, query, request.referer.as_deref())
|
||||
|
||||
@@ -140,6 +140,7 @@ impl ContentSecurityPolicyReportingEndpoints {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ContentSecurityPolicyViolationEventFields<'a> {
|
||||
pub document_uri: &'a str,
|
||||
pub referrer: &'a str,
|
||||
|
||||
@@ -89,6 +89,7 @@ struct DocumentMetadata {
|
||||
// Document identity. Only a whole-arena snapshot may copy them unchanged.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct DocumentArenaState {
|
||||
source_response_status: Option<u16>,
|
||||
// Meta delivery updates policy in time order; removing the element does
|
||||
// not undo the policy it delivered to this Document.
|
||||
meta_referrer_policy: MetaReferrerPolicy,
|
||||
@@ -263,6 +264,10 @@ impl Document {
|
||||
self.metadata.source_last_modified_ms
|
||||
}
|
||||
|
||||
pub fn source_response_status(&self) -> Option<u16> {
|
||||
self.arena_state.source_response_status
|
||||
}
|
||||
|
||||
pub fn quirks_mode(&self) -> QuirksMode {
|
||||
self.metadata.quirks_mode
|
||||
}
|
||||
@@ -374,6 +379,10 @@ impl Document {
|
||||
self.metadata.source_last_modified_ms = timestamp_ms;
|
||||
}
|
||||
|
||||
pub fn set_source_response_status(&mut self, status: Option<u16>) {
|
||||
self.arena_state.source_response_status = status;
|
||||
}
|
||||
|
||||
pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
|
||||
self.metadata.quirks_mode = quirks_mode;
|
||||
}
|
||||
|
||||
@@ -642,6 +642,28 @@ mod tests {
|
||||
assert_eq!(host.owner_document_handle(template), Some(destination));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_response_metadata_survives_stream_replacement_but_not_cloning() {
|
||||
let mut host = test_host();
|
||||
let document = host.document_handle();
|
||||
assert_eq!(
|
||||
host.document_source_response_status_for_handle(document),
|
||||
None
|
||||
);
|
||||
assert!(host.set_document_source_response_status_for_handle(document, Some(404)));
|
||||
host.reset_html_document_shell();
|
||||
assert_eq!(
|
||||
host.document_source_response_status_for_handle(document),
|
||||
Some(404)
|
||||
);
|
||||
let clone = host.clone_node(document, true).unwrap();
|
||||
assert_eq!(host.document_source_response_status_for_handle(clone), None);
|
||||
assert_eq!(
|
||||
host.document_source_response_status_for_handle(document),
|
||||
Some(404)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn element_copies_preserve_native_identity_and_target_template_document() {
|
||||
const HTML: &str = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
@@ -1229,6 +1229,15 @@ impl DomHost {
|
||||
.and_then(Document::source_last_modified_ms)
|
||||
}
|
||||
|
||||
pub fn document_source_response_status_for_handle(
|
||||
&self,
|
||||
document_handle: DomHandle,
|
||||
) -> Option<u16> {
|
||||
self.node(document_handle)
|
||||
.and_then(Node::as_document)
|
||||
.and_then(Document::source_response_status)
|
||||
}
|
||||
|
||||
pub fn document_default_language_for_node(&self, handle: DomHandle) -> Option<String> {
|
||||
let document_handle = self.owner_document_handle(handle)?;
|
||||
self.document_meta_default_language_for_handle(document_handle)
|
||||
@@ -1420,6 +1429,24 @@ impl DomHost {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_document_source_response_status_for_handle(
|
||||
&mut self,
|
||||
document_handle: DomHandle,
|
||||
status: Option<u16>,
|
||||
) -> bool {
|
||||
let Some(document) = self
|
||||
.node_mut(document_handle)
|
||||
.and_then(|node| node.data_mut().as_document_mut())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if document.source_response_status() == status {
|
||||
return false;
|
||||
}
|
||||
document.set_source_response_status(status);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn document_target_element(&self, document_handle: DomHandle) -> Option<DomHandle> {
|
||||
let target = self
|
||||
.node(document_handle)
|
||||
|
||||
@@ -343,6 +343,13 @@ impl DocumentRuntime {
|
||||
.set_document_source_last_modified_for_handle(document_handle, timestamp_ms);
|
||||
}
|
||||
|
||||
pub(crate) fn set_document_source_response_status(&mut self, status: Option<u16>) {
|
||||
let document_handle = self.dom_host.document_handle();
|
||||
let _ = self
|
||||
.dom_host
|
||||
.set_document_source_response_status_for_handle(document_handle, status);
|
||||
}
|
||||
|
||||
pub(crate) fn take_style_source_document_sync_pending(&mut self) -> bool {
|
||||
std::mem::take(&mut self.style_source_document_sync_pending)
|
||||
}
|
||||
|
||||
@@ -2280,7 +2280,12 @@ impl DocumentRuntime {
|
||||
.current_main_document_task_owner()
|
||||
.ok_or_else(|| anyhow::anyhow!("main CSP violation document owner is unavailable"))?;
|
||||
if send_report {
|
||||
let fields = ContentSecurityPolicyViolationEventFields::from(violation);
|
||||
let fields = self.content_security_policy_violation_event_fields(
|
||||
target
|
||||
.and_then(|target| self.dom_host.owner_document_handle(target))
|
||||
.unwrap_or_else(|| self.document_handle()),
|
||||
violation,
|
||||
);
|
||||
let report_owner = event_owner.unwrap_or(document_owner);
|
||||
let child_handle = event_owner.and(target).and_then(|document| {
|
||||
host.child_browsing_context_host_for_document_handle(document)
|
||||
@@ -2361,11 +2366,19 @@ impl DocumentRuntime {
|
||||
})
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let document = match event_target {
|
||||
EventTargetHandle::Node(handle) => self
|
||||
.dom_host
|
||||
.owner_document_handle(handle)
|
||||
.unwrap_or_else(|| self.document_handle()),
|
||||
_ => self.document_handle(),
|
||||
};
|
||||
let fields = self.content_security_policy_violation_event_fields(document, violation);
|
||||
let event = create_content_security_policy_violation_event(
|
||||
scope,
|
||||
event_target_value,
|
||||
event_target_value,
|
||||
violation,
|
||||
&fields,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
self.dispatch_public_event_best_effort(
|
||||
@@ -2379,6 +2392,21 @@ impl DocumentRuntime {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn content_security_policy_violation_event_fields<'a>(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
violation: &'a DocumentContentSecurityPolicyViolation,
|
||||
) -> ContentSecurityPolicyViolationEventFields<'a> {
|
||||
ContentSecurityPolicyViolationEventFields {
|
||||
status_code: i32::from(
|
||||
self.dom_host
|
||||
.document_source_response_status_for_handle(document)
|
||||
.unwrap_or(0),
|
||||
),
|
||||
..ContentSecurityPolicyViolationEventFields::from(violation)
|
||||
}
|
||||
}
|
||||
|
||||
fn document_content_security_policies_for_optional_document(
|
||||
&self,
|
||||
document_handle: Option<DomHandle>,
|
||||
@@ -3038,13 +3066,10 @@ pub(crate) fn create_content_security_policy_violation_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
current_target: v8::Local<'s, v8::Value>,
|
||||
target: v8::Local<'s, v8::Value>,
|
||||
violation: &DocumentContentSecurityPolicyViolation,
|
||||
fields: &ContentSecurityPolicyViolationEventFields<'_>,
|
||||
) -> anyhow::Result<v8::Local<'s, v8::Object>> {
|
||||
let event = create_security_policy_violation_event(
|
||||
scope,
|
||||
&ContentSecurityPolicyViolationEventFields::from(violation),
|
||||
)
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to create CSP violation event"))?;
|
||||
let event = create_security_policy_violation_event(scope, fields)
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to create CSP violation event"))?;
|
||||
let _ = crate::context_bootstrap::event_backing(scope, event).set(
|
||||
scope,
|
||||
v8str(scope, "target").into(),
|
||||
|
||||
@@ -203,6 +203,11 @@ impl JsContextHost {
|
||||
};
|
||||
self.dom_host_mut()
|
||||
.set_document_character_set_for_handle(document_handle, &snapshot.character_set);
|
||||
self.dom_host_mut()
|
||||
.set_document_source_response_status_for_handle(
|
||||
document_handle,
|
||||
snapshot.response_status,
|
||||
);
|
||||
self.dom_host_mut()
|
||||
.set_document_allow_declarative_shadow_roots_for_handle(document_handle, true);
|
||||
// Navigation-created Documents start loading before parser scripts can
|
||||
|
||||
@@ -501,8 +501,10 @@ impl JsContextHost {
|
||||
// SAFETY: JsContextHost is owned by the ScriptVm that owns this DocumentRuntime.
|
||||
unsafe { &mut *self.runtime }
|
||||
.record_content_security_policy_inspector_issue(Some(handle), violation);
|
||||
let fields =
|
||||
ContentSecurityPolicyViolationEventFields::from_url_violation(violation);
|
||||
let fields = ContentSecurityPolicyViolationEventFields {
|
||||
status_code: i32::from(loaded.response_status.unwrap_or(0)),
|
||||
..ContentSecurityPolicyViolationEventFields::from_url_violation(violation)
|
||||
};
|
||||
send_content_security_policy_reports(
|
||||
&reports,
|
||||
pending.resource_loader.request_client(),
|
||||
@@ -710,6 +712,7 @@ impl JsContextHost {
|
||||
loaded.content_type,
|
||||
loaded.character_set,
|
||||
)
|
||||
.with_response_status(loaded.response_status)
|
||||
.with_resource_was_cached(resource_was_cached);
|
||||
self.cache_child_snapshot_with_current_document_policy(handle, snapshot)
|
||||
}
|
||||
@@ -1024,6 +1027,7 @@ fn child_document_load_outcome_from_response(
|
||||
};
|
||||
Ok(ChildDocumentLoadOutcome::Loaded(Box::new(
|
||||
LoadedChildDocument {
|
||||
response_status: Some(head.status),
|
||||
response_links: crate::response_links::DocumentResponseLinks::parse(
|
||||
&head.final_url,
|
||||
&head.headers,
|
||||
|
||||
@@ -106,6 +106,7 @@ impl JsContextHost {
|
||||
content_type,
|
||||
character_set,
|
||||
)
|
||||
.with_response_status(Some(head.status))
|
||||
.with_policy_container(policy_container),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ pub(crate) struct ChildBrowsingContextSnapshot {
|
||||
pub(crate) resource_was_cached: bool,
|
||||
pub(crate) content_type: Option<String>,
|
||||
pub(crate) character_set: String,
|
||||
pub(crate) response_status: Option<u16>,
|
||||
pub(crate) policy_container: DocumentPolicyContainer,
|
||||
}
|
||||
|
||||
@@ -89,6 +90,7 @@ impl ChildBrowsingContextSnapshot {
|
||||
resource_was_cached: false,
|
||||
content_type,
|
||||
character_set: character_set.into(),
|
||||
response_status: None,
|
||||
policy_container: DocumentPolicyContainer::default(),
|
||||
}
|
||||
}
|
||||
@@ -126,6 +128,11 @@ impl ChildBrowsingContextSnapshot {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_response_status(mut self, status: Option<u16>) -> Self {
|
||||
self.response_status = status;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_policy_container(
|
||||
mut self,
|
||||
policy_container: DocumentPolicyContainer,
|
||||
|
||||
@@ -265,13 +265,15 @@ impl JsContextHost {
|
||||
})
|
||||
.unwrap_or(resolved.document_handle);
|
||||
let target = EventTargetHandle::Node(event_target);
|
||||
let fields = self.content_security_policy_violation_event_fields(
|
||||
resolved.document_handle,
|
||||
&violation,
|
||||
);
|
||||
crate::host::event_target_value(scope, host_ptr, target)
|
||||
.ok()
|
||||
.and_then(|value| {
|
||||
create_content_security_policy_violation_event(
|
||||
scope, value, value, &violation,
|
||||
)
|
||||
.ok()
|
||||
create_content_security_policy_violation_event(scope, value, value, &fields)
|
||||
.ok()
|
||||
})
|
||||
.is_some_and(|event| {
|
||||
// SAFETY: the Host and its DocumentRuntime share one ScriptVm.
|
||||
|
||||
@@ -289,6 +289,7 @@ enum LightweightPopupClassicScriptAdvance {
|
||||
}
|
||||
|
||||
struct LightweightPopupDocumentProjectionSource<'a> {
|
||||
response_status: Option<u16>,
|
||||
response_links: &'a crate::response_links::DocumentResponseLinks,
|
||||
task: LightweightPopupNavigationTaskToken,
|
||||
document_url: &'a Url,
|
||||
@@ -2997,6 +2998,7 @@ impl JsContextHost {
|
||||
policy_container.response_content_security_policies.clone();
|
||||
}
|
||||
let loaded = LoadedChildDocument {
|
||||
response_status: snapshot.response_status,
|
||||
response_links: Default::default(),
|
||||
final_url: snapshot.url,
|
||||
policy_container,
|
||||
@@ -3089,6 +3091,7 @@ impl JsContextHost {
|
||||
);
|
||||
Ok(PopupDocumentLoadOutcome::Loaded(Box::new(
|
||||
LoadedChildDocument {
|
||||
response_status: Some(head.status),
|
||||
response_links: crate::response_links::DocumentResponseLinks::parse(
|
||||
&head.final_url,
|
||||
&head.headers,
|
||||
@@ -3333,6 +3336,7 @@ impl JsContextHost {
|
||||
scope,
|
||||
window,
|
||||
LightweightPopupDocumentProjectionSource {
|
||||
response_status: loaded.response_status,
|
||||
response_links: &loaded.response_links,
|
||||
task,
|
||||
document_url: &final_url,
|
||||
@@ -3795,11 +3799,18 @@ impl JsContextHost {
|
||||
let Some(window) = self.lightweight_popup_window(scope, popup_id) else {
|
||||
return;
|
||||
};
|
||||
let fields = crate::content_security_policy::ContentSecurityPolicyViolationEventFields {
|
||||
status_code: i32::from(
|
||||
self.content_security_policy_response_status(
|
||||
super::OwnerDispatchScope::LightweightPopup(popup_id),
|
||||
)
|
||||
.unwrap_or(0),
|
||||
),
|
||||
..crate::content_security_policy::ContentSecurityPolicyViolationEventFields::from(
|
||||
violation,
|
||||
)
|
||||
};
|
||||
if send_report {
|
||||
let fields =
|
||||
crate::content_security_policy::ContentSecurityPolicyViolationEventFields::from(
|
||||
violation,
|
||||
);
|
||||
let Some(document_owner) = self.current_lightweight_popup_document_owner(popup_id)
|
||||
else {
|
||||
tracing::debug!(popup_id, "discarded CSP report for retired popup document");
|
||||
@@ -3819,7 +3830,7 @@ impl JsContextHost {
|
||||
scope,
|
||||
window.into(),
|
||||
window.into(),
|
||||
violation,
|
||||
&fields,
|
||||
) {
|
||||
Ok(event) => {
|
||||
self.dispatch_lightweight_popup_window_event(
|
||||
@@ -3985,6 +3996,11 @@ impl JsContextHost {
|
||||
let document_handle =
|
||||
self.remember_lightweight_popup_document_handle(scope, popup_id, document);
|
||||
if let Some(document_handle) = document_handle {
|
||||
self.dom_host_mut()
|
||||
.set_document_source_response_status_for_handle(
|
||||
document_handle,
|
||||
source.response_status,
|
||||
);
|
||||
let _ = crate::context_bootstrap::install_css_runtime_state_for_document(
|
||||
scope,
|
||||
window,
|
||||
@@ -5130,6 +5146,7 @@ impl JsContextHost {
|
||||
scope,
|
||||
window,
|
||||
LightweightPopupDocumentProjectionSource {
|
||||
response_status: None,
|
||||
response_links: &Default::default(),
|
||||
task,
|
||||
document_url: &document_url,
|
||||
|
||||
@@ -292,6 +292,7 @@ impl JsContextHost {
|
||||
document_policy_container: policy.clone(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: moli_page_types::DocumentSettings {
|
||||
script_execution_disabled: !policy.sandbox.allows_scripts,
|
||||
debugger_enabled: self.dom_debugger_state.debugger_pauses_enabled(),
|
||||
|
||||
@@ -1791,6 +1791,23 @@ impl JsContextHost {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn content_security_policy_response_status(
|
||||
&self,
|
||||
owner: OwnerDispatchScope,
|
||||
) -> Option<u16> {
|
||||
let document = match owner {
|
||||
OwnerDispatchScope::Top => Some(self.document_handle()),
|
||||
OwnerDispatchScope::Child(handle) => {
|
||||
self.child_browsing_context_document_handle(handle)
|
||||
}
|
||||
OwnerDispatchScope::LightweightPopup(popup_id) => {
|
||||
self.lightweight_popup_document_handle(popup_id)
|
||||
}
|
||||
}?;
|
||||
self.dom_host()
|
||||
.document_source_response_status_for_handle(document)
|
||||
}
|
||||
|
||||
fn dispatch_child_content_security_policy_violation_event<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -1801,14 +1818,20 @@ impl JsContextHost {
|
||||
let window = self
|
||||
.child_browsing_context_window_wrapper(scope, handle)
|
||||
.ok_or_else(|| anyhow::anyhow!("child window wrapper is unavailable"))?;
|
||||
let fields = ContentSecurityPolicyViolationEventFields {
|
||||
status_code: i32::from(
|
||||
self.content_security_policy_response_status(OwnerDispatchScope::Child(handle))
|
||||
.unwrap_or(0),
|
||||
),
|
||||
..ContentSecurityPolicyViolationEventFields::from(violation)
|
||||
};
|
||||
let event = create_content_security_policy_violation_event(
|
||||
scope,
|
||||
window.into(),
|
||||
window.into(),
|
||||
violation,
|
||||
&fields,
|
||||
)?;
|
||||
if send_report {
|
||||
let fields = ContentSecurityPolicyViolationEventFields::from(violation);
|
||||
let document_owner =
|
||||
self.current_child_document_task_owner(handle)
|
||||
.ok_or_else(|| {
|
||||
|
||||
@@ -31,19 +31,23 @@ pub(crate) fn send_content_security_policy_reports_for_window(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let fields = ContentSecurityPolicyViolationEventFields {
|
||||
status_code: i32::from(request_context.response_status),
|
||||
..*fields
|
||||
};
|
||||
if let Some(execution_owner) = host.current_window_execution_context_owner(owner.dispatch_scope)
|
||||
&& let Some((_, context)) =
|
||||
host.window_execution_context(scope, execution_owner, owner.dispatch_scope)
|
||||
{
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
crate::content_security_policy::notify_content_security_policy_reporting_observers(
|
||||
scope, fields,
|
||||
scope, &fields,
|
||||
);
|
||||
}
|
||||
send_content_security_policy_reports_from_window_context(
|
||||
host,
|
||||
&request_context,
|
||||
fields,
|
||||
&fields,
|
||||
report_uri_endpoints,
|
||||
report_to_endpoints,
|
||||
);
|
||||
@@ -67,19 +71,23 @@ pub(crate) fn send_content_security_policy_reports_for_lightweight_popup(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let fields = ContentSecurityPolicyViolationEventFields {
|
||||
status_code: i32::from(request_context.response_status),
|
||||
..*fields
|
||||
};
|
||||
if let Some(execution_owner) = host.current_window_execution_context_owner(owner.dispatch_scope)
|
||||
&& let Some((_, context)) =
|
||||
host.window_execution_context(scope, execution_owner, owner.dispatch_scope)
|
||||
{
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
crate::content_security_policy::notify_content_security_policy_reporting_observers(
|
||||
scope, fields,
|
||||
scope, &fields,
|
||||
);
|
||||
}
|
||||
send_content_security_policy_reports_from_window_context(
|
||||
host,
|
||||
&request_context,
|
||||
fields,
|
||||
&fields,
|
||||
report_uri_endpoints,
|
||||
report_to_endpoints,
|
||||
);
|
||||
@@ -118,6 +126,7 @@ pub(crate) struct WindowCspReportRequestContext {
|
||||
request_client: ResourceRequestClient,
|
||||
frame_id: Option<String>,
|
||||
document_url: url::Url,
|
||||
response_status: u16,
|
||||
request_origin: moli_url::WebOrigin,
|
||||
network_partition_key: Option<String>,
|
||||
policy_context: crate::types::SubresourcePolicyContext,
|
||||
@@ -208,7 +217,10 @@ pub(crate) fn send_content_security_policy_violation_report_from_window_context(
|
||||
request_context: &WindowCspReportRequestContext,
|
||||
violation: &crate::document_runtime::DocumentContentSecurityPolicyViolation,
|
||||
) {
|
||||
let fields = ContentSecurityPolicyViolationEventFields::from(violation);
|
||||
let fields = ContentSecurityPolicyViolationEventFields {
|
||||
status_code: i32::from(request_context.response_status),
|
||||
..ContentSecurityPolicyViolationEventFields::from(violation)
|
||||
};
|
||||
send_content_security_policy_reports_from_window_context(
|
||||
host,
|
||||
request_context,
|
||||
@@ -552,6 +564,9 @@ fn window_csp_report_request_context_for_identity(
|
||||
resource_loader,
|
||||
frame_id,
|
||||
document_url,
|
||||
response_status: host
|
||||
.content_security_policy_response_status(identity.dispatch_scope())
|
||||
.unwrap_or(0),
|
||||
request_origin,
|
||||
network_partition_key: active_subresource_network_partition_key(
|
||||
host,
|
||||
|
||||
@@ -1186,6 +1186,7 @@ mod tests {
|
||||
moli_dom::NodeId::new(7),
|
||||
Ok(ChildDocumentLoadOutcome::Loaded(Box::new(
|
||||
LoadedChildDocument {
|
||||
response_status: None,
|
||||
response_links: Default::default(),
|
||||
final_url: Url::parse("https://example.test/child").unwrap(),
|
||||
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
|
||||
@@ -1212,6 +1213,7 @@ mod tests {
|
||||
),
|
||||
Ok(PopupDocumentLoadOutcome::Loaded(Box::new(
|
||||
LoadedChildDocument {
|
||||
response_status: None,
|
||||
response_links: Default::default(),
|
||||
final_url: Url::parse("https://example.test/popup").unwrap(),
|
||||
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
|
||||
|
||||
@@ -121,6 +121,8 @@ impl RendererCreateStreamingRawPageRequest {
|
||||
history_policy_container,
|
||||
document_default_language,
|
||||
document_last_modified,
|
||||
document_response_status: (!moli_url::is_about_blank(&final_url))
|
||||
.then_some(response_status),
|
||||
document_settings,
|
||||
network_offline,
|
||||
blocked_url_patterns,
|
||||
|
||||
@@ -937,6 +937,8 @@ impl RendererOwnerHandle {
|
||||
history_policy_container,
|
||||
document_default_language,
|
||||
document_last_modified,
|
||||
document_response_status: (!moli_url::is_about_blank(&final_url))
|
||||
.then_some(response_status),
|
||||
document_settings,
|
||||
network_offline,
|
||||
blocked_url_patterns,
|
||||
|
||||
@@ -1497,6 +1497,7 @@ impl PageVm {
|
||||
},
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: moli_page_types::DocumentSettings {
|
||||
script_execution_disabled: self.script_execution_disabled(),
|
||||
..self.document_settings.clone()
|
||||
|
||||
@@ -1100,6 +1100,7 @@ pub(crate) struct PageVmEnvConfig {
|
||||
pub(crate) document_policy_container: crate::document_runtime::DocumentPolicyContainer,
|
||||
pub(crate) document_default_language: Option<String>,
|
||||
pub(crate) document_last_modified: Option<f64>,
|
||||
pub(crate) document_response_status: Option<u16>,
|
||||
pub(crate) document_settings: moli_page_types::DocumentSettings,
|
||||
pub(crate) network_offline: bool,
|
||||
pub(crate) blocked_url_patterns: Vec<String>,
|
||||
@@ -1138,11 +1139,14 @@ impl PageVmEnvConfig {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn apply_navigation_response_headers(
|
||||
pub(crate) fn apply_navigation_response_metadata(
|
||||
&mut self,
|
||||
final_url: &Url,
|
||||
status: u16,
|
||||
headers: &[(String, Vec<u8>)],
|
||||
) {
|
||||
// Blank Documents inherit policy, but have no fetched response.
|
||||
self.document_response_status = (!moli_url::is_about_blank(final_url)).then_some(status);
|
||||
self.about_document_state = self
|
||||
.about_document_state
|
||||
.take()
|
||||
@@ -4672,6 +4676,9 @@ impl PageVm {
|
||||
self.vm_mut()
|
||||
.document_runtime
|
||||
.set_document_source_last_modified(env.document_last_modified);
|
||||
self.vm_mut()
|
||||
.document_runtime
|
||||
.set_document_source_response_status(env.document_response_status);
|
||||
}
|
||||
self.vm_mut()
|
||||
.set_stored_document_start_scripts(&env.document_start_scripts);
|
||||
|
||||
@@ -729,6 +729,7 @@ fn minimal_test_page_vm_env_config() -> PageVmEnvConfig {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
@@ -988,6 +988,7 @@ pub(super) fn stale_loaded_completion(
|
||||
ChildDocumentLoadNetworkAttribution::new(frame_id.to_owned(), None, loader_id),
|
||||
Ok(ChildDocumentLoadOutcome::Loaded(Box::new(
|
||||
LoadedChildDocument {
|
||||
response_status: None,
|
||||
response_links: Default::default(),
|
||||
final_url: Url::parse(request_url).expect("final URL"),
|
||||
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
|
||||
|
||||
@@ -25,6 +25,7 @@ fn default_runtime_hooks_reject_direct_no_owner_page_vm_construction() {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
@@ -709,6 +709,7 @@ fn test_page_vm_with_loader_dom_host_hooks_and_response_referrer_policy(
|
||||
},
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
@@ -77,6 +77,7 @@ fn loaded_popup_completion(
|
||||
target,
|
||||
Ok(PopupDocumentLoadOutcome::Loaded(Box::new(
|
||||
LoadedChildDocument {
|
||||
response_status: None,
|
||||
response_links: Default::default(),
|
||||
final_url: Url::parse(final_url).expect("popup completion final URL"),
|
||||
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
|
||||
|
||||
@@ -695,6 +695,7 @@ mod tests {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
@@ -277,7 +277,7 @@ impl ConcurrentParseTimeRuntime {
|
||||
) -> Result<StreamingNavigationPageCreationResult> {
|
||||
debug_assert!(!response_headers_indicate_download(&response_headers));
|
||||
let mut env = env.clone();
|
||||
env.apply_navigation_response_headers(&final_url, &response_headers);
|
||||
env.apply_navigation_response_metadata(&final_url, response_status, &response_headers);
|
||||
|
||||
let content_type = content_type.or_else(|| {
|
||||
moli_web_mime::navigation_response_mime_type_from_headers(&response_headers)
|
||||
@@ -1313,6 +1313,7 @@ mod tests {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
@@ -657,6 +657,7 @@ fn parser_owner_boundary_with_live_backend_queues_document_turn_before_runtime_w
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -951,6 +952,7 @@ fn parser_connected_head_script_does_not_push_later_head_tokens_into_body() {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -1132,6 +1134,7 @@ fn parser_connected_external_head_script_with_live_head_and_body_mutation_keeps_
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -1737,6 +1740,7 @@ fn parser_owner_body_stylesheet_pause_preserves_unconsumed_tail_on_live_page_vm(
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
@@ -354,6 +354,7 @@ fn external_async_handoff_marks_parser_stream_already_started_on_live_backend()
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -481,6 +482,7 @@ fn blocking_classic_handoff_registers_parser_owned_handle_on_live_backend() {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -622,6 +624,7 @@ fn non_async_post_parse_handoff_registers_pending_before_source_and_seals_withou
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -797,6 +800,7 @@ fn parser_owned_external_module_handoff_starts_pending_script_tree_root_fetch()
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -1117,6 +1121,7 @@ fn parser_owned_inline_importmap_handoff_registers_parser_owned_handle_on_live_b
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
@@ -1285,6 +1290,7 @@ fn parser_owner_style_import_handoff_is_stylesheet_gated_on_live_page_vm() {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
+1
@@ -1142,6 +1142,7 @@ fn parser_connected_head_document_write_keeps_later_head_tokens_in_head() {
|
||||
document_policy_container: Default::default(),
|
||||
document_default_language: None,
|
||||
document_last_modified: None,
|
||||
document_response_status: None,
|
||||
document_settings: Default::default(),
|
||||
network_offline: false,
|
||||
blocked_url_patterns: Vec::new(),
|
||||
|
||||
@@ -1759,6 +1759,7 @@ impl ChildModulepreloadFetchCompletion {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct LoadedChildDocument {
|
||||
pub(super) response_status: Option<u16>,
|
||||
pub(super) response_links: crate::response_links::DocumentResponseLinks,
|
||||
pub(super) final_url: Url,
|
||||
pub(super) policy_container: DocumentPolicyContainer,
|
||||
@@ -1787,6 +1788,7 @@ impl ChildDocumentLoadOutcome {
|
||||
>,
|
||||
) -> Self {
|
||||
Self::BlockedResponse(Box::new(LoadedChildDocument {
|
||||
response_status: None,
|
||||
final_url,
|
||||
policy_container: DocumentPolicyContainer {
|
||||
required_content_security_policy,
|
||||
|
||||
Reference in New Issue
Block a user