Skip to content

Commit 3afed8f

Browse files
authored
Rollup merge of #92208 - ChrisDenton:win-bat-cmd, r=dtolnay
Quote bat script command line Fixes #91991 [`CreateProcessW`](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw#parameters) should only be used to run exe files but it does have some (undocumented) special handling for files with `.bat` and `.cmd` extensions. Essentially those magic extensions will cause the parameters to be automatically rewritten. Example pseudo Rust code (note that `CreateProcess` starts with an optional application name followed by the application arguments): ```rust // These arguments... CreateProcess(None, `@"foo.bat` "hello world""`@,` ...); // ...are rewritten as CreateProcess(Some(r"C:\Windows\System32\cmd.exe"), `@""foo.bat` "hello world"""`@,` ...); ``` However, when setting the first parameter (the application name) as we now do, it will omit the extra level of quotes around the arguments: ```rust // These arguments... CreateProcess(Some("foo.bat"), `@"foo.bat` "hello world""`@,` ...); // ...are rewritten as CreateProcess(Some(r"C:\Windows\System32\cmd.exe"), `@"foo.bat` "hello world""`@,` ...); ``` This means the arguments won't be passed to the script as intended. Note that running batch files this way is undocumented but people have relied on this so we probably shouldn't break it.
2 parents 051d91a + de764a7 commit 3afed8f

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

Diff for: library/std/src/process/tests.rs

+19
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,22 @@ fn env_empty() {
420420
let p = Command::new("cmd").args(&["/C", "exit 0"]).env_clear().spawn();
421421
assert!(p.is_ok());
422422
}
423+
424+
// See issue #91991
425+
#[test]
426+
#[cfg(windows)]
427+
fn run_bat_script() {
428+
let tempdir = crate::sys_common::io::test::tmpdir();
429+
let script_path = tempdir.join("hello.cmd");
430+
431+
crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap();
432+
let output = Command::new(&script_path)
433+
.arg("fellow Rustaceans")
434+
.stdout(crate::process::Stdio::piped())
435+
.spawn()
436+
.unwrap()
437+
.wait_with_output()
438+
.unwrap();
439+
assert!(output.status.success());
440+
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!");
441+
}

Diff for: library/std/src/sys/windows/process.rs

+16
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,19 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
704704
// Encode the command and arguments in a command line string such
705705
// that the spawned process may recover them using CommandLineToArgvW.
706706
let mut cmd: Vec<u16> = Vec::new();
707+
708+
// CreateFileW has special handling for .bat and .cmd files, which means we
709+
// need to add an extra pair of quotes surrounding the whole command line
710+
// so they are properly passed on to the script.
711+
// See issue #91991.
712+
let is_batch_file = Path::new(prog)
713+
.extension()
714+
.map(|ext| ext.eq_ignore_ascii_case("cmd") || ext.eq_ignore_ascii_case("bat"))
715+
.unwrap_or(false);
716+
if is_batch_file {
717+
cmd.push(b'"' as u16);
718+
}
719+
707720
// Always quote the program name so CreateProcess doesn't interpret args as
708721
// part of the name if the binary wasn't found first time.
709722
append_arg(&mut cmd, prog, Quote::Always)?;
@@ -715,6 +728,9 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
715728
};
716729
append_arg(&mut cmd, arg, quote)?;
717730
}
731+
if is_batch_file {
732+
cmd.push(b'"' as u16);
733+
}
718734
return Ok(cmd);
719735

720736
fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr, quote: Quote) -> io::Result<()> {

0 commit comments

Comments
 (0)