script: Append to existing CSP list in set_csp_list

Signed-off-by: Emmanuel Paul Elom <elomemmanuel007@gmail.com>
This commit is contained in:
Emmanuel Paul Elom 2025-05-10 15:21:09 +01:00 committed by elomscansio
parent ed469fe72f
commit 7e61cee526
8 changed files with 133 additions and 60 deletions

View file

@ -4293,7 +4293,14 @@ impl Document {
}
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> {

View file

@ -2,19 +2,15 @@
* 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/. */
use content_security_policy::{CspList, PolicyDisposition, PolicySource};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlmetaelement::HTMLMetaElement;
use crate::dom::node::{BindContext, Node, NodeTraits, ShadowIncluding};
use crate::dom::node::{BindContext, Node};
use crate::dom::userscripts::load_script;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
@ -53,48 +49,6 @@ impl HTMLHeadElement {
n.upcast::<Node>().set_weird_parser_insertion_mode();
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 {

View file

@ -2,6 +2,7 @@
* 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/. */
use content_security_policy::{CspList, PolicyDisposition, PolicySource};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
@ -118,12 +119,47 @@ impl HTMLMetaElement {
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
fn apply_csp_list(&self) {
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
if let Some(head) = parent.downcast::<HTMLHeadElement>() {
head.set_content_security_policy();
if parent.downcast::<HTMLHeadElement>().is_some() {
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>
fn declarative_refresh(&self) {
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 {
return;
};
let new_csp_list = match parser.document.get_csp_list() {
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));
parser.document.set_csp_list(Some(parent_csp_list.clone()));
}
}

View file

@ -396020,6 +396020,10 @@
"062d823228a0bad1ed84fc432a262501e11a7d79",
[]
],
"meta-append-header.html.headers": [
"85de8bd415def35ca45c0abf74590cdfa393d0f4",
[]
],
"meta-outside-head.sub.html.sub.headers": [
"8e90073147a233a74727a3ba03307c1564dc3224",
[]
@ -570637,6 +570641,13 @@
{}
]
],
"meta-append-header.html": [
"2b95e92fdb72b54b716ac51196acc93afab17f92",
[
null,
{}
]
],
"meta-img-src.html": [
"bc7ffd66a70d78c4ae9f2cc19f8c2df70914243d",
[
@ -570651,6 +570662,13 @@
{}
]
],
"meta-multiple-csp.html": [
"f09ead7f3e7588f88aadda68e72b1c9362d59910",
[
null,
{}
]
],
"meta-outside-head.sub.html": [
"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>