use std::{ borrow::Cow, ffi::{OsStr, OsString}, path::{Path, PathBuf}, process::{Command, Stdio}, }; use bstr::ByteSlice; use crate::{Context, Prepare, extract_interpreter, is_bare_command, split_paths, win_path_lookup}; /// Builder impl Prepare { /// If called, the command will be checked for characters that are typical for shell /// scripts, and if found will use `sh` to execute it or whatever is set as /// [`with_shell_program()`](Self::with_shell_program()). /// /// Commands are inspected as bytes, including non-UTF-8 commands on Unix. If the platform /// cannot represent a command as bytes, it is invoked directly. /// /// If a shell is used, then arguments given here with [arg()](Self::arg) or /// [args()](Self::args) will be substituted via `"$@"` if it's not already present in the /// command. /// /// /// The [`command_may_be_shell_script_allow_manual_argument_splitting()`](Self::command_may_be_shell_script_allow_manual_argument_splitting()) /// and [`command_may_be_shell_script_disallow_manual_argument_splitting()`](Self::command_may_be_shell_script_disallow_manual_argument_splitting()) /// methods also call this method. /// /// If neither this method nor [`with_shell()`](Self::with_shell()) is called, commands are /// always executed verbatim and directly, without the use of a shell. pub fn command_may_be_shell_script(mut self) -> Self { self.use_shell = gix_path::os_str_into_bstr(&self.command) .is_ok_and(|cmd| cmd.find_byteset(b"|&;<>()$`\\\"' \t\n*?[#~=%").is_some()); self } /// If called, unconditionally use a shell to execute the command and its arguments. /// /// This uses `sh` to execute it, or whatever is set as /// [`with_shell_program()`](Self::with_shell_program()). /// /// Arguments given here with [arg()](Self::arg) or [args()](Self::args) will be /// substituted via `"$@"` if it's not already present in the command. /// /// If neither this method nor /// [`command_may_be_shell_script()`](Self::command_may_be_shell_script()) is called, /// commands are always executed verbatim and directly, without the use of a shell. (But /// see [`command_may_be_shell_script()`](Self::command_may_be_shell_script()) on other /// methods that call that method.) /// /// We also disallow manual argument splitting /// (see [`command_may_be_shell_script_disallow_manual_argument_splitting`](Self::command_may_be_shell_script_disallow_manual_argument_splitting())) /// to assure a shell is indeed used, no matter what. pub fn with_shell(mut self) -> Self { self.use_shell = true; self.allow_manual_arg_splitting = false; self } /// Quote the command if it is run in a shell, so its path is left intact. /// /// This is only meaningful if the command has been arranged to run in a shell, either /// unconditionally with [`with_shell()`](Self::with_shell()), or conditionally with /// [`command_may_be_shell_script()`](Self::command_may_be_shell_script()). /// /// Note that this should not be used if the command is a script - quoting is only the /// right choice if it's known to be a program path. /// /// Note also that this does not affect arguments passed with [arg()](Self::arg) or /// [args()](Self::args), which do not have to be quoted by the *caller* because they are /// passed as `"$@"` positional parameters (`"$1"`, `"$2"`, and so on). pub fn with_quoted_command(mut self) -> Self { self.quote_command = true; self } /// Set the name or path to the shell `program` to use if a shell is to be used, to avoid /// using the default shell which is `sh`. /// /// Note that shells that are not Bourne-style cannot be expected to work correctly, /// because POSIX shell syntax is assumed when searching for and conditionally adding /// `"$@"` to receive arguments, where applicable (and in the behaviour of /// [`with_quoted_command()`](Self::with_quoted_command()), if called). pub fn with_shell_program(mut self, program: impl Into) -> Self { self.shell_program = Some(program.into()); self } /// Unconditionally turn off using the shell when spawning the command. /// /// Note that not using the shell is the default. So an effective use of this method /// is some time after [`command_may_be_shell_script()`](Self::command_may_be_shell_script()) /// or [`with_shell()`](Self::with_shell()) was called. pub fn without_shell(mut self) -> Self { self.use_shell = false; self } /// Set additional `ctx` to be used when spawning the process. /// /// Note that this is a must for most kind of commands that `git` usually spawns, as at /// least they need to know the correct Git repository to function. pub fn with_context(mut self, ctx: Context) -> Self { self.context = Some(ctx); self } /// Like [`command_may_be_shell_script()`](Self::command_may_be_shell_script()), but try to /// split arguments by hand if this can be safely done without a shell. /// /// This is useful on platforms where spawning processes is slow, or where many processes /// have to be spawned in a row which should be sped up. Manual argument splitting is /// enabled by default on Windows only. /// /// Note that this does *not* check for the use of possible shell builtins. Commands may /// fail or behave differently if they are available as shell builtins and no corresponding /// external command exists, or the external command behaves differently. /// Leading shell assignment words are applied to the environment when followed by a command. pub fn command_may_be_shell_script_allow_manual_argument_splitting(mut self) -> Self { self.allow_manual_arg_splitting = true; self.command_may_be_shell_script() } /// Like [`command_may_be_shell_script()`](Self::command_may_be_shell_script()), but don't /// allow to bypass the shell even if manual argument splitting can be performed safely. pub fn command_may_be_shell_script_disallow_manual_argument_splitting(mut self) -> Self { self.allow_manual_arg_splitting = false; self.command_may_be_shell_script() } /// Configure the process to use `stdio` for _stdin_. pub fn stdin(mut self, stdio: Stdio) -> Self { self.stdin = stdio; self } /// Configure the process to use `stdio` for _stdout_. pub fn stdout(mut self, stdio: Stdio) -> Self { self.stdout = stdio; self } /// Configure the process to use `stdio` for _stderr_. pub fn stderr(mut self, stdio: Stdio) -> Self { self.stderr = stdio; self } /// Add `arg` to the list of arguments to call the command with. pub fn arg(mut self, arg: impl Into) -> Self { self.args.push(arg.into()); self } /// Add `args` to the list of arguments to call the command with. pub fn args(mut self, args: impl IntoIterator>) -> Self { self.args .append(&mut args.into_iter().map(Into::into).collect::>()); self } /// Add `key` with `value` to the environment of the spawned command. pub fn env(mut self, key: impl Into, value: impl Into) -> Self { self.env.push((key.into(), value.into())); self } } /// Finalization impl Prepare { /// Spawn the command as configured. pub fn spawn(self) -> std::io::Result { let mut cmd = Command::from(self); gix_trace::debug!(cmd = ?cmd); cmd.spawn() } } impl From for Command { fn from(mut prep: Prepare) -> Command { let mut inline_env = Vec::new(); let mut cmd = if prep.use_shell { let split_args = prep .allow_manual_arg_splitting .then(|| { let command = gix_path::os_str_into_bstr(&prep.command).ok()?; if command.find_byteset(b"\\|&;<>()$`\n*?[#~%").is_none() { crate::parse::command_line(command).ok() } else { None } }) .flatten(); match split_args { Some(parsed) => { let mut cmd = if cfg!(windows) { windows_command(parsed.command, &prep.env, &parsed.env) } else { Command::new(parsed.command) }; cmd.args(parsed.args); inline_env = parsed.env; cmd } None => { let mut cmd = match prep.shell_program { Some(shell) => Command::new(shell), None => gix_path::env::shell_command(), }; // Passed as `command_name` after `-c