Skip to content

Commit f03eea9

Browse files
authored
feat(core): inject invoke key on <script type="module"> (#2120)
1 parent 94a5848 commit f03eea9

File tree

5 files changed

+113
-58
lines changed

5 files changed

+113
-58
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
"tauri-codegen": patch
4+
"tauri-utils": patch
5+
---
6+
7+
Inject invoke key on `script` tags with `type="module"`.

core/tauri-codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ tauri-utils = { version = "1.0.0-beta.1", path = "../tauri-utils", features = [
2121
thiserror = "1"
2222
walkdir = "2"
2323
zstd = "0.9"
24+
kuchiki = "0.8"

core/tauri-codegen/src/embedded_assets.rs

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5+
use kuchiki::traits::*;
56
use proc_macro2::TokenStream;
67
use quote::{quote, ToTokens, TokenStreamExt};
78
use std::{
@@ -10,7 +11,10 @@ use std::{
1011
fs::File,
1112
path::{Path, PathBuf},
1213
};
13-
use tauri_utils::{assets::AssetKey, html::inject_csp};
14+
use tauri_utils::{
15+
assets::AssetKey,
16+
html::{inject_csp, inject_invoke_key_token},
17+
};
1418
use thiserror::Error;
1519
use walkdir::WalkDir;
1620

@@ -170,45 +174,47 @@ impl EmbeddedAssets {
170174
path: path.to_owned(),
171175
error,
172176
})?;
173-
if let Some(csp) = &options.csp {
174-
if path.extension() == Some(OsStr::new("html")) {
175-
input = inject_csp(String::from_utf8_lossy(&input).into_owned(), csp)
177+
if path.extension() == Some(OsStr::new("html")) {
178+
let mut document = kuchiki::parse_html().one(String::from_utf8_lossy(&input).into_owned());
179+
if let Some(csp) = &options.csp {
180+
inject_csp(&mut document, csp);
181+
}
182+
inject_invoke_key_token(&mut document);
183+
input = document.to_string().as_bytes().to_vec();
184+
} else {
185+
let is_javascript = ["js", "cjs", "mjs"]
186+
.iter()
187+
.any(|e| path.extension() == Some(OsStr::new(e)));
188+
if is_javascript {
189+
let js = String::from_utf8_lossy(&input).into_owned();
190+
input = if [
191+
"import{", "import*", "import ", "export{", "export*", "export ",
192+
]
193+
.iter()
194+
.any(|t| js.contains(t))
195+
{
196+
format!(
197+
r#"
198+
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
199+
{}
200+
"#,
201+
js
202+
)
203+
.as_bytes()
204+
.to_vec()
205+
} else {
206+
format!(
207+
r#"(function () {{
208+
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
209+
{}
210+
}})()"#,
211+
js
212+
)
176213
.as_bytes()
177-
.to_vec();
214+
.to_vec()
215+
};
178216
}
179217
}
180-
let is_javascript = ["js", "cjs", "mjs"]
181-
.iter()
182-
.any(|e| path.extension() == Some(OsStr::new(e)));
183-
if is_javascript {
184-
let js = String::from_utf8_lossy(&input).into_owned();
185-
input = if [
186-
"import{", "import*", "import ", "export{", "export*", "export ",
187-
]
188-
.iter()
189-
.any(|t| js.contains(t))
190-
{
191-
format!(
192-
r#"
193-
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
194-
{}
195-
"#,
196-
js
197-
)
198-
.as_bytes()
199-
.to_vec()
200-
} else {
201-
format!(
202-
r#"(function () {{
203-
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
204-
{}
205-
}})()"#,
206-
js
207-
)
208-
.as_bytes()
209-
.to_vec()
210-
};
211-
}
212218

213219
// we must canonicalize the base of our paths to allow long paths on windows
214220
let out_dir = std::env::var("OUT_DIR")

core/tauri-utils/src/html.rs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,55 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5-
use html5ever::{
6-
interface::QualName,
7-
namespace_url, ns,
8-
tendril::{fmt::UTF8, NonAtomic, Tendril},
9-
LocalName,
10-
};
11-
use kuchiki::{traits::*, Attribute, ExpandedName, NodeRef};
5+
use html5ever::{interface::QualName, namespace_url, ns, LocalName};
6+
use 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.
11+
pub 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`) or its type is not "module", we won't inject the token
23+
if attrs.get("src").is_some() || attrs.get("type") != Some("module") {
24+
continue;
25+
}
26+
27+
let replacement_node = NodeRef::new_element(
28+
QualName::new(None, ns!(html), "script".into()),
29+
element
30+
.attributes
31+
.borrow()
32+
.clone()
33+
.map
34+
.into_iter()
35+
.collect::<Vec<_>>(),
36+
);
37+
let script = node.text_contents();
38+
replacement_node.append(NodeRef::new_text(format!(
39+
r#"
40+
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
41+
{}
42+
"#,
43+
script
44+
)));
45+
46+
node.insert_after(replacement_node);
47+
node.detach();
48+
}
49+
}
50+
}
1251

1352
/// Injects a content security policy to the HTML.
14-
pub fn inject_csp<H: Into<Tendril<UTF8, NonAtomic>>>(html: H, csp: &str) -> String {
15-
let document = kuchiki::parse_html().one(html);
53+
pub fn inject_csp(document: &mut NodeRef, csp: &str) {
1654
if let Ok(ref head) = document.select_first("head") {
1755
head.as_node().append(create_csp_meta_tag(csp));
1856
} else {
@@ -23,7 +61,6 @@ pub fn inject_csp<H: Into<Tendril<UTF8, NonAtomic>>>(html: H, csp: &str) -> Stri
2361
head.append(create_csp_meta_tag(csp));
2462
document.prepend(head);
2563
}
26-
document.to_string()
2764
}
2865

2966
fn create_csp_meta_tag(csp: &str) -> NodeRef {
@@ -50,17 +87,19 @@ fn create_csp_meta_tag(csp: &str) -> NodeRef {
5087

5188
#[cfg(test)]
5289
mod tests {
90+
use kuchiki::traits::*;
5391
#[test]
5492
fn csp() {
5593
let htmls = vec![
5694
"<html><head></head></html>".to_string(),
5795
"<html></html>".to_string(),
5896
];
5997
for html in htmls {
98+
let mut document = kuchiki::parse_html().one(html);
6099
let csp = "default-src 'self'; img-src https://*; child-src 'none';";
61-
let new = super::inject_csp(html, csp);
100+
super::inject_csp(&mut document, csp);
62101
assert_eq!(
63-
new,
102+
document.to_string(),
64103
format!(
65104
r#"<html><head><meta content="{}" http-equiv="Content-Security-Policy"></head><body></body></html>"#,
66105
csp

core/tauri/src/manager.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ impl<P: Params> WindowManager<P> {
459459
};
460460
let is_javascript =
461461
path.ends_with(".js") || path.ends_with(".cjs") || path.ends_with(".mjs");
462+
let is_html = path.ends_with(".html");
462463

463464
let asset_response = assets
464465
.get(&path)
@@ -471,16 +472,17 @@ impl<P: Params> WindowManager<P> {
471472
.map(Cow::into_owned);
472473
match asset_response {
473474
Ok(asset) => {
474-
if is_javascript {
475-
let js = String::from_utf8_lossy(&asset).into_owned();
475+
if is_javascript || is_html {
476+
let contents = String::from_utf8_lossy(&asset).into_owned();
476477
Ok(
477-
js.replacen(
478-
"__TAURI__INVOKE_KEY_TOKEN__",
479-
&manager.generate_invoke_key().to_string(),
480-
1,
481-
)
482-
.as_bytes()
483-
.to_vec(),
478+
contents
479+
.replacen(
480+
"__TAURI__INVOKE_KEY_TOKEN__",
481+
&manager.generate_invoke_key().to_string(),
482+
1,
483+
)
484+
.as_bytes()
485+
.to_vec(),
484486
)
485487
} else {
486488
Ok(asset)

0 commit comments

Comments
 (0)