Skip to main content

rightkit_tauri_shell/
opener.rs

1//! System handler for external links. Arguments are passed as argv (never through a shell) and
2//! only schemes allowed by [`crate::nav::external_scheme_allowed`] reach it.
3
4use std::process::{Command, Stdio};
5
6use crate::nav::external_scheme_allowed;
7
8pub trait ExternalOpener: Send + Sync + 'static {
9    /// Open `target` (already scheme-validated) with the operating system's handler.
10    fn open(&self, target: &str) -> Result<(), String>;
11}
12
13/// The platform handler: `open` (macOS), `rundll32 url.dll,FileProtocolHandler` (Windows),
14/// `xdg-open` (Linux).
15pub struct SystemOpener;
16
17impl ExternalOpener for SystemOpener {
18    fn open(&self, target: &str) -> Result<(), String> {
19        let mut cmd;
20        #[cfg(target_os = "macos")]
21        {
22            cmd = Command::new("/usr/bin/open");
23            cmd.arg(target);
24        }
25        #[cfg(windows)]
26        {
27            use std::os::windows::process::CommandExt;
28            cmd = Command::new("rundll32.exe");
29            cmd.arg("url.dll,FileProtocolHandler").arg(target);
30            // CREATE_NO_WINDOW: never flash a console for background automation.
31            cmd.creation_flags(0x0800_0000);
32        }
33        #[cfg(not(any(target_os = "macos", windows)))]
34        {
35            cmd = Command::new("xdg-open");
36            cmd.arg(target);
37        }
38        cmd.stdin(Stdio::null())
39            .stdout(Stdio::null())
40            .stderr(Stdio::null());
41        cmd.spawn()
42            .map(|mut child| {
43                // Reap without blocking the caller.
44                std::thread::spawn(move || {
45                    let _ = child.wait();
46                });
47            })
48            .map_err(|e| format!("open {target}: {e}"))
49    }
50}
51
52/// Validate and open. Refuses anything outside http(s)/mailto.
53pub fn open_checked(opener: &dyn ExternalOpener, target: &str) -> Result<(), String> {
54    if !external_scheme_allowed(target) {
55        return Err("blocked: only http(s)/mailto links open externally".into());
56    }
57    opener.open(target.trim())
58}