Skip to content

Commit

Permalink
Expose force_quotes on Windows.
Browse files Browse the repository at this point in the history
Quotes the arg and not quotes the arg have different effect on Windows when the program called
are msys2/cygwin program.
Refer to msys2/MSYS2-packages#2176

Signed-off-by: Yonggang Luo <[email protected]>
  • Loading branch information
lygstate committed Feb 17, 2021
1 parent ee88f46 commit fa23ddf
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 10 deletions.
21 changes: 21 additions & 0 deletions library/std/src/sys/windows/ext/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]: <https://github.com/msys2/MSYS2-packages/issues/2176>
/// [2]: <https://msdn.microsoft.com/en-us/library/17w5ykft.aspx>
#[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")]
Expand All @@ -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
}
}
12 changes: 9 additions & 3 deletions library/std/src/sys/windows/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub struct Command {
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
force_quotes_enabled: bool,
}

pub enum Stdio {
Expand Down Expand Up @@ -109,6 +110,7 @@ impl Command {
stdin: None,
stdout: None,
stderr: None,
force_quotes_enabled: false,
}
}

Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<Vec<u16>> {
fn make_command_line(prog: &OsStr, args: &[OsString], force_quotes: bool) -> io::Result<Vec<u16>> {
// 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();
Expand All @@ -476,7 +482,7 @@ fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result<Vec<u16>> {
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);

Expand Down
26 changes: 19 additions & 7 deletions library/std/src/sys/windows/process/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<OsString>>(),
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}\""
);
}

0 comments on commit fa23ddf

Please sign in to comment.