diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-28 02:19:33 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-28 02:19:33 +0300 |
| commit | b499e611b6a983ce6077da555ad55c1fd58b1387 (patch) | |
| tree | bca459a912ad7159b2a46d62ed095093348270f6 /src/plugin_command.rs | |
| parent | 7383f120c669bad7e55118d920a6dbb76112dfe0 (diff) | |
fix: resolve relative plugin commands from plugin root
refs #1949
Diffstat (limited to 'src/plugin_command.rs')
| -rw-r--r-- | src/plugin_command.rs | 93 |
1 files changed, 72 insertions, 21 deletions
diff --git a/src/plugin_command.rs b/src/plugin_command.rs index a0f71894..6b639f0d 100644 --- a/src/plugin_command.rs +++ b/src/plugin_command.rs @@ -1,25 +1,37 @@ -#[cfg(any(windows, test))] +use std::ffi::{OsStr, OsString}; use std::path::Path; #[cfg(windows)] use std::path::PathBuf; use std::process::Command; -pub(crate) fn command_for_argv(program: &str, args: &[String]) -> Command { - let mut command = command_for_program(program); - command.args(args); +pub(crate) fn command_for_argv_in_dir(program: &str, args: &[String], cwd: &Path) -> Command { + let program = program_for_cwd(program, cwd); + let mut command = command_for_program(&program); + command.args(args).current_dir(cwd); command } +fn program_for_cwd(program: &str, cwd: &Path) -> OsString { + let path = Path::new(program); + let has_separator = program.contains('/') || (cfg!(windows) && program.contains('\\')); + if path.is_relative() && has_separator { + let relative = path.strip_prefix(Path::new(".")).unwrap_or(path); + cwd.join(relative).into_os_string() + } else { + path.as_os_str().to_os_string() + } +} + #[cfg(not(windows))] -fn command_for_program(program: &str) -> Command { +fn command_for_program(program: &OsStr) -> Command { crate::noninteractive_process::command(program) } #[cfg(windows)] -fn command_for_program(program: &str) -> Command { +fn command_for_program(program: &OsStr) -> Command { let resolved = resolve_windows_program(program); let command_program = resolved.as_ref().map_or_else( - || std::ffi::OsString::from(program), + || program.to_os_string(), |path| path.as_os_str().to_os_string(), ); if is_windows_batch_file_name(program) @@ -38,7 +50,7 @@ fn command_for_program(program: &str) -> Command { } #[cfg(windows)] -fn resolve_windows_program(program: &str) -> Option<PathBuf> { +fn resolve_windows_program(program: &OsStr) -> Option<PathBuf> { if has_path_separator(program) { return None; } @@ -55,7 +67,11 @@ fn resolve_windows_program(program: &str) -> Option<PathBuf> { std::env::split_paths(&path_var).find_map(|dir| { extensions .iter() - .map(|extension| dir.join(format!("{program}{extension}"))) + .map(|extension| { + let mut file_name = program.to_os_string(); + file_name.push(extension); + dir.join(file_name) + }) .find(|candidate| candidate.is_file()) }) }) @@ -91,22 +107,22 @@ fn windows_path_extensions() -> Vec<String> { } #[cfg(windows)] -fn has_path_separator(program: &str) -> bool { - program.contains(['/', '\\']) +fn has_path_separator(program: &OsStr) -> bool { + program.to_string_lossy().contains(['/', '\\']) } #[cfg(windows)] fn is_windows_batch_path(path: &Path) -> bool { path.extension() - .and_then(std::ffi::OsStr::to_str) + .and_then(OsStr::to_str) .is_some_and(is_windows_batch_extension) } #[cfg(any(windows, test))] -fn is_windows_batch_file_name(program: &str) -> bool { +fn is_windows_batch_file_name(program: &OsStr) -> bool { Path::new(program) .extension() - .and_then(std::ffi::OsStr::to_str) + .and_then(OsStr::to_str) .is_some_and(is_windows_batch_extension) } @@ -120,11 +136,22 @@ mod tests { use super::*; #[test] + fn resolves_explicit_relative_program_against_working_directory() { + let cwd = Path::new("plugin-root"); + + assert_eq!( + program_for_cwd("./bin/tool", cwd), + cwd.join("bin/tool").into_os_string() + ); + assert_eq!(program_for_cwd("tool", cwd), OsString::from("tool")); + } + + #[test] fn recognizes_windows_batch_extensions_case_insensitively() { - assert!(is_windows_batch_file_name("npm.cmd")); - assert!(is_windows_batch_file_name("script.BAT")); - assert!(!is_windows_batch_file_name("node.exe")); - assert!(!is_windows_batch_file_name("node")); + assert!(is_windows_batch_file_name(OsStr::new("npm.cmd"))); + assert!(is_windows_batch_file_name(OsStr::new("script.BAT"))); + assert!(!is_windows_batch_file_name(OsStr::new("node.exe"))); + assert!(!is_windows_batch_file_name(OsStr::new("node"))); } #[cfg(windows)] @@ -135,10 +162,12 @@ mod tests { std::process::id() )); std::fs::write(&path, "@echo off\r\necho plugin-%1\r\n").expect("write batch fixture"); + let cwd = path.parent().expect("batch fixture parent"); - let output = command_for_argv(&path.display().to_string(), &["ready".to_string()]) - .output() - .expect("run batch fixture"); + let output = + command_for_argv_in_dir(&path.display().to_string(), &["ready".to_string()], cwd) + .output() + .expect("run batch fixture"); let _ = std::fs::remove_file(&path); assert!(output.status.success(), "{output:?}"); @@ -147,4 +176,26 @@ mod tests { "plugin-ready" ); } + + #[cfg(windows)] + #[test] + fn windows_explicit_relative_executable_runs_from_working_directory() { + let root = std::env::temp_dir().join(format!( + "herdr-plugin-relative-command-{}", + std::process::id() + )); + std::fs::create_dir_all(&root).expect("create relative command fixture"); + let source = PathBuf::from(std::env::var_os("SystemRoot").expect("SystemRoot")) + .join("System32") + .join("where.exe"); + let executable = root.join("tool.exe"); + std::fs::copy(source, &executable).expect("copy relative command fixture"); + + let output = command_for_argv_in_dir("./tool.exe", &["/?".to_string()], &root) + .output() + .expect("run relative executable"); + let _ = std::fs::remove_dir_all(&root); + + assert!(output.status.success(), "{output:?}"); + } } |
