Skip to content

Commit

Permalink
Rollup merge of #92208 - ChrisDenton:win-bat-cmd, r=dtolnay
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
matthiaskrgr authored Dec 22, 2021
2 parents 051d91a + de764a7 commit 3afed8f
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 0 deletions.
19 changes: 19 additions & 0 deletions library/std/src/process/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,3 +420,22 @@ fn env_empty() {
let p = Command::new("cmd").args(&["/C", "exit 0"]).env_clear().spawn();
assert!(p.is_ok());
}

// See issue #91991
#[test]
#[cfg(windows)]
fn run_bat_script() {
let tempdir = crate::sys_common::io::test::tmpdir();
let script_path = tempdir.join("hello.cmd");

crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap();
let output = Command::new(&script_path)
.arg("fellow Rustaceans")
.stdout(crate::process::Stdio::piped())
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(output.status.success());
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!");
}
16 changes: 16 additions & 0 deletions library/std/src/sys/windows/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,19 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
// Encode the command and arguments in a command line string such
// that the spawned process may recover them using CommandLineToArgvW.
let mut cmd: Vec<u16> = Vec::new();

// CreateFileW has special handling for .bat and .cmd files, which means we
// need to add an extra pair of quotes surrounding the whole command line
// so they are properly passed on to the script.
// See issue #91991.
let is_batch_file = Path::new(prog)
.extension()
.map(|ext| ext.eq_ignore_ascii_case("cmd") || ext.eq_ignore_ascii_case("bat"))
.unwrap_or(false);
if is_batch_file {
cmd.push(b'"' as u16);
}

// Always quote the program name so CreateProcess doesn't interpret args as
// part of the name if the binary wasn't found first time.
append_arg(&mut cmd, prog, Quote::Always)?;
Expand All @@ -715,6 +728,9 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
};
append_arg(&mut cmd, arg, quote)?;
}
if is_batch_file {
cmd.push(b'"' as u16);
}
return Ok(cmd);

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

0 comments on commit 3afed8f

Please sign in to comment.