Skip to content

Commit 6132f3f

Browse files
authored
feat(core): reintroduce CSP injection (#1704)
1 parent 428d50a commit 6132f3f

File tree

10 files changed

+151
-12
lines changed

10 files changed

+151
-12
lines changed

.changes/csp.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri-codegen": patch
3+
"tauri-utils": patch
4+
"tauri": patch
5+
---
6+
7+
Reintroduce `csp` injection, configured on `tauri.conf.json > tauri > security > csp`.

core/tauri-codegen/src/context.rs

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

5-
use crate::embedded_assets::{EmbeddedAssets, EmbeddedAssetsError};
5+
use crate::embedded_assets::{AssetOptions, EmbeddedAssets, EmbeddedAssetsError};
66
use proc_macro2::TokenStream;
77
use quote::quote;
88
use std::path::PathBuf;
@@ -37,7 +37,11 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
3737

3838
// generate the assets inside the dist dir into a perfect hash function
3939
let assets = if let Some(assets_path) = assets_path {
40-
EmbeddedAssets::new(&assets_path)?
40+
let mut options = AssetOptions::new();
41+
if let Some(csp) = &config.tauri.security.csp {
42+
options = options.csp(csp.clone());
43+
}
44+
EmbeddedAssets::new(&assets_path, options)?
4145
} else {
4246
Default::default()
4347
};

core/tauri-codegen/src/embedded_assets.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use proc_macro2::TokenStream;
66
use quote::{quote, ToTokens, TokenStreamExt};
77
use std::{
88
collections::HashMap,
9+
ffi::OsStr,
910
fs::File,
1011
path::{Path, PathBuf},
1112
};
12-
use tauri_utils::assets::AssetKey;
13+
use tauri_utils::{assets::AssetKey, html::inject_csp};
1314
use thiserror::Error;
1415
use walkdir::WalkDir;
1516

@@ -62,9 +63,28 @@ pub enum EmbeddedAssetsError {
6263
#[derive(Default)]
6364
pub struct EmbeddedAssets(HashMap<AssetKey, (PathBuf, PathBuf)>);
6465

66+
/// Options used to embed assets.
67+
#[derive(Default)]
68+
pub struct AssetOptions {
69+
csp: Option<String>,
70+
}
71+
72+
impl AssetOptions {
73+
/// Creates the default asset options.
74+
pub fn new() -> Self {
75+
Self::default()
76+
}
77+
78+
/// Sets the content security policy to add to HTML files.
79+
pub fn csp(mut self, csp: String) -> Self {
80+
self.csp.replace(csp);
81+
self
82+
}
83+
}
84+
6585
impl EmbeddedAssets {
6686
/// Compress a directory of assets, ready to be generated into a [`tauri_utils::assets::Assets`].
67-
pub fn new(path: &Path) -> Result<Self, EmbeddedAssetsError> {
87+
pub fn new(path: &Path, options: AssetOptions) -> Result<Self, EmbeddedAssetsError> {
6888
WalkDir::new(&path)
6989
.follow_links(true)
7090
.into_iter()
@@ -73,7 +93,7 @@ impl EmbeddedAssets {
7393
Ok(entry) if entry.file_type().is_dir() => None,
7494

7595
// compress all files encountered
76-
Ok(entry) => Some(Self::compress_file(path, entry.path())),
96+
Ok(entry) => Some(Self::compress_file(path, entry.path(), &options)),
7797

7898
// pass down error through filter to fail when encountering any error
7999
Err(error) => Some(Err(EmbeddedAssetsError::Walkdir {
@@ -96,11 +116,22 @@ impl EmbeddedAssets {
96116
}
97117

98118
/// Compress a file and spit out the information in a [`HashMap`] friendly form.
99-
fn compress_file(prefix: &Path, path: &Path) -> Result<Asset, EmbeddedAssetsError> {
100-
let input = std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {
119+
fn compress_file(
120+
prefix: &Path,
121+
path: &Path,
122+
options: &AssetOptions,
123+
) -> Result<Asset, EmbeddedAssetsError> {
124+
let mut input = std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {
101125
path: path.to_owned(),
102126
error,
103127
})?;
128+
if let Some(csp) = &options.csp {
129+
if path.extension() == Some(OsStr::new("html")) {
130+
input = inject_csp(String::from_utf8_lossy(&input).into_owned(), csp)
131+
.as_bytes()
132+
.to_vec();
133+
}
134+
}
104135

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

core/tauri-utils/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ thiserror = "1.0.24"
1616
phf = { version = "0.8", features = [ "macros" ] }
1717
zstd = "0.7"
1818
url = { version = "2.2", features = [ "serde" ] }
19+
kuchiki = "0.8"
20+
html5ever = "0.25"
1921
proc-macro2 = { version = "1.0", optional = true }
2022
quote = { version = "1.0", optional = true }
2123

core/tauri-utils/src/config.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ impl Default for UpdaterConfig {
164164
}
165165
}
166166

167+
/// Security configuration.
168+
#[derive(PartialEq, Deserialize, Debug, Clone, Default)]
169+
#[serde(tag = "updater", rename_all = "camelCase")]
170+
pub struct SecurityConfig {
171+
/// Content security policy to inject to HTML files with the custom protocol.
172+
pub csp: Option<String>,
173+
}
174+
167175
/// A CLI argument definition
168176
#[derive(PartialEq, Deserialize, Debug, Default, Clone)]
169177
#[serde(rename_all = "camelCase")]
@@ -340,6 +348,9 @@ pub struct TauriConfig {
340348
/// The updater configuration.
341349
#[serde(default)]
342350
pub updater: UpdaterConfig,
351+
/// The security configuration.
352+
#[serde(default)]
353+
pub security: SecurityConfig,
343354
}
344355

345356
impl Default for TauriConfig {
@@ -349,6 +360,7 @@ impl Default for TauriConfig {
349360
cli: None,
350361
bundle: BundleConfig::default(),
351362
updater: UpdaterConfig::default(),
363+
security: SecurityConfig::default(),
352364
}
353365
}
354366
}
@@ -756,14 +768,23 @@ mod build {
756768
}
757769
}
758770

771+
impl ToTokens for SecurityConfig {
772+
fn to_tokens(&self, tokens: &mut TokenStream) {
773+
let csp = opt_str_lit(self.csp.as_ref());
774+
775+
literal_struct!(tokens, SecurityConfig, csp);
776+
}
777+
}
778+
759779
impl ToTokens for TauriConfig {
760780
fn to_tokens(&self, tokens: &mut TokenStream) {
761781
let windows = vec_lit(&self.windows, identity);
762782
let cli = opt_lit(self.cli.as_ref());
763783
let bundle = &self.bundle;
764784
let updater = &self.updater;
785+
let security = &self.security;
765786

766-
literal_struct!(tokens, TauriConfig, windows, cli, bundle, updater);
787+
literal_struct!(tokens, TauriConfig, windows, cli, bundle, updater, security);
767788
}
768789
}
769790

@@ -857,6 +878,7 @@ mod test {
857878
pubkey: None,
858879
endpoints: None,
859880
},
881+
security: SecurityConfig { csp: None },
860882
};
861883

862884
// create a build config

core/tauri-utils/src/html.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
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};
12+
13+
/// 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);
16+
if let Ok(ref head) = document.select_first("head") {
17+
head.as_node().append(create_csp_meta_tag(csp));
18+
} else {
19+
let head = NodeRef::new_element(
20+
QualName::new(None, ns!(html), LocalName::from("head")),
21+
None,
22+
);
23+
head.append(create_csp_meta_tag(csp));
24+
document.prepend(head);
25+
}
26+
document.to_string()
27+
}
28+
29+
fn create_csp_meta_tag(csp: &str) -> NodeRef {
30+
NodeRef::new_element(
31+
QualName::new(None, ns!(html), LocalName::from("meta")),
32+
vec![
33+
(
34+
ExpandedName::new(ns!(), LocalName::from("http-equiv")),
35+
Attribute {
36+
prefix: None,
37+
value: "Content-Security-Policy".into(),
38+
},
39+
),
40+
(
41+
ExpandedName::new(ns!(), LocalName::from("content")),
42+
Attribute {
43+
prefix: None,
44+
value: csp.into(),
45+
},
46+
),
47+
],
48+
)
49+
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
#[test]
54+
fn csp() {
55+
let htmls = vec![
56+
"<html><head></head></html>".to_string(),
57+
"<html></html>".to_string(),
58+
];
59+
for html in htmls {
60+
let csp = "default-src 'self'; img-src https://*; child-src 'none';";
61+
let new = super::inject_csp(html, csp);
62+
assert_eq!(
63+
new,
64+
format!(
65+
r#"<html><head><meta content="{}" http-equiv="Content-Security-Policy"></head><body></body></html>"#,
66+
csp
67+
)
68+
);
69+
}
70+
}
71+
}

core/tauri-utils/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
pub mod assets;
1010
/// Tauri config definition.
1111
pub mod config;
12+
/// Tauri HTML processing.
13+
pub mod html;
1214
/// Platform helpers
1315
pub mod platform;
1416
/// Process helpers

examples/api/src-tauri/tauri.conf.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
}
7979
],
8080
"security": {
81-
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
81+
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline' img-src: 'self'"
8282
}
8383
}
84-
}
84+
}

examples/splashscreen/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
}
4343
],
4444
"security": {
45-
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
45+
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline' img-src: 'self'"
4646
},
4747
"updater": {
4848
"active": false

tooling/cli.rs/templates/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
}
6262
],
6363
"security": {
64-
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
64+
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline' img-src: 'self'"
6565
}
6666
}
6767
}

0 commit comments

Comments
 (0)