#[cfg(test)]
mod tests;
use crate::ffi::OsString;
use crate::fmt;
use crate::io;
use crate::num::NonZeroU16;
use crate::os::windows::prelude::*;
use crate::path::{Path, PathBuf};
use crate::sys::path::get_long_path;
use crate::sys::process::ensure_no_nuls;
use crate::sys::windows::os::current_exe;
use crate::sys::{c, to_u16s};
use crate::sys_common::wstr::WStrUnits;
use crate::vec;
use crate::iter;
const fn non_zero_u16(n: u16) -> NonZeroU16 {
match NonZeroU16::new(n) {
Some(n) => n,
None => panic!("called `unwrap` on a `None` value"),
}
}
pub fn args() -> Args {
unsafe {
let lp_cmd_line = c::GetCommandLineW();
let parsed_args_list = parse_lp_cmd_line(WStrUnits::new(lp_cmd_line), || {
current_exe().map(PathBuf::into_os_string).unwrap_or_else(|_| OsString::new())
});
Args { parsed_args_list: parsed_args_list.into_iter() }
}
}
fn parse_lp_cmd_line<'a, F: Fn() -> OsString>(
lp_cmd_line: Option<WStrUnits<'a>>,
exe_name: F,
) -> Vec<OsString> {
const BACKSLASH: NonZeroU16 = non_zero_u16(b'\\' as u16);
const QUOTE: NonZeroU16 = non_zero_u16(b'"' as u16);
const TAB: NonZeroU16 = non_zero_u16(b'\t' as u16);
const SPACE: NonZeroU16 = non_zero_u16(b' ' as u16);
let mut ret_val = Vec::new();
if lp_cmd_line.as_ref().and_then(|cmd| cmd.peek()).is_none() {
ret_val.push(exe_name());
return ret_val;
}
let mut code_units = lp_cmd_line.unwrap();
let mut in_quotes = false;
let mut cur = Vec::new();
for w in &mut code_units {
match w {
QUOTE => in_quotes = !in_quotes,
SPACE | TAB if !in_quotes => break,
_ => cur.push(w.get()),
}
}
code_units.advance_while(|w| w == SPACE || w == TAB);
ret_val.push(OsString::from_wide(&cur));
let mut cur = Vec::new();
let mut in_quotes = false;
while let Some(w) = code_units.next() {
match w {
SPACE | TAB if !in_quotes => {
ret_val.push(OsString::from_wide(&cur[..]));
cur.truncate(0);
code_units.advance_while(|w| w == SPACE || w == TAB);
}
BACKSLASH => {
let backslash_count = code_units.advance_while(|w| w == BACKSLASH) + 1;
if code_units.peek() == Some(QUOTE) {
cur.extend(iter::repeat(BACKSLASH.get()).take(backslash_count / 2));
if backslash_count % 2 == 1 {
code_units.next();
cur.push(QUOTE.get());
}
} else {
cur.extend(iter::repeat(BACKSLASH.get()).take(backslash_count));
}
}
QUOTE if in_quotes => match code_units.peek() {
Some(QUOTE) => {
cur.push(QUOTE.get());
code_units.next();
}
Some(_) => in_quotes = false,
None => break,
},
QUOTE => in_quotes = true,
_ => cur.push(w.get()),
}
}
if !cur.is_empty() || in_quotes {
ret_val.push(OsString::from_wide(&cur[..]));
}
ret_val
}
pub struct Args {
parsed_args_list: vec::IntoIter<OsString>,
}
impl fmt::Debug for Args {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.parsed_args_list.as_slice().fmt(f)
}
}
impl Iterator for Args {
type Item = OsString;
fn next(&mut self) -> Option<OsString> {
self.parsed_args_list.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.parsed_args_list.size_hint()
}
}
impl DoubleEndedIterator for Args {
fn next_back(&mut self) -> Option<OsString> {
self.parsed_args_list.next_back()
}
}
impl ExactSizeIterator for Args {
fn len(&self) -> usize {
self.parsed_args_list.len()
}
}
#[derive(Debug)]
pub(crate) enum Arg {
Regular(OsString),
Raw(OsString),
}
enum Quote {
Always,
Auto,
Never,
}
pub(crate) fn append_arg(cmd: &mut Vec<u16>, arg: &Arg, force_quotes: bool) -> io::Result<()> {
let (arg, quote) = match arg {
Arg::Regular(arg) => (arg, if force_quotes { Quote::Always } else { Quote::Auto }),
Arg::Raw(arg) => (arg, Quote::Never),
};
ensure_no_nuls(arg)?;
let arg_bytes = arg.bytes();
let (quote, escape) = match quote {
Quote::Always => (true, true),
Quote::Auto => {
(arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty(), true)
}
Quote::Never => (false, false),
};
if quote {
cmd.push('"' as u16);
}
let mut backslashes: usize = 0;
for x in arg.encode_wide() {
if escape {
if x == '\\' as u16 {
backslashes += 1;
} else {
if x == '"' as u16 {
cmd.extend((0..=backslashes).map(|_| '\\' as u16));
}
backslashes = 0;
}
}
cmd.push(x);
}
if quote {
cmd.extend((0..backslashes).map(|_| '\\' as u16));
cmd.push('"' as u16);
}
Ok(())
}
pub(crate) fn make_bat_command_line(
script: &[u16],
args: &[Arg],
force_quotes: bool,
) -> io::Result<Vec<u16>> {
let mut cmd: Vec<u16> = "cmd.exe /d /c \"".encode_utf16().collect();
cmd.push(b'"' as u16);
if script.contains(&(b'"' as u16)) || script.last() == Some(&(b'\\' as u16)) {
return Err(io::const_io_error!(
io::ErrorKind::InvalidInput,
"Windows file names may not contain `\"` or end with `\\`"
));
}
cmd.extend_from_slice(script.strip_suffix(&[0]).unwrap_or(script));
cmd.push(b'"' as u16);
for arg in args {
cmd.push(' ' as u16);
const SPECIAL: &[u8] = b"\t &()[]{}^=;!'+,`~%|<>";
let force_quotes = match arg {
Arg::Regular(arg) if !force_quotes => arg.bytes().iter().any(|c| SPECIAL.contains(c)),
_ => force_quotes,
};
append_arg(&mut cmd, arg, force_quotes)?;
}
cmd.push(b'"' as u16);
Ok(cmd)
}
pub(crate) fn to_user_path(path: &Path) -> io::Result<Vec<u16>> {
use crate::ptr;
use crate::sys::windows::fill_utf16_buf;
const SEP: u16 = b'\\' as _;
const QUERY: u16 = b'?' as _;
const COLON: u16 = b':' as _;
const U: u16 = b'U' as _;
const N: u16 = b'N' as _;
const C: u16 = b'C' as _;
let mut path = to_u16s(path)?;
const LEGACY_MAX_PATH: usize = 260;
if path.len() > LEGACY_MAX_PATH {
return Ok(path);
}
match &path[..] {
[SEP, SEP, QUERY, SEP, _, COLON, SEP, ..] => unsafe {
let lpfilename = path[4..].as_ptr();
fill_utf16_buf(
|buffer, size| c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()),
|full_path: &[u16]| {
if full_path == &path[4..path.len() - 1] {
let mut path: Vec<u16> = full_path.into();
path.push(0);
path
} else {
path
}
},
)
},
[SEP, SEP, QUERY, SEP, U, N, C, SEP, ..] => unsafe {
path[6] = b'\\' as u16;
let lpfilename = path[6..].as_ptr();
fill_utf16_buf(
|buffer, size| c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()),
|full_path: &[u16]| {
if full_path == &path[6..path.len() - 1] {
let mut path: Vec<u16> = full_path.into();
path.push(0);
path
} else {
path[6] = b'C' as u16;
path
}
},
)
},
_ => get_long_path(path, false),
}
}