From ae5d6d2df49596033a64e72e86ad4438cbc4a4f2 Mon Sep 17 00:00:00 2001 From: Sunshine Date: Fri, 26 Jun 2020 16:19:44 -0400 Subject: [PATCH] refactor CSP code --- src/html.rs | 71 +++++++++++----- src/tests/cli.rs | 2 +- src/tests/html/csp.rs | 119 +++++++++++++++++++++++++++ src/tests/html/mod.rs | 1 + src/tests/html/stringify_document.rs | 4 +- 5 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 src/tests/html/csp.rs 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\ \ \