diff --git a/library/std/src/sys/windows/ext/process.rs b/library/std/src/sys/windows/ext/process.rs index 3d680a7f2d94f..67412e1677937 100644 --- a/library/std/src/sys/windows/ext/process.rs +++ b/library/std/src/sys/windows/ext/process.rs @@ -105,6 +105,22 @@ pub trait CommandExt: Sealed { /// [1]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags #[stable(feature = "windows_process_extensions", since = "1.16.0")] fn creation_flags(&mut self, flags: u32) -> &mut process::Command; + + /// Forces all arguments to be wrapped in quote (`"`) characters. + /// + /// This is useful for passing arguments to [MSYS2/Cygwin][1] based + /// executables: these programs will expand unquoted arguments containing + /// wildcard characters (`?` and `*`) by searching for any file paths + /// matching the wildcard pattern. + /// + /// Adding quotes has no effect when passing arguments to programs + /// that use [msvcrt][2]. This includes programs built with both + /// MinGW and MSVC. + /// + /// [1]: + /// [2]: + #[unstable(feature = "windows_process_extensions_force_quotes", issue = "82227")] + fn force_quotes(&mut self, enabled: bool) -> &mut process::Command; } #[stable(feature = "windows_process_extensions", since = "1.16.0")] @@ -113,4 +129,9 @@ impl CommandExt for process::Command { self.as_inner_mut().creation_flags(flags); self } + + fn force_quotes(&mut self, enabled: bool) -> &mut process::Command { + self.as_inner_mut().force_quotes(enabled); + self + } } diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index 243065b94b125..83d37795ee5c1 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -78,6 +78,7 @@ pub struct Command { stdin: Option, stdout: Option, stderr: Option, + force_quotes_enabled: bool, } pub enum Stdio { @@ -109,6 +110,7 @@ impl Command { stdin: None, stdout: None, stderr: None, + force_quotes_enabled: false, } } @@ -134,6 +136,10 @@ impl Command { self.flags = flags; } + pub fn force_quotes(&mut self, enabled: bool) { + self.force_quotes_enabled = enabled; + } + pub fn get_program(&self) -> &OsStr { &self.program } @@ -181,7 +187,7 @@ impl Command { si.dwFlags = c::STARTF_USESTDHANDLES; let program = program.as_ref().unwrap_or(&self.program); - let mut cmd_str = make_command_line(program, &self.args)?; + let mut cmd_str = make_command_line(program, &self.args, self.force_quotes_enabled)?; cmd_str.push(0); // add null terminator // stolen from the libuv code. @@ -467,7 +473,7 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION { // 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: &[OsString]) -> io::Result> { +fn make_command_line(prog: &OsStr, args: &[OsString], force_quotes: 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(); @@ -476,7 +482,7 @@ fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result> { append_arg(&mut cmd, prog, true)?; for arg in args { cmd.push(' ' as u16); - append_arg(&mut cmd, arg, false)?; + append_arg(&mut cmd, arg, force_quotes)?; } return Ok(cmd); diff --git a/library/std/src/sys/windows/process/tests.rs b/library/std/src/sys/windows/process/tests.rs index 81627ad139bb9..8830ae049c65d 100644 --- a/library/std/src/sys/windows/process/tests.rs +++ b/library/std/src/sys/windows/process/tests.rs @@ -3,29 +3,41 @@ use crate::ffi::{OsStr, OsString}; #[test] fn test_make_command_line() { - fn test_wrapper(prog: &str, args: &[&str]) -> String { + fn test_wrapper(prog: &str, args: &[&str], force_quotes: bool) -> String { let command_line = &make_command_line( OsStr::new(prog), &args.iter().map(|a| OsString::from(a)).collect::>(), + force_quotes, ) .unwrap(); String::from_utf16(command_line).unwrap() } - assert_eq!(test_wrapper("prog", &["aaa", "bbb", "ccc"]), "\"prog\" aaa bbb ccc"); + assert_eq!(test_wrapper("prog", &["aaa", "bbb", "ccc"], false), "\"prog\" aaa bbb ccc"); assert_eq!( - test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa"]), + test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa"], false), "\"C:\\Program Files\\blah\\blah.exe\" aaa" ); assert_eq!( - test_wrapper("C:\\Program Files\\test", &["aa\"bb"]), + test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa", "v*"], false), + "\"C:\\Program Files\\blah\\blah.exe\" aaa v*" + ); + assert_eq!( + test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa", "v*"], true), + "\"C:\\Program Files\\blah\\blah.exe\" \"aaa\" \"v*\"" + ); + assert_eq!( + test_wrapper("C:\\Program Files\\test", &["aa\"bb"], false), "\"C:\\Program Files\\test\" aa\\\"bb" ); - assert_eq!(test_wrapper("echo", &["a b c"]), "\"echo\" \"a b c\""); - assert_eq!(test_wrapper("echo", &["\" \\\" \\", "\\"]), "\"echo\" \"\\\" \\\\\\\" \\\\\" \\"); + assert_eq!(test_wrapper("echo", &["a b c"], false), "\"echo\" \"a b c\""); + assert_eq!( + test_wrapper("echo", &["\" \\\" \\", "\\"], false), + "\"echo\" \"\\\" \\\\\\\" \\\\\" \\" + ); assert_eq!( - test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[]), + test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[], false), "\"\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}\"" ); }