diff --git a/CHANGELOG.md b/CHANGELOG.md index 1025b3c2..ae0bdca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,13 @@ functionality, under the "Removed" section. explicit through the `--install-bootloader` flag. ([#424](https://github.com/nix-community/nh/issues/424)) +### Fixed + +- Correctly handle spaces and quoted arguments when constructing elevated commands. + Previously command lines were split on whitespace, which broke arguments containing spaces. + `shlex` is now used to parse the command line, ensuring quotes and escapes are + preserved when building `std::process::Command` via `Command::self_elevate_cmd`. + ## 4.2.0 ### Changed diff --git a/Cargo.lock b/Cargo.lock index d86e1969..ff53ec24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1243,6 +1243,7 @@ dependencies = [ "serde", "serde_json", "serial_test", + "shlex", "subprocess", "supports-hyperlinks", "system-configuration", diff --git a/Cargo.toml b/Cargo.toml index bcacc4c7..271da7bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ secrecy = { version = "0.10.3", features = [ "serde" ] } semver = "1.0.26" serde = { features = [ "derive" ], version = "1.0.219" } serde_json = "1.0.143" +shlex = "1.3.0" subprocess = "0.2.9" supports-hyperlinks = "3.1.0" tempfile = "3.21.0" diff --git a/src/commands.rs b/src/commands.rs index 04c19d7a..d72cd87a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -428,13 +428,18 @@ impl Command { // Convert Exec to std::process::Command by parsing the command line let cmdline = final_exec.to_cmdline_lossy(); - let parts: Vec<&str> = cmdline.split_whitespace().collect(); + let parts = match shlex::split(&cmdline) { + Some(tokens) => tokens, + None => { + bail!("Failed to parse sudo command") + }, + }; if parts.is_empty() { bail!("Failed to build sudo command"); } - let mut std_cmd = std::process::Command::new(parts[0]); + let mut std_cmd = std::process::Command::new(&parts[0]); if parts.len() > 1 { std_cmd.args(&parts[1..]); } @@ -979,7 +984,12 @@ mod tests { // injected (e.g., NH_SUDO_ASKPASS). Accept any command line where // 'sudo' is present as a token. let cmdline = sudo_exec.to_cmdline_lossy(); - assert!(cmdline.split_whitespace().any(|tok| tok == "sudo")); + assert!( + shlex::split(&cmdline) + .unwrap_or_default() + .iter() + .any(|tok| tok == "sudo") + ); } #[test]