diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index fafd1412d4cb..e3fd04fe20f7 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -19,12 +19,12 @@ use crate::path::{Path, PathBuf}; use crate::ptr; use crate::sys::c; use crate::sys::c::NonZeroDWORD; +use crate::sys::cvt; use crate::sys::fs::{File, OpenOptions}; use crate::sys::handle::Handle; use crate::sys::path; use crate::sys::pipe::{self, AnonPipe}; use crate::sys::stdio; -use crate::sys::{cvt, to_u16s}; use crate::sys_common::mutex::StaticMutex; use crate::sys_common::process::{CommandEnv, CommandEnvs}; use crate::sys_common::{AsInner, IntoInner}; @@ -269,8 +269,13 @@ impl Command { None }; let program = resolve_exe(&self.program, || env::var_os("PATH"), child_paths)?; + let is_batch_file = program + .extension() + .map(|ext| ext.eq_ignore_ascii_case("cmd") || ext.eq_ignore_ascii_case("bat")) + .unwrap_or(false); + let program = path::maybe_verbatim(&program)?; let mut cmd_str = - make_command_line(program.as_os_str(), &self.args, self.force_quotes_enabled)?; + make_command_line(&program, &self.args, self.force_quotes_enabled, is_batch_file)?; cmd_str.push(0); // add null terminator // stolen from the libuv code. @@ -309,7 +314,6 @@ impl Command { si.hStdOutput = stdout.as_raw_handle(); si.hStdError = stderr.as_raw_handle(); - let program = to_u16s(&program)?; unsafe { cvt(c::CreateProcessW( program.as_ptr(), @@ -730,7 +734,12 @@ enum Quote { // Produces a wide string *without terminating null*; returns an error if // `prog` or any of the `args` contain a nul. -fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Result> { +fn make_command_line( + prog: &[u16], + args: &[Arg], + force_quotes: bool, + is_batch_file: bool, +) -> io::Result> { // Encode the command and arguments in a command line string such // that the spawned process may recover them using CommandLineToArgvW. let mut cmd: Vec = Vec::new(); @@ -739,17 +748,18 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu // 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)?; + // Always quote the program name so CreateProcess to avoid ambiguity when + // the child process parses its arguments. + // Note that quotes aren't escaped here because they can't be used in arg0. + // But that's ok because file paths can't contain quotes. + cmd.push(b'"' as u16); + cmd.extend_from_slice(prog.strip_suffix(&[0]).unwrap_or(prog)); + cmd.push(b'"' as u16); + for arg in args { cmd.push(' ' as u16); let (arg, quote) = match arg { diff --git a/library/std/src/sys/windows/process/tests.rs b/library/std/src/sys/windows/process/tests.rs index d18c3d855bcc..a199bb8b5678 100644 --- a/library/std/src/sys/windows/process/tests.rs +++ b/library/std/src/sys/windows/process/tests.rs @@ -3,11 +3,12 @@ use super::Arg; use crate::env; use crate::ffi::{OsStr, OsString}; use crate::process::Command; +use crate::sys::to_u16s; #[test] fn test_raw_args() { let command_line = &make_command_line( - OsStr::new("quoted exe"), + &to_u16s("quoted exe").unwrap(), &[ Arg::Regular(OsString::from("quote me")), Arg::Raw(OsString::from("quote me *not*")), @@ -16,6 +17,7 @@ fn test_raw_args() { Arg::Regular(OsString::from("optional-quotes")), ], false, + false, ) .unwrap(); assert_eq!( @@ -28,9 +30,10 @@ fn test_raw_args() { fn test_make_command_line() { fn test_wrapper(prog: &str, args: &[&str], force_quotes: bool) -> String { let command_line = &make_command_line( - OsStr::new(prog), + &to_u16s(prog).unwrap(), &args.iter().map(|a| Arg::Regular(OsString::from(a))).collect::>(), force_quotes, + false, ) .unwrap(); String::from_utf16(command_line).unwrap()