use crate::common::{for_each_token, run_command};
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType};
use log::trace;
use std::path::Path;
use std::process::Command;
const ASSOCF_IS_PROTOCOL: u32 = 0x00001000;
const ASSOCSTR_COMMAND: i32 = 1;
#[link(name = "shlwapi")]
extern "system" {
fn AssocQueryStringW(
flags: u32,
string: i32,
association: *const u16,
extra: *const u16,
out: *mut u16,
out_len: *mut u32,
) -> i32;
}
pub(super) fn open_browser_internal(
browser: Browser,
target: &TargetType,
options: &BrowserOptions,
) -> Result<()> {
match browser {
Browser::Default => {
if options.dry_run {
return Ok(());
}
trace!("trying to figure out default browser command");
let cmdline = unsafe {
const BUF_SIZE: usize = 512;
let mut cmdline_u16 = [0_u16; BUF_SIZE];
let mut line_len = BUF_SIZE as u32;
if AssocQueryStringW(
ASSOCF_IS_PROTOCOL,
ASSOCSTR_COMMAND,
[0x68, 0x74, 0x74, 0x70, 0x0].as_ptr(), std::ptr::null(),
cmdline_u16.as_mut_ptr(),
&mut line_len,
) != 0
{
return Err(Error::other("failed to get default browser"));
}
let mut line_len = line_len as usize;
if line_len == BUF_SIZE {
if let Some(found_nul) = cmdline_u16.iter().position(|&c| c == 0) {
trace!(
"Broken AssocQueryStringW(), manually string length determined at {found_nul}"
);
line_len = found_nul + 1;
}
}
use std::os::windows::ffi::OsStringExt;
std::ffi::OsString::from_wide(&cmdline_u16[..(line_len - 1)])
.into_string()
.map_err(|_err| {
Error::other(
"The default web browser command contains invalid unicode characters",
)
})?
};
trace!("default browser command: {}", &cmdline);
let cmdline = ensure_cmd_quotes(&cmdline);
let mut cmd = get_browser_cmd(&cmdline, target)?;
run_command(&mut cmd, true, options)
}
_ => Err(Error::new(
ErrorKind::NotFound,
"Only the default browser is supported on this platform right now",
)),
}
}
fn ensure_cmd_quotes(cmdline: &str) -> String {
if !cmdline.starts_with('"') {
let mut end = 0;
for (idx, ch) in cmdline.char_indices() {
if ch == ' ' {
let potential_exe = Path::new(&cmdline[..idx]);
if potential_exe.exists() {
end = idx;
break;
}
}
}
if end > 0 {
return format!("\"{}\"{}", &cmdline[..end], &cmdline[end..]);
}
}
cmdline.to_string()
}
fn get_browser_cmd(cmdline: &str, url: &TargetType) -> Result<Command> {
let mut tokens: Vec<String> = Vec::new();
for_each_token(cmdline, |token: &str| {
if matches!(token, "%0" | "%1") {
tokens.push(url.to_string());
} else {
tokens.push(token.to_string());
}
});
if tokens.is_empty() {
Err(Error::new(ErrorKind::NotFound, "invalid command"))
} else {
let mut cmd = Command::new(&tokens[0]);
if tokens.len() > 1 {
cmd.args(&tokens[1..]);
}
Ok(cmd)
}
}