tauri_utils/
html.rs

1// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use html5ever::{interface::QualName, namespace_url, ns, LocalName};
6use kuchiki::{Attribute, ExpandedName, NodeRef};
7
8/// Injects the invoke key token to each script on the document.
9///
10/// The invoke key token is replaced at runtime with the actual invoke key value.
11pub fn inject_invoke_key_token(document: &mut NodeRef) {
12  let mut targets = vec![];
13  if let Ok(scripts) = document.select("script") {
14    for target in scripts {
15      targets.push(target);
16    }
17    for target in targets {
18      let node = target.as_node();
19      let element = node.as_element().unwrap();
20
21      let attrs = element.attributes.borrow();
22      // if the script is external (has `src`), we won't inject the token
23      if attrs.get("src").is_some() {
24        continue;
25      }
26
27      let replacement_node = match attrs.get("type") {
28        Some("module") | Some("application/ecmascript") => {
29          let replacement_node = NodeRef::new_element(
30            QualName::new(None, ns!(html), "script".into()),
31            element
32              .attributes
33              .borrow()
34              .clone()
35              .map
36              .into_iter()
37              .collect::<Vec<_>>(),
38          );
39          let script = node.text_contents();
40          replacement_node.append(NodeRef::new_text(format!(
41            r#"
42          const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
43          {}
44        "#,
45            script
46          )));
47          replacement_node
48        }
49        Some("application/javascript") | None => {
50          let replacement_node = NodeRef::new_element(
51            QualName::new(None, ns!(html), "script".into()),
52            element
53              .attributes
54              .borrow()
55              .clone()
56              .map
57              .into_iter()
58              .collect::<Vec<_>>(),
59          );
60          let script = node.text_contents();
61          replacement_node.append(NodeRef::new_text(
62            script.replace("__TAURI_INVOKE_KEY__", "__TAURI__INVOKE_KEY_TOKEN__"),
63          ));
64          replacement_node
65        }
66        _ => {
67          continue;
68        }
69      };
70
71      node.insert_after(replacement_node);
72      node.detach();
73    }
74  }
75}
76
77/// Injects a content security policy to the HTML.
78pub fn inject_csp(document: &mut NodeRef, csp: &str) {
79  if let Ok(ref head) = document.select_first("head") {
80    head.as_node().append(create_csp_meta_tag(csp));
81  } else {
82    let head = NodeRef::new_element(
83      QualName::new(None, ns!(html), LocalName::from("head")),
84      None,
85    );
86    head.append(create_csp_meta_tag(csp));
87    document.prepend(head);
88  }
89}
90
91fn create_csp_meta_tag(csp: &str) -> NodeRef {
92  NodeRef::new_element(
93    QualName::new(None, ns!(html), LocalName::from("meta")),
94    vec![
95      (
96        ExpandedName::new(ns!(), LocalName::from("http-equiv")),
97        Attribute {
98          prefix: None,
99          value: "Content-Security-Policy".into(),
100        },
101      ),
102      (
103        ExpandedName::new(ns!(), LocalName::from("content")),
104        Attribute {
105          prefix: None,
106          value: csp.into(),
107        },
108      ),
109    ],
110  )
111}
112
113#[cfg(test)]
114mod tests {
115  use kuchiki::traits::*;
116  #[test]
117  fn csp() {
118    let htmls = vec![
119      "<html><head></head></html>".to_string(),
120      "<html></html>".to_string(),
121    ];
122    for html in htmls {
123      let mut document = kuchiki::parse_html().one(html);
124      let csp = "default-src 'self'; img-src https://*; child-src 'none';";
125      super::inject_csp(&mut document, csp);
126      assert_eq!(
127        document.to_string(),
128        format!(
129          r#"<html><head><meta content="{}" http-equiv="Content-Security-Policy"></head><body></body></html>"#,
130          csp
131        )
132      );
133    }
134  }
135}