mirror of https://github.com/tauri-apps/tauri
feat(core): inject invoke key on `<script type="module">` (#2120)
This commit is contained in:
parent
94a5848afb
commit
f03eea9c9b
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"tauri": patch
|
||||
"tauri-codegen": patch
|
||||
"tauri-utils": patch
|
||||
---
|
||||
|
||||
Inject invoke key on `script` tags with `type="module"`.
|
|
@ -21,3 +21,4 @@ tauri-utils = { version = "1.0.0-beta.1", path = "../tauri-utils", features = [
|
|||
thiserror = "1"
|
||||
walkdir = "2"
|
||||
zstd = "0.9"
|
||||
kuchiki = "0.8"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use kuchiki::traits::*;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use std::{
|
||||
|
@ -10,7 +11,10 @@ use std::{
|
|||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tauri_utils::{assets::AssetKey, html::inject_csp};
|
||||
use tauri_utils::{
|
||||
assets::AssetKey,
|
||||
html::{inject_csp, inject_invoke_key_token},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
|
@ -170,44 +174,46 @@ impl EmbeddedAssets {
|
|||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
if let Some(csp) = &options.csp {
|
||||
if path.extension() == Some(OsStr::new("html")) {
|
||||
input = inject_csp(String::from_utf8_lossy(&input).into_owned(), csp)
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
if path.extension() == Some(OsStr::new("html")) {
|
||||
let mut document = kuchiki::parse_html().one(String::from_utf8_lossy(&input).into_owned());
|
||||
if let Some(csp) = &options.csp {
|
||||
inject_csp(&mut document, csp);
|
||||
}
|
||||
inject_invoke_key_token(&mut document);
|
||||
input = document.to_string().as_bytes().to_vec();
|
||||
} else {
|
||||
let is_javascript = ["js", "cjs", "mjs"]
|
||||
.iter()
|
||||
.any(|e| path.extension() == Some(OsStr::new(e)));
|
||||
if is_javascript {
|
||||
let js = String::from_utf8_lossy(&input).into_owned();
|
||||
input = if [
|
||||
"import{", "import*", "import ", "export{", "export*", "export ",
|
||||
]
|
||||
.iter()
|
||||
.any(|t| js.contains(t))
|
||||
{
|
||||
format!(
|
||||
r#"
|
||||
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
|
||||
{}
|
||||
"#,
|
||||
js
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
} else {
|
||||
format!(
|
||||
r#"(function () {{
|
||||
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
|
||||
{}
|
||||
}})()"#,
|
||||
js
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
};
|
||||
}
|
||||
}
|
||||
let is_javascript = ["js", "cjs", "mjs"]
|
||||
.iter()
|
||||
.any(|e| path.extension() == Some(OsStr::new(e)));
|
||||
if is_javascript {
|
||||
let js = String::from_utf8_lossy(&input).into_owned();
|
||||
input = if [
|
||||
"import{", "import*", "import ", "export{", "export*", "export ",
|
||||
]
|
||||
.iter()
|
||||
.any(|t| js.contains(t))
|
||||
{
|
||||
format!(
|
||||
r#"
|
||||
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
|
||||
{}
|
||||
"#,
|
||||
js
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
} else {
|
||||
format!(
|
||||
r#"(function () {{
|
||||
const __TAURI_INVOKE_KEY__ = __TAURI__INVOKE_KEY_TOKEN__;
|
||||
{}
|
||||
}})()"#,
|
||||
js
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
};
|
||||
}
|
||||
|
||||
// we must canonicalize the base of our paths to allow long paths on windows
|
||||
|
|
|
@ -2,17 +2,55 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use html5ever::{
|
||||
interface::QualName,
|
||||
namespace_url, ns,
|
||||
tendril::{fmt::UTF8, NonAtomic, Tendril},
|
||||
LocalName,
|
||||
};
|
||||
use kuchiki::{traits::*, Attribute, ExpandedName, NodeRef};
|
||||
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`) or its type is not "module", we won't inject the token
|
||||
if attrs.get("src").is_some() || attrs.get("type") != Some("module") {
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
)));
|
||||
|
||||
node.insert_after(replacement_node);
|
||||
node.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Injects a content security policy to the HTML.
|
||||
pub fn inject_csp<H: Into<Tendril<UTF8, NonAtomic>>>(html: H, csp: &str) -> String {
|
||||
let document = kuchiki::parse_html().one(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 {
|
||||
|
@ -23,7 +61,6 @@ pub fn inject_csp<H: Into<Tendril<UTF8, NonAtomic>>>(html: H, csp: &str) -> Stri
|
|||
head.append(create_csp_meta_tag(csp));
|
||||
document.prepend(head);
|
||||
}
|
||||
document.to_string()
|
||||
}
|
||||
|
||||
fn create_csp_meta_tag(csp: &str) -> NodeRef {
|
||||
|
@ -50,6 +87,7 @@ fn create_csp_meta_tag(csp: &str) -> NodeRef {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kuchiki::traits::*;
|
||||
#[test]
|
||||
fn csp() {
|
||||
let htmls = vec![
|
||||
|
@ -57,10 +95,11 @@ mod tests {
|
|||
"<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';";
|
||||
let new = super::inject_csp(html, csp);
|
||||
super::inject_csp(&mut document, csp);
|
||||
assert_eq!(
|
||||
new,
|
||||
document.to_string(),
|
||||
format!(
|
||||
r#"<html><head><meta content="{}" http-equiv="Content-Security-Policy"></head><body></body></html>"#,
|
||||
csp
|
||||
|
|
|
@ -459,6 +459,7 @@ impl<P: Params> WindowManager<P> {
|
|||
};
|
||||
let is_javascript =
|
||||
path.ends_with(".js") || path.ends_with(".cjs") || path.ends_with(".mjs");
|
||||
let is_html = path.ends_with(".html");
|
||||
|
||||
let asset_response = assets
|
||||
.get(&path)
|
||||
|
@ -471,16 +472,17 @@ impl<P: Params> WindowManager<P> {
|
|||
.map(Cow::into_owned);
|
||||
match asset_response {
|
||||
Ok(asset) => {
|
||||
if is_javascript {
|
||||
let js = String::from_utf8_lossy(&asset).into_owned();
|
||||
if is_javascript || is_html {
|
||||
let contents = String::from_utf8_lossy(&asset).into_owned();
|
||||
Ok(
|
||||
js.replacen(
|
||||
"__TAURI__INVOKE_KEY_TOKEN__",
|
||||
&manager.generate_invoke_key().to_string(),
|
||||
1,
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
contents
|
||||
.replacen(
|
||||
"__TAURI__INVOKE_KEY_TOKEN__",
|
||||
&manager.generate_invoke_key().to_string(),
|
||||
1,
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)
|
||||
} else {
|
||||
Ok(asset)
|
||||
|
|
Loading…
Reference in New Issue