Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/IPC/WindServiceHandlers/NativeHost/
OpenExternal.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! Wire method: `native:openExternal`, `nativeHost:openExternal`.
4//! Opens an http/https URL in the platform default browser.
5
6use std::sync::Arc;
7
8use serde_json::Value;
9
10use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
11
12pub async fn OpenExternal(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
13	// Accept both a plain URI string and the object shape
14	// `{ uri: "..." }` that some VS Code callers emit.
15	let url_str = match Arguments.first() {
16		Some(Value::String(S)) => S.as_str(),
17		Some(Value::Object(Obj)) => Obj.get("uri").or_else(|| Obj.get("url")).and_then(|V| V.as_str()).unwrap_or(""),
18		_ => return Ok(Value::Bool(false)),
19	};
20
21	if url_str.is_empty() {
22		return Ok(Value::Bool(false));
23	}
24
25	dev_log!("lifecycle", "openExternal: {}", url_str);
26
27	// Allowlist of safe protocols. Block `file://` (arbitrary filesystem
28	// access) and bare shell commands. Everything else that parses as a
29	// valid URI scheme is forwarded to the OS default handler.
30	let Scheme = url_str.splitn(2, ':').next().unwrap_or("").to_lowercase();
31	let AllowedSchemes = [
32		"http",
33		"https",
34		"mailto",
35		"ftp",
36		"vscode",
37		"fiddee",
38		"ssh",
39		"git",
40		"x-github-client",
41		"github-windows",
42		"slack",
43		"teams",
44		"zoommtg",
45		"tel",
46		"callto",
47	];
48	if Scheme == "file" || Scheme.is_empty() || !url_str.contains(':') {
49		dev_log!(
50			"lifecycle",
51			"warn: [OpenExternal] blocked scheme '{}' for uri '{}'",
52			Scheme,
53			url_str
54		);
55		return Ok(Value::Bool(false));
56	}
57	let IsKnownScheme = AllowedSchemes.contains(&Scheme.as_str());
58	if !IsKnownScheme {
59		dev_log!(
60			"lifecycle",
61			"[OpenExternal] unknown scheme '{}' - forwarding to OS anyway",
62			Scheme
63		);
64	}
65
66	#[cfg(target_os = "macos")]
67	{
68		use std::process::Command;
69
70		let result = Command::new("open")
71			.arg(url_str)
72			.output()
73			.map_err(|Error| format!("Failed to execute open command: {}", Error))?;
74
75		if !result.status.success() {
76			return Err(format!("Failed to open URL: {}", String::from_utf8_lossy(&result.stderr)));
77		}
78	}
79
80	#[cfg(target_os = "windows")]
81	{
82		use std::process::Command;
83
84		let result = Command::new("cmd")
85			.arg("/c")
86			.arg("start")
87			.arg(url_str)
88			.output()
89			.map_err(|Error| format!("Failed to execute start command: {}", Error))?;
90
91		if !result.status.success() {
92			return Err(format!("Failed to open URL: {}", String::from_utf8_lossy(&result.stderr)));
93		}
94	}
95
96	#[cfg(target_os = "linux")]
97	{
98		use std::process::Command;
99
100		let handlers = ["xdg-open", "gnome-open", "kde-open", "x-www-browser"];
101
102		let mut last_error = String::new();
103
104		for handler in handlers.iter() {
105			let result = Command::new(handler).arg(url_str).output();
106
107			match result {
108				Ok(output) if output.status.success() => {
109					dev_log!("lifecycle", "opened with {}", handler);
110
111					break;
112				},
113
114				Err(e) => {
115					last_error = e.to_string();
116
117					continue;
118				},
119
120				_ => continue,
121			}
122		}
123
124		if !last_error.is_empty() {
125			return Err(format!("Failed to open URL with any handler: {}", last_error));
126		}
127	}
128
129	dev_log!("lifecycle", "opened URL: {}", url_str);
130
131	Ok(Value::Bool(true))
132}