Skip to content

Commit a97e60c

Browse files
authored
Shell-escape build command args in --print-commands-only output. (#2502)
1 parent f7f24a9 commit a97e60c

1 file changed

Lines changed: 50 additions & 16 deletions

File tree

  • cmd/soroban-cli/src/commands/contract

cmd/soroban-cli/src/commands/contract/build.rs

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use rustc_version::version;
55
use semver::Version;
66
use sha2::{Digest, Sha256};
77
use std::{
8-
borrow::Cow,
98
collections::HashSet,
109
env,
1110
ffi::OsStr,
@@ -283,21 +282,7 @@ impl Cmd {
283282
// optimization using markers.
284283
cmd.env("SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2", "1");
285284

286-
let mut cmd_str_parts = Vec::<String>::new();
287-
cmd_str_parts.extend(cmd.get_envs().map(|(key, val)| {
288-
format!(
289-
"{}={}",
290-
key.to_string_lossy(),
291-
shell_escape::escape(val.unwrap_or_default().to_string_lossy())
292-
)
293-
}));
294-
cmd_str_parts.push("cargo".to_string());
295-
cmd_str_parts.extend(
296-
cmd.get_args()
297-
.map(OsStr::to_string_lossy)
298-
.map(Cow::into_owned),
299-
);
300-
let cmd_str = cmd_str_parts.join(" ");
285+
let cmd_str = serialize_command(&cmd);
301286

302287
if self.print_commands_only {
303288
println!("{cmd_str}");
@@ -589,6 +574,24 @@ impl Cmd {
589574
}
590575
}
591576

577+
fn serialize_command(cmd: &Command) -> String {
578+
let mut parts = Vec::<String>::new();
579+
parts.extend(cmd.get_envs().map(|(key, val)| {
580+
format!(
581+
"{}={}",
582+
key.to_string_lossy(),
583+
shell_escape::escape(val.unwrap_or_default().to_string_lossy())
584+
)
585+
}));
586+
parts.push(cmd.get_program().to_string_lossy().into_owned());
587+
parts.extend(
588+
cmd.get_args()
589+
.map(OsStr::to_string_lossy)
590+
.map(|a| shell_escape::escape(a).into_owned()),
591+
);
592+
parts.join(" ")
593+
}
594+
592595
/// Configure cargo/rustc to replace absolute paths in panic messages / debuginfo
593596
/// with relative paths.
594597
///
@@ -812,3 +815,34 @@ pub fn filter_and_dedup_spec(
812815
}
813816
Ok(filtered_xdr)
814817
}
818+
819+
#[cfg(test)]
820+
mod tests {
821+
use super::*;
822+
823+
#[test]
824+
fn serialize_command_shell_escapes_args_with_metacharacters() {
825+
let raw_arg = "--manifest-path=/path/to/contract;touch PWNED;#/Cargo.toml";
826+
let escaped_arg = shell_escape::escape(raw_arg.into()).into_owned();
827+
828+
let mut cmd = Command::new("cargo");
829+
cmd.arg("rustc");
830+
cmd.arg(raw_arg);
831+
832+
let output = serialize_command(&cmd);
833+
834+
// The full escaped form of the argument must appear verbatim.
835+
assert!(
836+
output.contains(&escaped_arg),
837+
"expected escaped arg {escaped_arg:?} in output: {output}"
838+
);
839+
840+
// Round-trip through shlex: the metacharacter-laden arg must parse back
841+
// as a single token equal to the original.
842+
let tokens = shlex::split(&output).expect("serialize_command output must be valid shell");
843+
assert!(
844+
tokens.iter().any(|t| t == raw_arg),
845+
"shlex round-trip failed: {raw_arg:?} not found as a single token in {tokens:?}"
846+
);
847+
}
848+
}

0 commit comments

Comments
 (0)