CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m2s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m38s
CI/CD / test_and_check_app (pull_request) Successful in 6m47s
CI/CD / Publish immutable image (pull_request) Skipped
148 lines
4.5 KiB
Rust
148 lines
4.5 KiB
Rust
use serde::Deserialize;
|
|
use serde_json::{json, Value};
|
|
use std::ffi::{c_char, CStr, CString};
|
|
use std::sync::OnceLock;
|
|
use tauri::{AppHandle, Url};
|
|
|
|
static APP: OnceLock<AppHandle> = OnceLock::new();
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Request {
|
|
pub version: u8,
|
|
pub id: u64,
|
|
pub action: String,
|
|
pub payload: Value,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(
|
|
tag = "action",
|
|
content = "payload",
|
|
rename_all = "camelCase",
|
|
deny_unknown_fields
|
|
)]
|
|
pub enum Action {
|
|
GetState {},
|
|
SettingsReady {},
|
|
OpenSettings {},
|
|
SetWorkspace {
|
|
server: String,
|
|
},
|
|
CheckForUpdates {},
|
|
SetUpdatePreferences {
|
|
automatic: bool,
|
|
#[serde(rename = "intervalHours")]
|
|
interval_hours: u64,
|
|
},
|
|
InstallUpdate {
|
|
version: String,
|
|
},
|
|
}
|
|
|
|
pub fn parse(value: &str) -> Result<(u64, Action), &'static str> {
|
|
if value.len() > 8192 {
|
|
return Err("invalidRequest");
|
|
}
|
|
let request: Request = serde_json::from_str(value).map_err(|_| "invalidRequest")?;
|
|
if request.version != 1 {
|
|
return Err("incompatibleVersion");
|
|
}
|
|
let action =
|
|
serde_json::from_value(json!({"action": request.action, "payload": request.payload}))
|
|
.map_err(|_| "invalidRequest")?;
|
|
Ok((request.id, action))
|
|
}
|
|
|
|
pub fn install(app: &AppHandle, window: &tauri::WebviewWindow) -> tauri::Result<()> {
|
|
let _ = APP.set(app.clone());
|
|
let label = CString::new(window.label()).unwrap();
|
|
let script = CString::new(include_str!("../../ui/workspace-settings.js")).unwrap();
|
|
window.with_webview(move |view| unsafe {
|
|
unsafe extern "C" {
|
|
fn install_workspace_settings(
|
|
view: *mut std::ffi::c_void,
|
|
label: *const c_char,
|
|
script: *const c_char,
|
|
callback: extern "C" fn(
|
|
*const c_char,
|
|
*const c_char,
|
|
*const c_char,
|
|
bool,
|
|
) -> *mut c_char,
|
|
free: extern "C" fn(*mut c_char),
|
|
);
|
|
}
|
|
install_workspace_settings(
|
|
view.inner().cast(),
|
|
label.as_ptr(),
|
|
script.as_ptr(),
|
|
receive,
|
|
release,
|
|
);
|
|
})
|
|
}
|
|
|
|
extern "C" fn release(value: *mut c_char) {
|
|
if !value.is_null() {
|
|
unsafe {
|
|
drop(CString::from_raw(value));
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" fn receive(
|
|
label: *const c_char,
|
|
url: *const c_char,
|
|
body: *const c_char,
|
|
main: bool,
|
|
) -> *mut c_char {
|
|
let result = std::panic::catch_unwind(|| -> Result<Value, &'static str> {
|
|
if label.is_null() || url.is_null() || body.is_null() {
|
|
return Err("invalidRequest");
|
|
}
|
|
let label = unsafe { CStr::from_ptr(label) }
|
|
.to_str()
|
|
.map_err(|_| "invalidRequest")?;
|
|
let url = unsafe { CStr::from_ptr(url) }
|
|
.to_str()
|
|
.map_err(|_| "invalidRequest")?;
|
|
let body = unsafe { CStr::from_ptr(body) }
|
|
.to_str()
|
|
.map_err(|_| "invalidRequest")?;
|
|
let app = APP.get().ok_or("bridgeUnavailable")?;
|
|
let url = Url::parse(url).map_err(|_| "untrustedFrame")?;
|
|
if !crate::desktop::allows_settings(app, label, &url, main) {
|
|
return Err("untrustedFrame");
|
|
}
|
|
let (id, action) = parse(body)?;
|
|
let result = crate::desktop::settings_action(app, action)?;
|
|
Ok(json!({"id": id, "result": result}))
|
|
})
|
|
.unwrap_or(Err("bridgeUnavailable"));
|
|
let value = result.unwrap_or_else(|error| json!({"error": error}));
|
|
CString::new(value.to_string()).unwrap().into_raw()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
#[test]
|
|
fn protocol_rejects_unknown_versions_actions_and_malformed_payloads() {
|
|
for value in [
|
|
r#"{"version":2,"id":1,"action":"getState","payload":{}}"#,
|
|
r#"{"version":1,"id":1,"action":"shell","payload":{}}"#,
|
|
r#"{"version":1,"id":1,"action":"setWorkspace","payload":{"server":3}}"#,
|
|
r#"{"version":1,"id":1,"action":"setUpdatePreferences","payload":{"automatic":"true","intervalHours":6}}"#,
|
|
r#"{"version":1,"id":1,"action":"installUpdate","payload":{"version":"1.2.3","url":"https://evil.test"}}"#,
|
|
] {
|
|
assert!(parse(value).is_err(), "{value}");
|
|
}
|
|
assert!(parse(&"x".repeat(8193)).is_err());
|
|
assert!(matches!(
|
|
parse(r#"{"version":1,"id":1,"action":"getState","payload":{}}"#),
|
|
Ok((1, Action::GetState {}))
|
|
));
|
|
}
|
|
}
|