use std::ffi::OsStr;
use std::iter;
use std::os::windows::ffi::OsStrExt;
const QUOTE: u16 = b'"' as u16;
const BACKSLASH: u16 = b'\\' as u16;
const SPACE: u16 = b' ' as u16;
const TAB: u16 = b'\t' as u16;
const NEWLINE: u16 = b'\n' as u16;
const RETURN: u16 = b'\r' as u16;
#[must_use]
pub(crate) fn windows_command_line(exe: &OsStr, args: &[String]) -> Vec<u16> {
let mut line = quote_wide(&encode(exe));
for arg in args {
line.push(SPACE);
line.extend(quote_wide(&encode(OsStr::new(arg))));
}
line
}
fn encode(value: &OsStr) -> Vec<u16> {
value.encode_wide().collect()
}
fn quote_wide(arg: &[u16]) -> Vec<u16> {
if arg.is_empty() {
return vec![QUOTE, QUOTE];
}
let needs_quotes = arg
.iter()
.any(|unit| matches!(*unit, SPACE | TAB | NEWLINE | RETURN | QUOTE));
if !needs_quotes {
return arg.to_vec();
}
let mut quoted = vec![QUOTE];
let mut backslashes = 0_usize;
for unit in arg {
if *unit == BACKSLASH {
backslashes = backslashes.saturating_add(1);
continue;
}
if *unit == QUOTE {
quoted.extend(iter::repeat_n(
BACKSLASH,
backslashes.saturating_mul(2).saturating_add(1),
));
quoted.push(QUOTE);
backslashes = 0;
continue;
}
quoted.extend(iter::repeat_n(BACKSLASH, backslashes));
quoted.push(*unit);
backslashes = 0;
}
quoted.extend(iter::repeat_n(BACKSLASH, backslashes.saturating_mul(2)));
quoted.push(QUOTE);
quoted
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use super::*;
fn line(exe: &str, args: &[&str]) -> String {
let args: Vec<String> = args.iter().map(|arg| (*arg).to_string()).collect();
String::from_utf16(&windows_command_line(OsStr::new(exe), &args)).unwrap()
}
#[test]
fn each_element_survives_the_split_that_follows() {
assert_eq!(line("app.exe", &["--foo"]), "app.exe --foo");
assert_eq!(
line(r"C:\Program Files\app.exe", &["a b"]),
r#""C:\Program Files\app.exe" "a b""#
);
}
#[test]
fn empty_and_trailing_backslash_arguments_are_quoted() {
assert_eq!(quote_wide(&[]), vec![QUOTE, QUOTE]);
assert_eq!(
quote_wide(&[SPACE, BACKSLASH]),
vec![QUOTE, SPACE, BACKSLASH, BACKSLASH, QUOTE]
);
assert_eq!(
quote_wide(&[BACKSLASH, QUOTE]),
vec![QUOTE, BACKSLASH, BACKSLASH, BACKSLASH, QUOTE, QUOTE]
);
}
#[test]
fn a_path_windows_gave_us_is_the_path_windows_gets_back() {
let exe = OsString::from_wide(&[
u16::from(b'a'),
0xD800,
u16::from(b'.'),
u16::from(b'e'),
u16::from(b'x'),
u16::from(b'e'),
]);
assert!(
windows_command_line(&exe, &[]).contains(&0xD800),
"the code unit Windows gave us reached the command line"
);
}
}