parent
fcd5cecdcf
commit
d868da7796
3 changed files with 86 additions and 19 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#![stable(feature = "process_extensions", since = "1.2.0")]
|
#![stable(feature = "process_extensions", since = "1.2.0")]
|
||||||
|
|
||||||
|
use crate::ffi::OsStr;
|
||||||
use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
|
use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
|
||||||
use crate::process;
|
use crate::process;
|
||||||
use crate::sealed::Sealed;
|
use crate::sealed::Sealed;
|
||||||
|
@ -125,6 +126,13 @@ pub trait CommandExt: Sealed {
|
||||||
/// [2]: <https://msdn.microsoft.com/en-us/library/17w5ykft.aspx>
|
/// [2]: <https://msdn.microsoft.com/en-us/library/17w5ykft.aspx>
|
||||||
#[unstable(feature = "windows_process_extensions_force_quotes", issue = "82227")]
|
#[unstable(feature = "windows_process_extensions_force_quotes", issue = "82227")]
|
||||||
fn force_quotes(&mut self, enabled: bool) -> &mut process::Command;
|
fn force_quotes(&mut self, enabled: bool) -> &mut process::Command;
|
||||||
|
|
||||||
|
/// Append literal text to the command line without any quoting or escaping.
|
||||||
|
///
|
||||||
|
/// This is useful for passing arguments to `cmd.exe /c`, which doesn't follow
|
||||||
|
/// `CommandLineToArgvW` escaping rules.
|
||||||
|
#[unstable(feature = "windows_process_extensions_raw_arg", issue = "29494")]
|
||||||
|
fn raw_arg(&mut self, text_to_append_as_is: &OsStr) -> &mut process::Command;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[stable(feature = "windows_process_extensions", since = "1.16.0")]
|
#[stable(feature = "windows_process_extensions", since = "1.16.0")]
|
||||||
|
@ -138,4 +146,9 @@ impl CommandExt for process::Command {
|
||||||
self.as_inner_mut().force_quotes(enabled);
|
self.as_inner_mut().force_quotes(enabled);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn raw_arg(&mut self, raw_text: &OsStr) -> &mut process::Command {
|
||||||
|
self.as_inner_mut().raw_arg(raw_text);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> io::Result<T> {
|
||||||
|
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
program: OsString,
|
program: OsString,
|
||||||
args: Vec<OsString>,
|
args: Vec<Arg>,
|
||||||
env: CommandEnv,
|
env: CommandEnv,
|
||||||
cwd: Option<OsString>,
|
cwd: Option<OsString>,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
|
@ -161,6 +161,14 @@ pub struct StdioPipes {
|
||||||
pub stderr: Option<AnonPipe>,
|
pub stderr: Option<AnonPipe>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Arg {
|
||||||
|
/// Add quotes (if needed)
|
||||||
|
Regular(OsString),
|
||||||
|
/// Append raw string without quoting
|
||||||
|
Raw(OsString),
|
||||||
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn new(program: &OsStr) -> Command {
|
pub fn new(program: &OsStr) -> Command {
|
||||||
Command {
|
Command {
|
||||||
|
@ -178,7 +186,7 @@ impl Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arg(&mut self, arg: &OsStr) {
|
pub fn arg(&mut self, arg: &OsStr) {
|
||||||
self.args.push(arg.to_os_string())
|
self.args.push(Arg::Regular(arg.to_os_string()))
|
||||||
}
|
}
|
||||||
pub fn env_mut(&mut self) -> &mut CommandEnv {
|
pub fn env_mut(&mut self) -> &mut CommandEnv {
|
||||||
&mut self.env
|
&mut self.env
|
||||||
|
@ -203,6 +211,10 @@ impl Command {
|
||||||
self.force_quotes_enabled = enabled;
|
self.force_quotes_enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raw_arg(&mut self, command_str_to_append: &OsStr) {
|
||||||
|
self.args.push(Arg::Raw(command_str_to_append.to_os_string()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_program(&self) -> &OsStr {
|
pub fn get_program(&self) -> &OsStr {
|
||||||
&self.program
|
&self.program
|
||||||
}
|
}
|
||||||
|
@ -536,36 +548,54 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Quote {
|
||||||
|
// Every arg is quoted
|
||||||
|
Always,
|
||||||
|
// Whitespace and empty args are quoted
|
||||||
|
Auto,
|
||||||
|
// Arg appended without any changes (#29494)
|
||||||
|
Never,
|
||||||
|
}
|
||||||
|
|
||||||
// Produces a wide string *without terminating null*; returns an error if
|
// Produces a wide string *without terminating null*; returns an error if
|
||||||
// `prog` or any of the `args` contain a nul.
|
// `prog` or any of the `args` contain a nul.
|
||||||
fn make_command_line(prog: &OsStr, args: &[OsString], force_quotes: bool) -> io::Result<Vec<u16>> {
|
fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Result<Vec<u16>> {
|
||||||
// Encode the command and arguments in a command line string such
|
// Encode the command and arguments in a command line string such
|
||||||
// that the spawned process may recover them using CommandLineToArgvW.
|
// that the spawned process may recover them using CommandLineToArgvW.
|
||||||
let mut cmd: Vec<u16> = Vec::new();
|
let mut cmd: Vec<u16> = Vec::new();
|
||||||
// Always quote the program name so CreateProcess doesn't interpret args as
|
// Always quote the program name so CreateProcess doesn't interpret args as
|
||||||
// part of the name if the binary wasn't found first time.
|
// part of the name if the binary wasn't found first time.
|
||||||
append_arg(&mut cmd, prog, true)?;
|
append_arg(&mut cmd, prog, Quote::Always)?;
|
||||||
for arg in args {
|
for arg in args {
|
||||||
cmd.push(' ' as u16);
|
cmd.push(' ' as u16);
|
||||||
append_arg(&mut cmd, arg, force_quotes)?;
|
let (arg, quote) = match arg {
|
||||||
|
Arg::Regular(arg) => (arg, if force_quotes { Quote::Always } else { Quote::Auto }),
|
||||||
|
Arg::Raw(arg) => (arg, Quote::Never),
|
||||||
|
};
|
||||||
|
append_arg(&mut cmd, arg, quote)?;
|
||||||
}
|
}
|
||||||
return Ok(cmd);
|
return Ok(cmd);
|
||||||
|
|
||||||
fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr, force_quotes: bool) -> io::Result<()> {
|
fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr, quote: Quote) -> io::Result<()> {
|
||||||
// If an argument has 0 characters then we need to quote it to ensure
|
// If an argument has 0 characters then we need to quote it to ensure
|
||||||
// that it actually gets passed through on the command line or otherwise
|
// that it actually gets passed through on the command line or otherwise
|
||||||
// it will be dropped entirely when parsed on the other end.
|
// it will be dropped entirely when parsed on the other end.
|
||||||
ensure_no_nuls(arg)?;
|
ensure_no_nuls(arg)?;
|
||||||
let arg_bytes = &arg.as_inner().inner.as_inner();
|
let arg_bytes = &arg.as_inner().inner.as_inner();
|
||||||
let quote = force_quotes
|
let (quote, escape) = match quote {
|
||||||
|| arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t')
|
Quote::Always => (true, true),
|
||||||
|| arg_bytes.is_empty();
|
Quote::Auto => {
|
||||||
|
(arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty(), true)
|
||||||
|
}
|
||||||
|
Quote::Never => (false, false),
|
||||||
|
};
|
||||||
if quote {
|
if quote {
|
||||||
cmd.push('"' as u16);
|
cmd.push('"' as u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut backslashes: usize = 0;
|
let mut backslashes: usize = 0;
|
||||||
for x in arg.encode_wide() {
|
for x in arg.encode_wide() {
|
||||||
|
if escape {
|
||||||
if x == '\\' as u16 {
|
if x == '\\' as u16 {
|
||||||
backslashes += 1;
|
backslashes += 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -575,6 +605,7 @@ fn make_command_line(prog: &OsStr, args: &[OsString], force_quotes: bool) -> io:
|
||||||
}
|
}
|
||||||
backslashes = 0;
|
backslashes = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
cmd.push(x);
|
cmd.push(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,13 +657,15 @@ fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CommandArgs<'a> {
|
pub struct CommandArgs<'a> {
|
||||||
iter: crate::slice::Iter<'a, OsString>,
|
iter: crate::slice::Iter<'a, Arg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for CommandArgs<'a> {
|
impl<'a> Iterator for CommandArgs<'a> {
|
||||||
type Item = &'a OsStr;
|
type Item = &'a OsStr;
|
||||||
fn next(&mut self) -> Option<&'a OsStr> {
|
fn next(&mut self) -> Option<&'a OsStr> {
|
||||||
self.iter.next().map(|s| s.as_ref())
|
self.iter.next().map(|arg| match arg {
|
||||||
|
Arg::Regular(s) | Arg::Raw(s) => s.as_ref(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
self.iter.size_hint()
|
self.iter.size_hint()
|
||||||
|
|
|
@ -1,14 +1,35 @@
|
||||||
use super::make_command_line;
|
use super::make_command_line;
|
||||||
|
use super::Arg;
|
||||||
use crate::env;
|
use crate::env;
|
||||||
use crate::ffi::{OsStr, OsString};
|
use crate::ffi::{OsStr, OsString};
|
||||||
use crate::process::Command;
|
use crate::process::Command;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_args() {
|
||||||
|
let command_line = &make_command_line(
|
||||||
|
OsStr::new("quoted exe"),
|
||||||
|
&[
|
||||||
|
Arg::Regular(OsString::from("quote me")),
|
||||||
|
Arg::Raw(OsString::from("quote me *not*")),
|
||||||
|
Arg::Raw(OsString::from("\t\\")),
|
||||||
|
Arg::Raw(OsString::from("internal \\\"backslash-\"quote")),
|
||||||
|
Arg::Regular(OsString::from("optional-quotes")),
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf16(command_line).unwrap(),
|
||||||
|
"\"quoted exe\" \"quote me\" quote me *not* \t\\ internal \\\"backslash-\"quote optional-quotes"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_make_command_line() {
|
fn test_make_command_line() {
|
||||||
fn test_wrapper(prog: &str, args: &[&str], force_quotes: bool) -> String {
|
fn test_wrapper(prog: &str, args: &[&str], force_quotes: bool) -> String {
|
||||||
let command_line = &make_command_line(
|
let command_line = &make_command_line(
|
||||||
OsStr::new(prog),
|
OsStr::new(prog),
|
||||||
&args.iter().map(|a| OsString::from(a)).collect::<Vec<OsString>>(),
|
&args.iter().map(|a| Arg::Regular(OsString::from(a))).collect::<Vec<_>>(),
|
||||||
force_quotes,
|
force_quotes,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue