use std::sync::{Arc, LazyLock};
use anyhow::Result;
use regex::Regex;
use super::browser_sources::{browser_sources, Environment};
use crate::utilities::subprocess::{run_command_blocking, RunCommandOptions};
pub type RunCommand = Arc<dyn Fn(&str, &[&str], &Environment) -> Result<String> + Send + Sync>;
pub fn default_run_command() -> RunCommand {
Arc::new(|command, args, environment| {
let output = run_command_blocking(
command,
args,
RunCommandOptions {
env: Some(environment.clone()),
check: false,
..RunCommandOptions::default()
},
)?;
Ok(output.stdout)
})
}
static MAC_HTTPS: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"LSHandlerURLScheme\s*=\s*"?https"?\s*;"#).expect("valid regex"));
static MAC_ROLE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r#"LSHandlerRoleAll\s*=\s*"?([^";]+)"?\s*;"#).expect("valid regex")
});
static WIN_PROGID: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?i)\bProgId\b").expect("valid regex"));
pub fn browser_for_identifier(identifier: &str, platform: &str) -> Option<&'static str> {
let needle = identifier.trim().to_lowercase();
if needle.is_empty() {
return None;
}
for source in browser_sources() {
if let Some(identifiers) = source.default.get(platform) {
if identifiers
.iter()
.any(|candidate| candidate.to_lowercase() == needle)
{
return Some(source.id.as_str());
}
}
}
None
}
pub fn parse_mac_launch_services_handler(output: &str) -> Option<String> {
for block in output.split('}') {
if !MAC_HTTPS.is_match(block) {
continue;
}
if let Some(captures) = MAC_ROLE.captures(block) {
return Some(captures[1].trim().to_string());
}
}
None
}
pub fn parse_windows_prog_id(output: &str) -> Option<String> {
for raw_line in output.split('\n') {
let line = raw_line.trim_end_matches('\r');
if WIN_PROGID.is_match(line) {
return line.split_whitespace().last().map(str::to_string);
}
}
None
}
fn resolve_darwin_default(
run_command: &RunCommand,
environment: &Environment,
) -> Result<Option<&'static str>> {
let output = run_command(
"defaults",
&[
"read",
"com.apple.LaunchServices/com.apple.launchservices.secure",
"LSHandlers",
],
environment,
)?;
Ok(parse_mac_launch_services_handler(&output)
.and_then(|identifier| browser_for_identifier(&identifier, "darwin")))
}
fn resolve_linux_default(
run_command: &RunCommand,
environment: &Environment,
) -> Result<Option<&'static str>> {
let desktop = run_command("xdg-settings", &["get", "default-web-browser"], environment)
.map(|stdout| stdout.trim().to_string())
.unwrap_or_default();
if let Some(resolved) = browser_for_identifier(&desktop, "linux") {
return Ok(Some(resolved));
}
let fallback = match run_command(
"xdg-mime",
&["query", "default", "x-scheme-handler/https"],
environment,
) {
Ok(stdout) => stdout.trim().to_string(),
Err(_) => return Ok(None),
};
Ok(browser_for_identifier(&fallback, "linux"))
}
fn resolve_windows_default(
run_command: &RunCommand,
environment: &Environment,
) -> Result<Option<&'static str>> {
let output = match run_command(
"reg",
&[
"query",
"HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice",
"/v",
"ProgId",
],
environment,
) {
Ok(output) => output,
Err(_) => return Ok(None),
};
Ok(parse_windows_prog_id(&output)
.and_then(|identifier| browser_for_identifier(&identifier, "win32")))
}
pub fn resolve_default_browser(
platform: &str,
environment: &Environment,
run_command: &RunCommand,
) -> Result<Option<&'static str>> {
match platform {
"darwin" => resolve_darwin_default(run_command, environment),
"linux" => resolve_linux_default(run_command, environment),
"win32" => resolve_windows_default(run_command, environment),
_ => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn runner_returning(pairs: &[(&'static str, &'static str)]) -> RunCommand {
let map: HashMap<String, String> = pairs
.iter()
.map(|(key, value)| ((*key).to_string(), (*value).to_string()))
.collect();
Arc::new(move |command: &str, args: &[&str], _env: &Environment| {
let key = std::iter::once(command)
.chain(args.iter().copied())
.collect::<Vec<_>>()
.join(" ");
map.get(&key)
.cloned()
.ok_or_else(|| anyhow::anyhow!("unexpected command: {key}"))
})
}
fn resolve(platform: &str, run_command: &RunCommand) -> Option<&'static str> {
resolve_default_browser(platform, &Environment::new(), run_command).unwrap()
}
#[test]
fn maps_a_macos_launch_services_https_handler_to_a_browser_id() {
let output = "(\n {\n LSHandlerRoleAll = \"com.apple.safari\";\n \
LSHandlerURLScheme = mailto;\n },\n {\n \
LSHandlerRoleAll = \"com.google.chrome\";\n \
LSHandlerURLScheme = https;\n }\n )";
let run_command = runner_returning(&[(
"defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers",
output,
)]);
assert_eq!(resolve("darwin", &run_command), Some("chrome"));
}
#[test]
fn maps_a_linux_xdg_settings_desktop_name_to_a_browser_id() {
let run_command =
runner_returning(&[("xdg-settings get default-web-browser", "firefox.desktop\n")]);
assert_eq!(resolve("linux", &run_command), Some("firefox"));
}
#[test]
fn falls_back_to_xdg_mime_when_xdg_settings_is_unknown() {
let run_command = runner_returning(&[
(
"xdg-settings get default-web-browser",
"some-unknown.desktop\n",
),
(
"xdg-mime query default x-scheme-handler/https",
"brave-browser.desktop\n",
),
]);
assert_eq!(resolve("linux", &run_command), Some("brave"));
}
#[test]
fn maps_a_windows_userchoice_progid_to_a_browser_id() {
let output = [
"",
"HKEY_CURRENT_USER\\...\\https\\UserChoice",
" ProgId REG_SZ MSEdgeHTM",
"",
]
.join("\r\n");
let run_command = runner_returning(&[(
"reg query HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice /v ProgId",
Box::leak(output.into_boxed_str()),
)]);
assert_eq!(resolve("win32", &run_command), Some("edge"));
}
#[test]
fn returns_none_when_nothing_matches_or_the_tool_fails() {
let failing: RunCommand = Arc::new(|_command: &str, _args: &[&str], _env: &Environment| {
Err(anyhow::anyhow!("no xdg"))
});
assert_eq!(resolve("linux", &failing), None);
let empty: RunCommand =
Arc::new(|_command: &str, _args: &[&str], _env: &Environment| Ok(String::new()));
assert_eq!(resolve("sunos", &empty), None);
}
#[test]
fn parses_handler_blocks_and_progid_lines_directly() {
assert_eq!(
parse_mac_launch_services_handler(
"{ LSHandlerURLScheme = https; LSHandlerRoleAll = \"com.brave.browser\"; }"
),
Some("com.brave.browser".to_string())
);
assert_eq!(
parse_windows_prog_id(" ProgId REG_SZ FirefoxHTML"),
Some("FirefoxHTML".to_string())
);
assert_eq!(
browser_for_identifier("COM.GOOGLE.CHROME", "darwin"),
Some("chrome")
);
assert_eq!(browser_for_identifier("", "darwin"), None);
}
}