refactor CSP code
This commit is contained in:
parent
2ed151d883
commit
ae5d6d2df4
5 changed files with 172 additions and 25 deletions
71
src/html.rs
71
src/html.rs
|
@ -1087,28 +1087,20 @@ pub fn stringify_document(
|
||||||
|
|
||||||
let mut result = String::from_utf8(buf).unwrap();
|
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 {
|
if opt_isolate || opt_no_css || opt_no_frames || opt_no_js || opt_no_images {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
let mut dom = html_to_dom(&result);
|
let mut dom = html_to_dom(&result);
|
||||||
let doc = dom.get_document();
|
let doc = dom.get_document();
|
||||||
let html = get_child_node_by_name(&doc, "html");
|
let html = get_child_node_by_name(&doc, "html");
|
||||||
let head = get_child_node_by_name(&html, "head");
|
let head = get_child_node_by_name(&html, "head");
|
||||||
let mut content_attr = str!();
|
let csp_content: String = csp(
|
||||||
if opt_isolate {
|
opt_isolate,
|
||||||
content_attr += " default-src 'unsafe-inline' data:;";
|
opt_no_css,
|
||||||
}
|
opt_no_frames,
|
||||||
if opt_no_css {
|
opt_no_js,
|
||||||
content_attr += " style-src 'none';";
|
opt_no_images,
|
||||||
}
|
);
|
||||||
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 meta = dom.create_element(
|
let meta = dom.create_element(
|
||||||
QualName::new(None, ns!(), local_name!("meta")),
|
QualName::new(None, ns!(), local_name!("meta")),
|
||||||
|
@ -1119,28 +1111,63 @@ pub fn stringify_document(
|
||||||
},
|
},
|
||||||
Attribute {
|
Attribute {
|
||||||
name: QualName::new(None, ns!(), local_name!("content")),
|
name: QualName::new(None, ns!(), local_name!("content")),
|
||||||
value: format_tendril!("{}", content_attr.trim()),
|
value: format_tendril!("{}", csp_content),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
Default::default(),
|
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,
|
// Note: the CSP meta-tag has to be prepended, never appended,
|
||||||
// since there already may be one defined in the document,
|
// since there already may be one defined in the document,
|
||||||
// and browsers don't allow re-defining them (for obvious reasons)
|
// 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())
|
serialize(&mut buf, &doc, SerializeOpts::default())
|
||||||
.expect("unable to serialize DOM into buffer");
|
.expect("unable to serialize DOM into buffer");
|
||||||
result = String::from_utf8(buf).unwrap();
|
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
|
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 {
|
pub fn metadata_tag(url: &str) -> String {
|
||||||
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
|
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
|
||||||
|
|
||||||
|
|
119
src/tests/html/csp.rs
Normal file
119
src/tests/html/csp.rs
Normal file
|
@ -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:;");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod csp;
|
||||||
mod embed_srcset;
|
mod embed_srcset;
|
||||||
mod get_node_name;
|
mod get_node_name;
|
||||||
mod has_proper_integrity;
|
mod has_proper_integrity;
|
||||||
|
|
Loading…
Reference in a new issue