Skip to content

Commit

Permalink
fix(core) Fix HTML encoding in webview rendered via data url
Browse files Browse the repository at this point in the history
  • Loading branch information
huangminggg authored and huangmingg committed Feb 7, 2024
1 parent b0f2781 commit 7066f97
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 37 deletions.
13 changes: 11 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/tauri-runtime-wry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ rand = "0.8"
raw-window-handle = "0.5"
tracing = { version = "0.1", optional = true }
arboard = { version = "3", optional = true }
urlencoding = "2.1.3"

[target."cfg(windows)".dependencies]
webview2-com = "0.19.1"
Expand Down
37 changes: 31 additions & 6 deletions core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3246,13 +3246,38 @@ fn create_webview<T: UserEvent>(
if window_builder.center {
let _ = center_window(&window, window.inner_size());
}

let url_str = url.to_string();

let mut webview_builder = WebViewBuilder::new(window)
.map_err(|e| Error::CreateWebview(Box::new(e)))?
.with_focused(focused)
.with_url(&url)
.unwrap() // safe to unwrap because we validate the URL beforehand
.with_transparent(is_window_transparent)
.with_accept_first_mouse(webview_attributes.accept_first_mouse);
.map_err(|e| Error::CreateWebview(Box::new(e)))?;


webview_builder = match (url.scheme(), tauri_utils::html::is_html(&url_str)) {
// only use with_html method if it is a data url, and is a html.
("data", true) => {
let html = tauri_utils::html::extract_html_content(&url_str)
.map(|s| urlencoding::decode(s))
.unwrap() // safe to unwrap because we validate the URL beforehand
.unwrap(); // safe to unwrap because we validate the URL beforehand

webview_builder
.with_html(html)
.unwrap() // safe to unwrap because we validate the URL beforehand
}

// defaults to with_url method
_ => {
webview_builder
.with_url(&url_str)
.unwrap() // safe to unwrap because we validate the URL beforehand
}
};

webview_builder = webview_builder
.with_focused(focused)
.with_transparent(is_window_transparent)
.with_accept_first_mouse(webview_attributes.accept_first_mouse);
if webview_attributes.file_drop_handler_enabled {
webview_builder = webview_builder
.with_file_drop_handler(create_file_drop_handler(window_event_listeners.clone()));
Expand Down
1 change: 1 addition & 0 deletions core/tauri-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ system-tray = [ ]
macos-private-api = [ ]
global-shortcut = [ ]
clipboard = [ ]
window-data-url = [ "tauri-utils/window-data-url" ]
10 changes: 6 additions & 4 deletions core/tauri-runtime/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {

pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,

/// The resolved URL to load on the webview.
pub url: String,
/// The URL to load on the webview.
pub url: Url,
}

pub fn is_label_valid(label: &str) -> bool {
Expand Down Expand Up @@ -283,7 +283,8 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
js_event_listeners: Default::default(),
navigation_handler: Default::default(),
web_resource_request_handler: Default::default(),
url: "tauri://localhost".to_string(),
// This will never fail
url: Url::parse("tauri://localhost").unwrap(),
http_scheme: false,
})
}
Expand Down Expand Up @@ -315,7 +316,8 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
js_event_listeners: Default::default(),
navigation_handler: Default::default(),
web_resource_request_handler: Default::default(),
url: "tauri://localhost".to_string(),
// This will never fail
url: Url::parse("tauri://localhost").unwrap(),
http_scheme: false,
})
}
Expand Down
2 changes: 2 additions & 0 deletions core/tauri-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ semver = "1"
infer = "0.13"
dunce = "1"
log = "0.4.20"
data-url = { version = "0.3.1", optional = true }

[target."cfg(target_os = \"linux\")".dependencies]
heck = "0.4"
Expand All @@ -54,3 +55,4 @@ process-relaunch-dangerous-allow-symlink-macos = [ ]
config-json5 = [ "json5" ]
config-toml = [ "toml" ]
resources = [ "glob", "walkdir" ]
window-data-url = [ "data-url" ]
13 changes: 12 additions & 1 deletion core/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,18 @@ pub enum WindowUrl {
/// For instance, to load `tauri://localhost/users/john`,
/// you can simply provide `users/john` in this configuration.
App(PathBuf),
#[cfg(feature = "window-data-url")]
/// A data url, for example data:text/html,<h1>Hello world</h1>
/// Data url should not be encoded
DataUrl(Url),
}

impl fmt::Display for WindowUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::External(url) => write!(f, "{url}"),
Self::External(url)=> write!(f, "{url}"),
#[cfg(feature = "window-data-url")]
Self::DataUrl(url) => write!(f, "{url}"),
Self::App(path) => write!(f, "{}", path.display()),
}
}
Expand Down Expand Up @@ -3281,6 +3287,11 @@ mod build {
let url = url_lit(url);
quote! { #prefix::External(#url) }
}
#[cfg(feature = "window-data-url")]
Self::DataUrl(url) => {
let url = url_lit(url);
quote! { #prefix::DataUrl(#url) }
}
})
}
}
Expand Down
14 changes: 14 additions & 0 deletions core/tauri-utils/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,20 @@ pub fn inline_isolation(document: &mut NodeRef, dir: &Path) {
}
}

/// Temporary naive method to check if a string is a html
pub fn is_html(data_string: &str) -> bool {
data_string.contains('<') && data_string.contains('>')
}

/// Temporary naive method to extract data from html data string
pub fn extract_html_content(input: &str) -> Option<&str> {
if input.starts_with("data:text/html,") {
Some(&input[15..])
} else {
None
}
}

#[cfg(test)]
mod tests {
use kuchiki::traits::*;
Expand Down
10 changes: 8 additions & 2 deletions core/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ time = { version = "0.3", features = [ "parsing", "formatting" ], optional = tru
os_info = { version = "3", optional = true }
regex = { version = "1", optional = true }
glob = "0.3"
data-url = { version = "0.2", optional = true }
data-url = { version = "0.3.1", optional = true }
serialize-to-javascript = "=0.1.1"
infer = { version = "0.9", optional = true }
png = { version = "0.17", optional = true }
Expand All @@ -96,6 +96,7 @@ encoding_rs = "0.8.31"
sys-locale = { version = "0.2.3", optional = true }
tracing = { version = "0.1", optional = true }
indexmap = { version = "1", features = [ "std", "serde" ], optional = true }
urlencoding = "2.1.3"

[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
rfd = { version = "0.10", optional = true, features = [ "gtk3", "common-controls-v6" ] }
Expand Down Expand Up @@ -183,7 +184,7 @@ macos-private-api = [
"tauri-runtime-wry/macos-private-api"
]
windows7-compat = [ "win7-notifications" ]
window-data-url = [ "data-url" ]
window-data-url = [ "tauri-utils/window-data-url", "data-url" ]
api-all = [
"clipboard-all",
"dialog-all",
Expand Down Expand Up @@ -358,3 +359,8 @@ path = "../../examples/streaming/main.rs"
name = "isolation"
path = "../../examples/isolation/main.rs"
required-features = [ "isolation" ]

[[example]]
name = "data-url"
path = "../../examples/data-url/main.rs"
required-features = [ "window-data-url" ]
45 changes: 25 additions & 20 deletions core/tauri/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,18 +499,17 @@ impl<R: Runtime> WindowManager<R> {
});
}

let window_url = Url::parse(&pending.url).unwrap();
let window_origin = if window_url.scheme() == "data" {
let window_origin = if pending.url.scheme() == "data" {
"null".into()
} else if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" {
} else if cfg!(windows) && pending.url.scheme() != "http" && pending.url.scheme() != "https" {
let scheme = if pending.http_scheme { "http" } else { "https" };
format!("{scheme}://{}.localhost", window_url.scheme())
format!("{scheme}://{}.localhost", pending.url.scheme())
} else {
format!(
"{}://{}{}",
window_url.scheme(),
window_url.host().unwrap(),
window_url
pending.url.scheme(),
pending.url.host().unwrap(),
pending.url
.port()
.map(|p| format!(":{p}"))
.unwrap_or_default()
Expand Down Expand Up @@ -995,6 +994,8 @@ impl<R: Runtime> WindowManager<R> {
}
}
WindowUrl::External(url) => url.clone(),
#[cfg(feature = "window-data-url")]
WindowUrl::DataUrl(url) => url.clone(),
_ => unimplemented!(),
};

Expand All @@ -1006,22 +1007,26 @@ impl<R: Runtime> WindowManager<R> {
}

#[cfg(feature = "window-data-url")]
if let Some(csp) = self.csp() {
if url.scheme() == "data" {
if let Ok(data_url) = data_url::DataUrl::process(url.as_str()) {
let (body, _) = data_url.decode_to_vec().unwrap();
let html = String::from_utf8_lossy(&body).into_owned();
// naive way to check if it's an html
if html.contains('<') && html.contains('>') {
let mut document = tauri_utils::html::parse(html);
tauri_utils::html::inject_csp(&mut document, &csp.to_string());
url.set_path(&format!("text/html,{}", document.to_string()));
match (url.scheme(), tauri_utils::html::extract_html_content(url.as_str())) {
("data", Some(html_string)) => {
// There is an issue with the external DataUrl where HTML containing special characters
// are not correctly processed. A workaround is to first percent encode the html string,
// before it processed by DataUrl.
if let Ok(data_url) = data_url::DataUrl::process(&urlencoding::encode(&html_string)) {
if let Ok((body, _)) = data_url.decode_to_vec() {
let html = String::from_utf8_lossy(&body).into_owned();
let mut document = tauri_utils::html::parse(html);
if let Some(csp) = self.csp() {
tauri_utils::html::inject_csp(&mut document, &csp.to_string());
}
url.set_path(&format!("text/html,{}", document.to_string()));
}
}
}
}
}
_ => {}
};

pending.url = url.to_string();
pending.url = url;

if !pending.window_builder.has_icon() {
if let Some(default_window_icon) = self.inner.default_window_icon.clone() {
Expand Down
4 changes: 2 additions & 2 deletions core/tauri/src/test/mock_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
pub struct MockDispatcher {
id: WindowId,
context: RuntimeContext,
url: String,
url: url::Url,
last_evaluated_script: Arc<Mutex<Option<String>>>,
}

Expand Down Expand Up @@ -417,7 +417,7 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
}

fn url(&self) -> Result<url::Url> {
self.url.parse().map_err(|_| Error::FailedToReceiveMessage)
Ok(self.url.clone())
}

fn scale_factor(&self) -> Result<f64> {
Expand Down
3 changes: 3 additions & 0 deletions examples/data-url/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Data Url Example

To execute run the following on the root directory of the repository: `cargo run --example data-url --features window-data-url`.
11 changes: 11 additions & 0 deletions examples/data-url/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to Tauri!</title>
</head>
<body>
<h1>Welcome to Tauri!</h1>
</body>
</html>
46 changes: 46 additions & 0 deletions examples/data-url/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::WindowBuilder;

#[cfg(not(feature = "window-data-url"))]
fn main() {
compile_error!("Feature `window-data-url` is required to run this example");
}

#[cfg(feature = "window-data-url")]
fn main() {

tauri::Builder::default()
.setup(|app| {
let html = r#"
<html>
<body>
/+&'=#%?\^`{}|[]~ <br/>
Hello World <br/>
สวัสดีชาวโลก! <br/>
你好世界!<br/>
</body>
</html>"#;
let data = format!("data:text/html,{}", html);
#[allow(unused_mut)]
let mut builder =
WindowBuilder::new(
app,
"Rust".to_string(),
tauri::WindowUrl::DataUrl(data.parse().unwrap()));
#[cfg(target_os = "macos")]
{
builder = builder.tabbing_identifier("Rust");
}
let _window = builder.title("Tauri - Rust").build()?;

Ok(())
})
.run(tauri::generate_context!(
"../../examples/data-url/tauri.conf.json"
))
.expect("error while running tauri application");
}
Loading

0 comments on commit 7066f97

Please sign in to comment.