diff --git a/src/html.rs b/src/html.rs
index c49148e..7ed0797 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -1087,28 +1087,20 @@ pub fn stringify_document(
let mut result = String::from_utf8(buf).unwrap();
+ // Take care of CSP
if opt_isolate || opt_no_css || opt_no_frames || opt_no_js || opt_no_images {
let mut buf: Vec = Vec::new();
let mut dom = html_to_dom(&result);
let doc = dom.get_document();
let html = get_child_node_by_name(&doc, "html");
let head = get_child_node_by_name(&html, "head");
- let mut content_attr = str!();
- if opt_isolate {
- content_attr += " default-src 'unsafe-inline' data:;";
- }
- if opt_no_css {
- content_attr += " style-src 'none';";
- }
- if opt_no_frames {
- content_attr += " frame-src 'none';child-src 'none';";
- }
- if opt_no_js {
- content_attr += " script-src 'none';";
- }
- if opt_no_images {
- content_attr += " img-src data:;";
- }
+ let csp_content: String = csp(
+ opt_isolate,
+ opt_no_css,
+ opt_no_frames,
+ opt_no_js,
+ opt_no_images,
+ );
let meta = dom.create_element(
QualName::new(None, ns!(), local_name!("meta")),
@@ -1119,28 +1111,63 @@ pub fn stringify_document(
},
Attribute {
name: QualName::new(None, ns!(), local_name!("content")),
- value: format_tendril!("{}", content_attr.trim()),
+ value: format_tendril!("{}", csp_content),
},
],
Default::default(),
);
- head.children.borrow_mut().reverse();
- head.children.borrow_mut().push(meta.clone());
- head.children.borrow_mut().reverse();
// Note: the CSP meta-tag has to be prepended, never appended,
// since there already may be one defined in the document,
// and browsers don't allow re-defining them (for obvious reasons)
+ head.children.borrow_mut().reverse();
+ head.children.borrow_mut().push(meta.clone());
+ head.children.borrow_mut().reverse();
+ // Note: we can't make it isolate the page right away since it may have no HEAD element,
+ // ergo we have to serialize, parse the DOM again, insert the CSP meta tag, and then
+ // finally serialize the result
serialize(&mut buf, &doc, SerializeOpts::default())
.expect("unable to serialize DOM into buffer");
result = String::from_utf8(buf).unwrap();
- // Note: we can't make it isolate the page right away since it may have no HEAD element,
- // ergo we have to serialize, parse DOM again, and then finally serialize the result
}
result
}
+pub fn csp(
+ opt_isolate: bool,
+ opt_no_css: bool,
+ opt_no_frames: bool,
+ opt_no_js: bool,
+ opt_no_images: bool,
+) -> String {
+ let mut string_list = vec![];
+
+ if opt_isolate {
+ string_list.push("default-src 'unsafe-inline' data:;");
+ }
+
+ if opt_no_css {
+ string_list.push("style-src 'none';");
+ }
+
+ if opt_no_frames {
+ string_list.push("frame-src 'none';");
+ string_list.push("child-src 'none';");
+ }
+
+ if opt_no_js {
+ string_list.push("script-src 'none';");
+ }
+
+ if opt_no_images {
+ // Note: data: is needed for transparent pixels
+ string_list.push("img-src data:;");
+ }
+
+ string_list.join(" ")
+}
+
pub fn metadata_tag(url: &str) -> String {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
diff --git a/src/tests/cli.rs b/src/tests/cli.rs
index 34dc9fd..d2824bf 100644
--- a/src/tests/cli.rs
+++ b/src/tests/cli.rs
@@ -142,7 +142,7 @@ mod passing {
assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(),
"\
- \
+ \
Hi\n"
);
diff --git a/src/tests/html/csp.rs b/src/tests/html/csp.rs
new file mode 100644
index 0000000..bc16124
--- /dev/null
+++ b/src/tests/html/csp.rs
@@ -0,0 +1,119 @@
+// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
+// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
+// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
+// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
+// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
+// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
+
+#[cfg(test)]
+mod passing {
+ use crate::html;
+
+ #[test]
+ fn isolated() {
+ let opt_isolate: bool = true;
+ let opt_no_css: bool = false;
+ let opt_no_frames: bool = false;
+ let opt_no_js: bool = false;
+ let opt_no_images: bool = false;
+ let csp_content = html::csp(
+ opt_isolate,
+ opt_no_css,
+ opt_no_frames,
+ opt_no_js,
+ opt_no_images,
+ );
+
+ assert_eq!(csp_content, "default-src 'unsafe-inline' data:;");
+ }
+
+ #[test]
+ fn no_css() {
+ let opt_isolate: bool = false;
+ let opt_no_css: bool = true;
+ let opt_no_frames: bool = false;
+ let opt_no_js: bool = false;
+ let opt_no_images: bool = false;
+ let csp_content = html::csp(
+ opt_isolate,
+ opt_no_css,
+ opt_no_frames,
+ opt_no_js,
+ opt_no_images,
+ );
+
+ assert_eq!(csp_content, "style-src 'none';");
+ }
+
+ #[test]
+ fn no_frames() {
+ let opt_isolate: bool = false;
+ let opt_no_css: bool = false;
+ let opt_no_frames: bool = true;
+ let opt_no_js: bool = false;
+ let opt_no_images: bool = false;
+ let csp_content = html::csp(
+ opt_isolate,
+ opt_no_css,
+ opt_no_frames,
+ opt_no_js,
+ opt_no_images,
+ );
+
+ assert_eq!(csp_content, "frame-src 'none'; child-src 'none';");
+ }
+
+ #[test]
+ fn no_js() {
+ let opt_isolate: bool = false;
+ let opt_no_css: bool = false;
+ let opt_no_frames: bool = false;
+ let opt_no_js: bool = true;
+ let opt_no_images: bool = false;
+ let csp_content = html::csp(
+ opt_isolate,
+ opt_no_css,
+ opt_no_frames,
+ opt_no_js,
+ opt_no_images,
+ );
+
+ assert_eq!(csp_content, "script-src 'none';");
+ }
+
+ #[test]
+ fn no_image() {
+ let opt_isolate: bool = false;
+ let opt_no_css: bool = false;
+ let opt_no_frames: bool = false;
+ let opt_no_js: bool = false;
+ let opt_no_images: bool = true;
+ let csp_content = html::csp(
+ opt_isolate,
+ opt_no_css,
+ opt_no_frames,
+ opt_no_js,
+ opt_no_images,
+ );
+
+ assert_eq!(csp_content, "img-src data:;");
+ }
+
+ #[test]
+ fn all() {
+ let opt_isolate: bool = true;
+ let opt_no_css: bool = true;
+ let opt_no_frames: bool = true;
+ let opt_no_js: bool = true;
+ let opt_no_images: bool = true;
+ let csp_content = html::csp(
+ opt_isolate,
+ opt_no_css,
+ opt_no_frames,
+ opt_no_js,
+ opt_no_images,
+ );
+
+ assert_eq!(csp_content, "default-src 'unsafe-inline' data:; style-src 'none'; frame-src 'none'; child-src 'none'; script-src 'none'; img-src data:;");
+ }
+}
diff --git a/src/tests/html/mod.rs b/src/tests/html/mod.rs
index cbe8321..09e65f7 100644
--- a/src/tests/html/mod.rs
+++ b/src/tests/html/mod.rs
@@ -1,3 +1,4 @@
+mod csp;
mod embed_srcset;
mod get_node_name;
mod has_proper_integrity;
diff --git a/src/tests/html/stringify_document.rs b/src/tests/html/stringify_document.rs
index 45bced9..14d9318 100644
--- a/src/tests/html/stringify_document.rs
+++ b/src/tests/html/stringify_document.rs
@@ -133,7 +133,7 @@ mod passing {
"\
\
\
- \
+ \
Frameless document\
\
\
@@ -173,7 +173,7 @@ mod passing {
"\
\
\
- \
+ \
no-frame no-css no-js no-image isolated document\
\
\