diff --git a/.changes/remote-urls.md b/.changes/remote-urls.md new file mode 100644 index 00000000000..71d094cd97a --- /dev/null +++ b/.changes/remote-urls.md @@ -0,0 +1,7 @@ +--- +"tauri": patch +"tauri-runtime": patch +"tauri-runtime-wry": patch +--- + +Block remote URLs from accessing the IPC. diff --git a/core/tauri-build/src/static_vcruntime.rs b/core/tauri-build/src/static_vcruntime.rs index a707ac93272..f079763cb31 100644 --- a/core/tauri-build/src/static_vcruntime.rs +++ b/core/tauri-build/src/static_vcruntime.rs @@ -54,5 +54,5 @@ fn override_msvcrt_lib() { f.write_all(bytes).unwrap(); } // Add the output directory to the native library path. - println!("cargo:rustc-link-search=native={}", out_dir); + println!("cargo:rustc-link-search=native={out_dir}"); } diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index fdb80bf8dff..970188abfb9 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -19,6 +19,7 @@ tauri-utils = { version = "1.1.1", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } rand = "0.8" raw-window-handle = "0.5" +url = "2" [target."cfg(windows)".dependencies] webview2-com = "0.19.1" diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 2ebc42de5ca..1598a9446f5 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -36,6 +36,7 @@ use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}; use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWindows}; use tauri_utils::{config::WindowConfig, debug_eprintln, Theme}; +use url::Url; use uuid::Uuid; use wry::{ application::{ @@ -210,6 +211,7 @@ impl Context { impl Context { fn create_webview(&self, pending: PendingWindow>) -> Result>> { let label = pending.label.clone(); + let current_url = pending.current_url.clone(); let menu_ids = pending.menu_ids.clone(); let js_event_listeners = pending.js_event_listeners.clone(); let context = self.clone(); @@ -231,6 +233,7 @@ impl Context { }; Ok(DetachedWindow { label, + current_url, dispatcher, menu_ids, js_event_listeners, @@ -1864,6 +1867,7 @@ impl Runtime for Wry { fn create_window(&self, pending: PendingWindow) -> Result> { let label = pending.label.clone(); + let current_url = pending.current_url.clone(); let menu_ids = pending.menu_ids.clone(); let js_event_listeners = pending.js_event_listeners.clone(); let window_id = rand::random(); @@ -1890,6 +1894,7 @@ impl Runtime for Wry { Ok(DetachedWindow { label, + current_url, dispatcher, menu_ids, js_event_listeners, @@ -2848,7 +2853,7 @@ fn create_webview( mut window_builder, ipc_handler, label, - url, + current_url, menu_ids, js_event_listeners, .. @@ -2888,17 +2893,23 @@ fn create_webview( } let mut webview_builder = WebViewBuilder::new(window) .map_err(|e| Error::CreateWebview(Box::new(e)))? - .with_url(&url) + .with_url(current_url.lock().unwrap().as_str()) .unwrap() // safe to unwrap because we validate the URL beforehand .with_transparent(is_window_transparent); if webview_attributes.file_drop_handler_enabled { webview_builder = webview_builder .with_file_drop_handler(create_file_drop_handler(window_event_listeners.clone())); } + if let Some(navigation_handler) = pending.navigation_handler { + webview_builder = webview_builder.with_navigation_handler(move |url| { + Url::parse(&url).map(&navigation_handler).unwrap_or(true) + }); + } if let Some(handler) = ipc_handler { webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( context, label.clone(), + current_url, menu_ids, js_event_listeners, handler, @@ -3002,6 +3013,7 @@ fn create_webview( fn create_ipc_handler( context: Context, label: String, + current_url: Arc>, menu_ids: Arc>>, js_event_listeners: Arc>>>, handler: WebviewIpcHandler>, @@ -3010,6 +3022,7 @@ fn create_ipc_handler( let window_id = context.webview_id_map.get(&window.id()).unwrap(); handler( DetachedWindow { + current_url: current_url.clone(), dispatcher: WryDispatcher { window_id, context: context.clone(), diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 7663a115798..cf6895c7f3e 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -33,6 +33,7 @@ http-range = "0.1.4" infer = "0.7" raw-window-handle = "0.5" rand = "0.8" +url = "2" [target."cfg(windows)".dependencies] webview2-com = "0.19.1" diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index c07408b5479..857ae4c367a 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -12,6 +12,7 @@ use crate::{ }; use serde::{Deserialize, Deserializer, Serialize}; use tauri_utils::{config::WindowConfig, Theme}; +use url::Url; use std::{ collections::{HashMap, HashSet}, @@ -224,14 +225,17 @@ pub struct PendingWindow> { /// How to handle IPC calls on the webview window. pub ipc_handler: Option>, - /// The resolved URL to load on the webview. - pub url: String, - /// Maps runtime id to a string menu id. pub menu_ids: Arc>>, /// A HashMap mapping JS event names with associated listener ids. pub js_event_listeners: Arc>>>, + + /// A handler to decide if incoming url is allowed to navigate. + pub navigation_handler: Option bool + Send>>, + + /// The current webview URL. + pub current_url: Arc>, } pub fn is_label_valid(label: &str) -> bool { @@ -268,9 +272,10 @@ impl> PendingWindow { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - url: "tauri://localhost".to_string(), menu_ids: Arc::new(Mutex::new(menu_ids)), js_event_listeners: Default::default(), + navigation_handler: Default::default(), + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), }) } } @@ -297,9 +302,10 @@ impl> PendingWindow { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - url: "tauri://localhost".to_string(), menu_ids: Arc::new(Mutex::new(menu_ids)), js_event_listeners: Default::default(), + navigation_handler: Default::default(), + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), }) } } @@ -340,6 +346,9 @@ pub struct JsEventListenerKey { /// A webview window that is not yet managed by Tauri. #[derive(Debug)] pub struct DetachedWindow> { + /// The current webview URL. + pub current_url: Arc>, + /// Name of the window pub label: String, @@ -356,6 +365,7 @@ pub struct DetachedWindow> { impl> Clone for DetachedWindow { fn clone(&self) -> Self { Self { + current_url: self.current_url.clone(), label: self.label.clone(), dispatcher: self.dispatcher.clone(), menu_ids: self.menu_ids.clone(), diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 0cb8bc362d1..fbc8e9fa146 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -948,7 +948,7 @@ impl Builder { #[cfg(any(windows, target_os = "linux"))] runtime_any_thread: false, setup: Box::new(|_| Ok(())), - invoke_handler: Box::new(|_| ()), + invoke_handler: Box::new(|invoke| invoke.resolver.reject("not implemented")), invoke_responder: Arc::new(window_invoke_responder), invoke_initialization_script: "Object.defineProperty(window, '__TAURI_POST_MESSAGE__', { value: (message) => window.ipc.postMessage(JSON.stringify(message)) })".into(), diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 220f248bc14..52a7524c2bb 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -25,10 +25,9 @@ use tauri_utils::{ html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, }; -use crate::hooks::IpcJavascript; #[cfg(feature = "isolation")] use crate::hooks::IsolationJavascript; -use crate::pattern::{format_real_schema, PatternJavascript}; +use crate::pattern::PatternJavascript; use crate::{ app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener}, event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, @@ -54,6 +53,7 @@ use crate::{ app::{GlobalMenuEventListener, WindowMenuEvent}, window::WebResourceRequestHandler, }; +use crate::{hooks::IpcJavascript, pattern::format_real_schema}; #[cfg(any(target_os = "linux", target_os = "windows"))] use crate::api::path::{resolve_path, BaseDirectory}; @@ -139,7 +139,7 @@ fn set_csp( let default_src = csp .entry("default-src".into()) .or_insert_with(Default::default); - default_src.push(format_real_schema(schema)); + default_src.push(crate::pattern::format_real_schema(schema)); } Csp::DirectiveMap(csp).to_string() @@ -231,7 +231,7 @@ pub struct InnerWindowManager { /// The script that initializes the invoke system. invoke_initialization_script: String, /// Application pattern. - pattern: Pattern, + pub(crate) pattern: Pattern, } impl fmt::Debug for InnerWindowManager { @@ -367,9 +367,12 @@ impl WindowManager { /// Get the base URL to use for webview requests. /// /// In dev mode, this will be based on the `devPath` configuration value. - fn get_url(&self) -> Cow<'_, Url> { + pub(crate) fn get_url(&self) -> Cow<'_, Url> { match self.base_path() { AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url), + #[cfg(windows)] + _ => Cow::Owned(Url::parse("https://tauri.localhost").unwrap()), + #[cfg(not(windows))] _ => Cow::Owned(Url::parse("tauri://localhost").unwrap()), } } @@ -477,7 +480,7 @@ impl WindowManager { }); } - let window_url = Url::parse(&pending.url).unwrap(); + let window_url = pending.current_url.lock().unwrap().clone(); let window_origin = if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" { format!("https://{}.localhost", window_url.scheme()) @@ -1011,7 +1014,16 @@ mod test { ); #[cfg(custom_protocol)] - assert_eq!(manager.get_url().to_string(), "tauri://localhost"); + { + assert_eq!( + manager.get_url().to_string(), + if cfg!(windows) { + "https://tauri.localhost/" + } else { + "tauri://localhost" + } + ); + } #[cfg(dev)] assert_eq!(manager.get_url().to_string(), "http://localhost:4000/"); @@ -1062,27 +1074,21 @@ impl WindowManager { return Err(crate::Error::WindowLabelAlreadyExists(pending.label)); } #[allow(unused_mut)] // mut url only for the data-url parsing - let (is_local, mut url) = match &pending.webview_attributes.url { + let mut url = match &pending.webview_attributes.url { WindowUrl::App(path) => { let url = self.get_url(); - ( - true, - // ignore "index.html" just to simplify the url - if path.to_str() != Some("index.html") { - url - .join(&*path.to_string_lossy()) - .map_err(crate::Error::InvalidUrl) - // this will never fail - .unwrap() - } else { - url.into_owned() - }, - ) - } - WindowUrl::External(url) => { - let config_url = self.get_url(); - (config_url.make_relative(url).is_some(), url.clone()) + // ignore "index.html" just to simplify the url + if path.to_str() != Some("index.html") { + url + .join(&*path.to_string_lossy()) + .map_err(crate::Error::InvalidUrl) + // this will never fail + .unwrap() + } else { + url.into_owned() + } } + WindowUrl::External(url) => url.clone(), _ => unimplemented!(), }; @@ -1109,7 +1115,7 @@ impl WindowManager { } } - pending.url = url.to_string(); + *pending.current_url.lock().unwrap() = url; if !pending.window_builder.has_icon() { if let Some(default_window_icon) = self.inner.default_window_icon.clone() { @@ -1125,17 +1131,15 @@ impl WindowManager { } } - if is_local { - let label = pending.label.clone(); - pending = self.prepare_pending_window( - pending, - &label, - window_labels, - app_handle.clone(), - web_resource_request_handler, - )?; - pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle)); - } + let label = pending.label.clone(); + pending = self.prepare_pending_window( + pending, + &label, + window_labels, + app_handle.clone(), + web_resource_request_handler, + )?; + pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle)); // in `Windows`, we need to force a data_directory // but we do respect user-specification @@ -1160,6 +1164,17 @@ impl WindowManager { } } + let current_url_ = pending.current_url.clone(); + let navigation_handler = pending.navigation_handler.take(); + pending.navigation_handler = Some(Box::new(move |url| { + *current_url_.lock().unwrap() = url.clone(); + if let Some(handler) = &navigation_handler { + handler(url) + } else { + true + } + })); + Ok(pending) } diff --git a/core/tauri/src/pattern.rs b/core/tauri/src/pattern.rs index 660959dbbd6..ad802ba4543 100644 --- a/core/tauri/src/pattern.rs +++ b/core/tauri/src/pattern.rs @@ -11,6 +11,9 @@ use serialize_to_javascript::{default_template, Template}; use tauri_utils::assets::{Assets, EmbeddedAssets}; +/// The domain of the isolation iframe source. +pub const ISOLATION_IFRAME_SRC_DOMAIN: &str = "localhost"; + /// An application pattern. #[derive(Debug, Clone)] pub enum Pattern { @@ -87,8 +90,8 @@ pub(crate) struct PatternJavascript { #[allow(dead_code)] pub(crate) fn format_real_schema(schema: &str) -> String { if cfg!(windows) { - format!("https://{}.localhost", schema) + format!("https://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}") } else { - format!("{}://localhost", schema) + format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}") } } diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 360bf9fc210..02584599948 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -67,6 +67,7 @@ impl RuntimeHandle for MockRuntimeHandle { ) -> Result> { Ok(DetachedWindow { label: pending.label, + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), dispatcher: MockDispatcher { context: self.context.clone(), }, @@ -627,6 +628,7 @@ impl Runtime for MockRuntime { fn create_window(&self, pending: PendingWindow) -> Result> { Ok(DetachedWindow { label: pending.label, + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), dispatcher: MockDispatcher { context: self.context.clone(), }, diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index fb987564a1d..63832858431 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -33,6 +33,7 @@ use crate::{ }; use serde::Serialize; +use url::Url; #[cfg(windows)] use windows::Win32::Foundation::HWND; @@ -510,7 +511,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { #[derive(Debug)] pub struct Window { /// The webview window created by the runtime. - window: DetachedWindow, + pub(crate) window: DetachedWindow, /// The manager to associate this webview window with. manager: WindowManager, pub(crate) app_handle: AppHandle, @@ -1184,9 +1185,27 @@ impl Window { /// Webview APIs. impl Window { + /// Returns the current url of the webview. + pub fn url(&self) -> Url { + self.window.current_url.lock().unwrap().clone() + } + /// Handles this window receiving an [`InvokeMessage`]. pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> { let manager = self.manager.clone(); + let current_url = self.url(); + let config_url = manager.get_url(); + #[allow(unused_mut)] + let mut is_local = config_url.make_relative(¤t_url).is_some(); + #[cfg(feature = "isolation")] + if let crate::Pattern::Isolation { schema, .. } = &self.manager.inner.pattern { + if current_url.scheme() == schema + && current_url.domain() == Some(crate::pattern::ISOLATION_IFRAME_SRC_DOMAIN) + { + is_local = true; + } + } + match payload.cmd.as_str() { "__initialized" => { let payload: PageLoadPayload = serde_json::from_value(payload.inner)?; @@ -1200,8 +1219,17 @@ impl Window { payload.inner, ); let resolver = InvokeResolver::new(self, payload.callback, payload.error); - let invoke = Invoke { message, resolver }; + + println!("{} {}", config_url, current_url); + + if !is_local { + invoke + .resolver + .reject("Remote URLs are not allowed to access the IPC"); + return Ok(()); + } + if let Some(module) = &payload.tauri_module { crate::endpoints::handle( module.to_string(), diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index e39ccc7fba9..ed5af1d3023 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3105,7 +3105,7 @@ dependencies = [ [[package]] name = "tauri" -version = "1.0.5" +version = "1.1.3" dependencies = [ "anyhow", "attohttpc", @@ -3167,7 +3167,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.0.4" +version = "1.1.1" dependencies = [ "anyhow", "cargo_toml", @@ -3183,7 +3183,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "1.0.4" +version = "1.1.1" dependencies = [ "base64", "brotli", @@ -3207,7 +3207,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "1.0.4" +version = "1.1.1" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -3219,7 +3219,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.10.2" +version = "0.11.1" dependencies = [ "gtk", "http", @@ -3231,6 +3231,7 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", + "url", "uuid 1.1.2", "webview2-com", "windows 0.39.0", @@ -3238,7 +3239,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.10.2" +version = "0.11.1" dependencies = [ "cocoa", "gtk", @@ -3247,6 +3248,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", + "url", "uuid 1.1.2", "webkit2gtk", "webview2-com", @@ -3256,7 +3258,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.0.3" +version = "1.1.1" dependencies = [ "aes-gcm", "brotli",