Files
tauri/core/tauri-utils/src/html.rs

136 lines
3.8 KiB
Rust

// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use html5ever::{interface::QualName, namespace_url, ns, LocalName};
use kuchiki::{Attribute, ExpandedName, NodeRef};
/// Injects the invoke key token to each script on the document.
///
/// The invoke key token is replaced at runtime with the actual invoke key value.
pub fn inject_invoke_key_token(document: &mut NodeRef) {
let mut targets = vec![];
if let Ok(scripts) = document.select("script") {
for target in scripts {
targets.push(target);
}
for target in targets {
let node = target.as_node();
let element = node.as_element().unwrap();
let attrs = element.attributes.borrow();
// if the script is external (has `src`), we won't inject the token
if attrs.get("src").is_some() {
continue;
}
let replacement_node = match attrs.get("type") {
Some("module") | Some("application/ecmascript") => {
let replacement_node = NodeRef::new_element(
QualName::new(None, ns!(html), "script".into()),
element
.attributes
.borrow()
.clone()
.map
.into_iter()
.collect::<Vec<_>>(),
);
let script = node.text_contents();
replacement_node.append(NodeRef::new_text(format!(
r#"
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
{}
"#,
script
)));
replacement_node
}
Some("application/javascript") | None => {
let replacement_node = NodeRef::new_element(
QualName::new(None, ns!(html), "script".into()),
element
.attributes
.borrow()
.clone()
.map
.into_iter()
.collect::<Vec<_>>(),
);
let script = node.text_contents();
replacement_node.append(NodeRef::new_text(
script.replace("__TAURI_INVOKE_KEY__", "__TAURI__INVOKE_KEY_TOKEN__"),
));
replacement_node
}
_ => {
continue;
}
};
node.insert_after(replacement_node);
node.detach();
}
}
}
/// Injects a content security policy to the HTML.
pub fn inject_csp(document: &mut NodeRef, csp: &str) {
if let Ok(ref head) = document.select_first("head") {
head.as_node().append(create_csp_meta_tag(csp));
} else {
let head = NodeRef::new_element(
QualName::new(None, ns!(html), LocalName::from("head")),
None,
);
head.append(create_csp_meta_tag(csp));
document.prepend(head);
}
}
fn create_csp_meta_tag(csp: &str) -> NodeRef {
NodeRef::new_element(
QualName::new(None, ns!(html), LocalName::from("meta")),
vec![
(
ExpandedName::new(ns!(), LocalName::from("http-equiv")),
Attribute {
prefix: None,
value: "Content-Security-Policy".into(),
},
),
(
ExpandedName::new(ns!(), LocalName::from("content")),
Attribute {
prefix: None,
value: csp.into(),
},
),
],
)
}
#[cfg(test)]
mod tests {
use kuchiki::traits::*;
#[test]
fn csp() {
let htmls = vec![
"<html><head></head></html>".to_string(),
"<html></html>".to_string(),
];
for html in htmls {
let mut document = kuchiki::parse_html().one(html);
let csp = "default-src 'self'; img-src https://*; child-src 'none';";
super::inject_csp(&mut document, csp);
assert_eq!(
document.to_string(),
format!(
r#"<html><head><meta content="{}" http-equiv="Content-Security-Policy"></head><body></body></html>"#,
csp
)
);
}
}
}