From a77be9747443ffc29c34160b55893483bb5f0d74 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Thu, 29 Feb 2024 18:24:43 -0300 Subject: [PATCH] fix(ipc): fallback to postMessage if protocol fails, closes #8476 (#9038) --- .changes/ipc-post-message-fallback.md | 5 ++ core/tauri/build.rs | 5 -- core/tauri/scripts/ipc-protocol.js | 123 +++++++++++++------------- core/tauri/src/app.rs | 4 +- core/tauri/src/ipc/mod.rs | 1 - core/tauri/src/ipc/protocol.rs | 28 +++--- core/tauri/src/manager/webview.rs | 9 +- 7 files changed, 88 insertions(+), 87 deletions(-) create mode 100644 .changes/ipc-post-message-fallback.md diff --git a/.changes/ipc-post-message-fallback.md b/.changes/ipc-post-message-fallback.md new file mode 100644 index 000000000..b271d945f --- /dev/null +++ b/.changes/ipc-post-message-fallback.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:enhance +--- + +Fallback to the postMessage IPC interface if we cannot reach the IPC custom protocol. diff --git a/core/tauri/build.rs b/core/tauri/build.rs index 9ba730734..c9421aa58 100644 --- a/core/tauri/build.rs +++ b/core/tauri/build.rs @@ -217,11 +217,6 @@ fn main() { alias("desktop", !mobile); alias("mobile", mobile); - alias( - "ipc_custom_protocol", - target_os != "android" && (target_os != "linux" || has_feature("linux-ipc-protocol")), - ); - let out_dir = PathBuf::from(var("OUT_DIR").unwrap()); let checked_features_out_path = out_dir.join("checked_features"); diff --git a/core/tauri/scripts/ipc-protocol.js b/core/tauri/scripts/ipc-protocol.js index 1f8e0018e..c3b6e1919 100644 --- a/core/tauri/scripts/ipc-protocol.js +++ b/core/tauri/scripts/ipc-protocol.js @@ -6,72 +6,73 @@ const processIpcMessage = __RAW_process_ipc_message_fn__ const osName = __TEMPLATE_os_name__ const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__ - const useCustomProtocol = __TEMPLATE_use_custom_protocol__ + const linuxIpcProtocolEnabled = __TEMPLATE_linux_ipc_protocol_enabled__ + let customProtocolIpcFailed = false - Object.defineProperty(window.__TAURI_INTERNALS__, 'postMessage', { - value: (message) => { - const { cmd, callback, error, payload, options } = message + // on Linux we only use the custom-protocol-based IPC if the the linux-ipc-protocol Cargo feature is enabled + // on Android we never use it because Android does not have support to reading the request body + const canUseCustomProtocol = + osName === 'linux' ? linuxIpcProtocolEnabled : osName !== 'android' - // use custom protocol for IPC if: - // - the flag is set to true or - // - the command is the fetch data command or - // - when not on Linux/Android - // AND - // - when not on macOS with an https URL - if ( - (useCustomProtocol || - cmd === fetchChannelDataCommand || - !(osName === 'linux' || osName === 'android')) && - !( - (osName === 'macos' || osName === 'ios') && - location.protocol === 'https:' - ) - ) { - const { contentType, data } = processIpcMessage(payload) - fetch(window.__TAURI_INTERNALS__.convertFileSrc(cmd, 'ipc'), { - method: 'POST', - body: data, - headers: { - 'Content-Type': contentType, - 'Tauri-Callback': callback, - 'Tauri-Error': error, - ...options?.headers + function sendIpcMessage(message) { + const { cmd, callback, error, payload, options } = message + + if ( + !customProtocolIpcFailed && + (canUseCustomProtocol || cmd === fetchChannelDataCommand) + ) { + const { contentType, data } = processIpcMessage(payload) + fetch(window.__TAURI_INTERNALS__.convertFileSrc(cmd, 'ipc'), { + method: 'POST', + body: data, + headers: { + 'Content-Type': contentType, + 'Tauri-Callback': callback, + 'Tauri-Error': error, + ...options?.headers + } + }) + .then((response) => { + const cb = response.ok ? callback : error + // we need to split here because on Android the content-type gets duplicated + switch ((response.headers.get('content-type') || '').split(',')[0]) { + case 'application/json': + return response.json().then((r) => [cb, r]) + case 'text/plain': + return response.text().then((r) => [cb, r]) + default: + return response.arrayBuffer().then((r) => [cb, r]) } }) - .then((response) => { - const cb = response.ok ? callback : error - // we need to split here because on Android the content-type gets duplicated - switch ( - (response.headers.get('content-type') || '').split(',')[0] - ) { - case 'application/json': - return response.json().then((r) => [cb, r]) - case 'text/plain': - return response.text().then((r) => [cb, r]) - default: - return response.arrayBuffer().then((r) => [cb, r]) - } - }) - .then(([cb, data]) => { - if (window[`_${cb}`]) { - window[`_${cb}`](data) - } else { - console.warn( - `[TAURI] Couldn't find callback id {cb} in window. This might happen when the app is reloaded while Rust is running an asynchronous operation.` - ) - } - }) - } else { - // otherwise use the postMessage interface - const { data } = processIpcMessage({ - cmd, - callback, - error, - options, - payload + .then(([cb, data]) => { + if (window[`_${cb}`]) { + window[`_${cb}`](data) + } else { + console.warn( + `[TAURI] Couldn't find callback id {cb} in window. This might happen when the app is reloaded while Rust is running an asynchronous operation.` + ) + } }) - window.ipc.postMessage(data) - } + .catch(() => { + // failed to use the custom protocol IPC (either the webview blocked a custom protocol or it was a CSP error) + // so we need to fallback to the postMessage interface + customProtocolIpcFailed = true + sendIpcMessage(message) + }) + } else { + // otherwise use the postMessage interface + const { data } = processIpcMessage({ + cmd, + callback, + error, + options, + payload + }) + window.ipc.postMessage(data) } + } + + Object.defineProperty(window.__TAURI_INTERNALS__, 'postMessage', { + value: sendIpcMessage }) })() diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index d6b6a71a0..47614f297 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1084,7 +1084,7 @@ struct InvokeInitializationScript<'a> { process_ipc_message_fn: &'a str, os_name: &'a str, fetch_channel_data_command: &'a str, - use_custom_protocol: bool, + linux_ipc_protocol_enabled: bool, } /// Make `Wry` the default `Runtime` for `Builder` @@ -1117,7 +1117,7 @@ impl Builder { process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN, os_name: std::env::consts::OS, fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND, - use_custom_protocol: cfg!(ipc_custom_protocol), + linux_ipc_protocol_enabled: cfg!(feature = "linux-ipc-protocol"), } .render_default(&Default::default()) .unwrap() diff --git a/core/tauri/src/ipc/mod.rs b/core/tauri/src/ipc/mod.rs index efe9c0406..1cf56523d 100644 --- a/core/tauri/src/ipc/mod.rs +++ b/core/tauri/src/ipc/mod.rs @@ -21,7 +21,6 @@ use crate::{webview::Webview, Runtime, StateManager}; mod authority; pub(crate) mod channel; mod command; -#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))] pub(crate) mod format_callback; pub(crate) mod protocol; diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index e3e29495d..3231e5fa9 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -19,7 +19,6 @@ use super::{CallbackFn, InvokeBody, InvokeResponse}; const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback"; const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error"; -#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))] pub fn message_handler( manager: Arc>, ) -> crate::runtime::webview::WebviewIpcHandler { @@ -162,7 +161,6 @@ pub fn get(manager: Arc>, label: String) -> UriSchemeP }) } -#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))] fn handle_ipc_message(message: String, manager: &AppManager, label: &str) { if let Some(webview) = manager.get_webview(label) { #[cfg(feature = "tracing")] @@ -374,15 +372,21 @@ fn parse_invoke_request( .decode_utf8_lossy() .to_string(); - // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it - #[cfg(all(feature = "isolation", ipc_custom_protocol))] - if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern { - #[cfg(feature = "tracing")] - let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered(); + // on Android and on Linux (without the linux-ipc-protocol Cargo feature) we cannot read the request body + // so we must ignore it because some commands use the IPC for faster response + let has_payload = !body.is_empty(); - body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body) - .and_then(|raw| crypto_keys.decrypt(raw)) - .map_err(|e| e.to_string())?; + #[cfg(feature = "isolation")] + if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern { + // if the platform does not support request body, we ignore it + if has_payload { + #[cfg(feature = "tracing")] + let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered(); + + body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body) + .and_then(|raw| crypto_keys.decrypt(raw)) + .map_err(|e| e.to_string())?; + } } let callback = CallbackFn( @@ -420,12 +424,12 @@ fn parse_invoke_request( let body = if content_type == mime::APPLICATION_OCTET_STREAM { body.into() } else if content_type == mime::APPLICATION_JSON { - if cfg!(ipc_custom_protocol) { + // if the platform does not support request body, we ignore it + if has_payload { serde_json::from_slice::(&body) .map_err(|e| e.to_string())? .into() } else { - // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it serde_json::Value::Object(Default::default()).into() } } else { diff --git a/core/tauri/src/manager/webview.rs b/core/tauri/src/manager/webview.rs index 80bb783bd..89cc0dda6 100644 --- a/core/tauri/src/manager/webview.rs +++ b/core/tauri/src/manager/webview.rs @@ -496,12 +496,9 @@ impl WebviewManager { manager, )?; - #[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))] - { - pending.ipc_handler = Some(crate::ipc::protocol::message_handler( - manager.manager_owned(), - )); - } + pending.ipc_handler = Some(crate::ipc::protocol::message_handler( + manager.manager_owned(), + )); // in `windows`, we need to force a data_directory // but we do respect user-specification