use crate::platform::types::OpenError;
use std::path::Path;
pub(crate) fn program_is_on_path(program: &str) -> bool {
let Some(path) = std::env::var_os("PATH") else {
return false;
};
std::env::split_paths(&path).any(|dir| {
if dir.as_os_str().is_empty() {
return false;
}
if is_executable_file(&dir.join(program)) {
return true;
}
#[cfg(windows)]
{
if let Some(exts) = std::env::var_os("PATHEXT") {
for ext in exts.to_string_lossy().split(';') {
if ext.is_empty() {
continue;
}
let mut name = std::string::String::from(program);
name.push_str(ext);
if is_executable_file(&dir.join(name)) {
return true;
}
}
}
}
false
})
}
fn is_executable_file(path: &Path) -> bool {
let Ok(meta) = std::fs::metadata(path) else {
return false;
};
if !meta.is_file() {
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
meta.permissions().mode() & 0o111 != 0
}
#[cfg(not(unix))]
{
true
}
}
pub(crate) fn validate_target(target: &str) -> Result<(), OpenError> {
if target.is_empty() || target.starts_with('-') {
return Err(OpenError::InvalidTarget);
}
Ok(())
}
pub(crate) fn spawn_handler(
program: &str,
prefix_args: &[&str],
target: &str,
) -> Result<(), OpenError> {
validate_target(target)?;
let mut command = std::process::Command::new(program);
command
.args(prefix_args)
.arg(target)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
super::child_process::in_own_group(&mut command);
command.spawn().map(|_child| ()).map_err(|_error| OpenError::NoHandler)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_option_shaped_or_empty_target_is_refused_before_spawning() {
assert_eq!(validate_target(""), Err(OpenError::InvalidTarget));
assert_eq!(validate_target("-"), Err(OpenError::InvalidTarget));
assert_eq!(validate_target("--help"), Err(OpenError::InvalidTarget));
assert_eq!(validate_target("https://example.com"), Ok(()));
assert_eq!(validate_target("/tmp/a b.txt"), Ok(()));
}
#[test]
fn a_missing_handler_is_reported_as_no_handler() {
let missing = "/nonexistent/rw-open-handler-for-test";
assert_eq!(spawn_handler(missing, &[], "/tmp/x"), Err(OpenError::NoHandler));
}
#[test]
fn validation_precedes_the_spawn_attempt() {
let missing = "/nonexistent/rw-open-handler-for-test";
assert_eq!(spawn_handler(missing, &[], "--version"), Err(OpenError::InvalidTarget));
}
#[test]
fn the_handler_existence_query_answers_without_running_anything() {
let present = if cfg!(windows) { "cmd" } else { "true" };
assert!(program_is_on_path(present), "`{present}` is a standard PATH member on this host");
assert!(
!program_is_on_path("/nonexistent/rw-open-handler-for-test"),
"an absolute path that does not exist must not be reported as a handler"
);
}
#[cfg(unix)]
#[test]
fn a_present_handler_is_started_without_waiting() {
assert_eq!(spawn_handler("true", &[], "some-target"), Ok(()));
}
}