This commit is contained in:
elomscansio 2025-06-03 16:01:07 +02:00 committed by GitHub
commit 8dbcb49906
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 133 additions and 60 deletions

View file

@ -4299,7 +4299,14 @@ impl Document {
} }
pub(crate) fn set_csp_list(&self, csp_list: Option<CspList>) { pub(crate) fn set_csp_list(&self, csp_list: Option<CspList>) {
self.policy_container.borrow_mut().set_csp_list(csp_list); if let Some(new_list) = csp_list {
let mut current = self.get_csp_list();
match current {
Some(ref mut existing) => existing.append(new_list),
None => current = Some(new_list),
}
self.policy_container.borrow_mut().set_csp_list(current);
}
} }
pub(crate) fn get_csp_list(&self) -> Option<CspList> { pub(crate) fn get_csp_list(&self) -> Option<CspList> {

View file

@ -2,19 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use content_security_policy::{CspList, PolicyDisposition, PolicySource};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns}; use html5ever::{LocalName, Prefix};
use js::rust::HandleObject; use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document; use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlmetaelement::HTMLMetaElement; use crate::dom::node::{BindContext, Node};
use crate::dom::node::{BindContext, Node, NodeTraits, ShadowIncluding};
use crate::dom::userscripts::load_script; use crate::dom::userscripts::load_script;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
@ -53,48 +49,6 @@ impl HTMLHeadElement {
n.upcast::<Node>().set_weird_parser_insertion_mode(); n.upcast::<Node>().set_weird_parser_insertion_mode();
n n
} }
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
pub(crate) fn set_content_security_policy(&self) {
let doc = self.owner_document();
if doc.GetHead().as_deref() != Some(self) {
return;
}
let mut csp_list: Option<CspList> = None;
let node = self.upcast::<Node>();
let candidates = node
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.filter(|elem| elem.is::<HTMLMetaElement>())
.filter(|elem| {
elem.get_string_attribute(&local_name!("http-equiv"))
.to_ascii_lowercase() ==
*"content-security-policy"
})
.filter(|elem| {
elem.get_attribute(&ns!(), &local_name!("content"))
.is_some()
});
for meta in candidates {
if let Some(ref content) = meta.get_attribute(&ns!(), &local_name!("content")) {
let content = content.value();
let content_val = content.trim();
if !content_val.is_empty() {
let policies =
CspList::parse(content_val, PolicySource::Meta, PolicyDisposition::Enforce);
match csp_list {
Some(ref mut csp_list) => csp_list.append(policies),
None => csp_list = Some(policies),
}
}
}
}
doc.set_csp_list(csp_list);
}
} }
impl VirtualMethods for HTMLHeadElement { impl VirtualMethods for HTMLHeadElement {

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use content_security_policy::{CspList, PolicyDisposition, PolicySource};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns}; use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject; use js::rust::HandleObject;
@ -118,12 +119,47 @@ impl HTMLMetaElement {
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy> /// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
fn apply_csp_list(&self) { fn apply_csp_list(&self) {
if let Some(parent) = self.upcast::<Node>().GetParentElement() { if let Some(parent) = self.upcast::<Node>().GetParentElement() {
if let Some(head) = parent.downcast::<HTMLHeadElement>() { if parent.downcast::<HTMLHeadElement>().is_some() {
head.set_content_security_policy(); self.set_content_security_policy();
} }
} }
} }
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
pub(crate) fn set_content_security_policy(&self) {
let doc = self.owner_document();
let meta_element = self.upcast::<Element>();
if meta_element
.get_string_attribute(&local_name!("http-equiv"))
.to_ascii_lowercase() !=
*"content-security-policy" ||
meta_element
.get_attribute(&ns!(), &local_name!("content"))
.is_none()
{
return;
}
let mut csp_list: Option<CspList> = None;
if let Some(ref content) = meta_element.get_attribute(&ns!(), &local_name!("content")) {
let content = content.value();
let content_val = content.trim();
if !content_val.is_empty() {
let policies =
CspList::parse(content_val, PolicySource::Meta, PolicyDisposition::Enforce);
match csp_list {
Some(ref mut csp_list) => csp_list.append(policies),
None => csp_list = Some(policies),
}
}
}
doc.set_csp_list(csp_list);
}
/// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps> /// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
fn declarative_refresh(&self) { fn declarative_refresh(&self) {
if !self.upcast::<Node>().is_in_a_document_tree() { if !self.upcast::<Node>().is_in_a_document_tree() {

View file

@ -821,15 +821,7 @@ impl ParserContext {
let Some(parser) = self.parser.as_ref().map(|p| p.root()) else { let Some(parser) = self.parser.as_ref().map(|p| p.root()) else {
return; return;
}; };
let new_csp_list = match parser.document.get_csp_list() { parser.document.set_csp_list(Some(parent_csp_list.clone()));
None => parent_csp_list.clone(),
Some(original_csp_list) => {
let mut appended_csp_list = original_csp_list.clone();
appended_csp_list.append(parent_csp_list.clone());
appended_csp_list.to_owned()
},
};
parser.document.set_csp_list(Some(new_csp_list));
} }
} }

View file

@ -397319,6 +397319,10 @@
"062d823228a0bad1ed84fc432a262501e11a7d79", "062d823228a0bad1ed84fc432a262501e11a7d79",
[] []
], ],
"meta-append-header.html.headers": [
"85de8bd415def35ca45c0abf74590cdfa393d0f4",
[]
],
"meta-outside-head.sub.html.sub.headers": [ "meta-outside-head.sub.html.sub.headers": [
"8e90073147a233a74727a3ba03307c1564dc3224", "8e90073147a233a74727a3ba03307c1564dc3224",
[] []
@ -572954,6 +572958,13 @@
{} {}
] ]
], ],
"meta-append-header.html": [
"2b95e92fdb72b54b716ac51196acc93afab17f92",
[
null,
{}
]
],
"meta-img-src.html": [ "meta-img-src.html": [
"bc7ffd66a70d78c4ae9f2cc19f8c2df70914243d", "bc7ffd66a70d78c4ae9f2cc19f8c2df70914243d",
[ [
@ -572968,6 +572979,13 @@
{} {}
] ]
], ],
"meta-multiple-csp.html": [
"f09ead7f3e7588f88aadda68e72b1c9362d59910",
[
null,
{}
]
],
"meta-outside-head.sub.html": [ "meta-outside-head.sub.html": [
"7a706c2fc5f5d21b76e7f62f224c3ff74c80ba79", "7a706c2fc5f5d21b76e7f62f224c3ff74c80ba79",
[ [

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<meta http-equiv="Content-Security-Policy" content="img-src 'none'">
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<body>
<script nonce="abc">
promise_test(t => {
return new Promise((resolve, reject) => {
const img = document.createElement("img");
img.src = "/content-security-policy/support/fail.png";
img.onload = () => reject("Image loaded unexpectedly");
img.onerror = (e) => resolve()
document.body.appendChild(img);
});
}, "dynamic img-src is blocked by meta CSP");
promise_test(t => {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "../resources/ran.js";
script.onload = () => reject("Script executed unexpectedly");
script.onerror = () => resolve();
document.body.appendChild(script);
});
}, "script without `abc` nonce is blocked by header CSP");
</script>
</body>

View file

@ -0,0 +1 @@
Content-Security-Policy: script-src 'nonce-abc'

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-abc'">
<meta http-equiv="Content-Security-Policy" content="img-src 'none'">
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<body>
<script nonce="abc">
promise_test(t => {
return new Promise((resolve, reject) => {
const img = document.createElement("img");
img.src = "/content-security-policy/support/fail.png";
img.onload = () => reject("Image loaded unexpectedly");
img.onerror = (e) => resolve()
document.body.appendChild(img);
});
}, "dynamic img-src is blocked by meta CSP");
promise_test(t => {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "../resources/ran.js";
script.onload = () => reject("Script executed unexpectedly");
script.onerror = () => resolve();
document.body.appendChild(script);
});
}, "script without `abc` nonce is blocked by meta CSP");
</script>
</body>