pub(crate) fn open_url_in_browser(url: &str) -> std::io::Result<()> {
validate_browser_url(url)?;
open_validated_url(url)
}
fn validate_browser_url(url: &str) -> std::io::Result<()> {
let parsed = reqwest::Url::parse(url).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid browser URL")
})?;
if !matches!(parsed.scheme(), "http" | "https") || url.contains('\0') {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"browser URL must use HTTP or HTTPS",
));
}
Ok(())
}
#[cfg(target_os = "windows")]
fn open_validated_url(url: &str) -> std::io::Result<()> {
use windows_sys::Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOWNORMAL};
let url: Vec<u16> = url.encode_utf16().chain(Some(0)).collect();
let operation: Vec<u16> = "open".encode_utf16().chain(Some(0)).collect();
let result = unsafe {
ShellExecuteW(
std::ptr::null_mut(),
operation.as_ptr(),
url.as_ptr(),
std::ptr::null(),
std::ptr::null(),
SW_SHOWNORMAL,
)
};
if result as isize <= 32 {
Err(std::io::Error::other("could not open browser"))
} else {
Ok(())
}
}
#[cfg(not(target_os = "windows"))]
fn open_validated_url(url: &str) -> std::io::Result<()> {
let opener = if cfg!(target_os = "macos") {
"open"
} else {
"xdg-open"
};
let mut child = std::process::Command::new(opener).arg(url).spawn()?;
std::thread::spawn(move || {
let _ = child.wait();
});
Ok(())
}