Skip to content

Commit e90408e

Browse files
committed
std: Fix process spawn for arguments ending in backslashes on Windows
Fix `make_command_line` for the case of backslashes at the end of an argument requiring quotes. We must encode the command and arguments such that `CommandLineToArgvW` recovers them in the spawned process. Simplify the logic by using a running count of backslashes as they are encountered instead of looking ahead for quotes following them. Extend `test_make_command_line` to additionally cover: * a leading quote in an argument that requires quotes, * a backslash before a quote in an argument that requires quotes, * a backslash at the end of an argument that requires quotes, and * a backslash at the end of an argument that does not require quotes.
1 parent 83263b4 commit e90408e

File tree

1 file changed

+21
-18
lines changed

1 file changed

+21
-18
lines changed

src/libstd/sys/windows/process2.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMA
367367

368368
// Produces a wide string *without terminating null*
369369
fn make_command_line(prog: &OsStr, args: &[OsString]) -> Vec<u16> {
370+
// Encode the command and arguments in a command line string such
371+
// that the spawned process may recover them using CommandLineToArgvW.
370372
let mut cmd: Vec<u16> = Vec::new();
371373
append_arg(&mut cmd, prog);
372374
for arg in args {
@@ -387,30 +389,27 @@ fn make_command_line(prog: &OsStr, args: &[OsString]) -> Vec<u16> {
387389
}
388390

389391
let mut iter = arg.encode_wide();
392+
let mut backslashes: usize = 0;
390393
while let Some(x) = iter.next() {
391-
if x == '"' as u16 {
392-
// escape quotes
393-
cmd.push('\\' as u16);
394-
cmd.push('"' as u16);
395-
} else if x == '\\' as u16 {
396-
// is this a run of backslashes followed by a " ?
397-
if iter.clone().skip_while(|y| *y == '\\' as u16).next() == Some('"' as u16) {
398-
// Double it ... NOTE: this behavior is being
399-
// preserved as it's been part of Rust for a long
400-
// time, but no one seems to know exactly why this
401-
// is the right thing to do.
402-
cmd.push('\\' as u16);
403-
cmd.push('\\' as u16);
404-
} else {
405-
// Push it through unescaped
406-
cmd.push('\\' as u16);
407-
}
394+
if x == '\\' as u16 {
395+
backslashes += 1;
408396
} else {
409-
cmd.push(x)
397+
if x == '"' as u16 {
398+
// Add n+1 backslashes to total 2n+1 before internal '"'.
399+
for _ in 0..(backslashes+1) {
400+
cmd.push('\\' as u16);
401+
}
402+
}
403+
backslashes = 0;
410404
}
405+
cmd.push(x);
411406
}
412407

413408
if quote {
409+
// Add n backslashes to total 2n before ending '"'.
410+
for _ in 0..backslashes {
411+
cmd.push('\\' as u16);
412+
}
414413
cmd.push('"' as u16);
415414
}
416415
}
@@ -486,6 +485,10 @@ mod tests {
486485
test_wrapper("echo", &["a b c"]),
487486
"echo \"a b c\""
488487
);
488+
assert_eq!(
489+
test_wrapper("echo", &["\" \\\" \\", "\\"]),
490+
"echo \"\\\" \\\\\\\" \\\\\" \\"
491+
);
489492
assert_eq!(
490493
test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[]),
491494
"\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}"

0 commit comments

Comments
 (0)