@@ -137,7 +137,7 @@ fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> io::Result<T> {
137137
138138pub struct Command {
139139 program : OsString ,
140- args : Vec < OsString > ,
140+ args : Vec < Arg > ,
141141 env : CommandEnv ,
142142 cwd : Option < OsString > ,
143143 flags : u32 ,
@@ -161,6 +161,14 @@ pub struct StdioPipes {
161161 pub stderr : Option < AnonPipe > ,
162162}
163163
164+ #[ derive( Debug ) ]
165+ enum Arg {
166+ /// Add quotes (if needed)
167+ Regular ( OsString ) ,
168+ /// Append raw string without quoting
169+ Raw ( OsString ) ,
170+ }
171+
164172impl Command {
165173 pub fn new ( program : & OsStr ) -> Command {
166174 Command {
@@ -178,7 +186,7 @@ impl Command {
178186 }
179187
180188 pub fn arg ( & mut self , arg : & OsStr ) {
181- self . args . push ( arg. to_os_string ( ) )
189+ self . args . push ( Arg :: Regular ( arg. to_os_string ( ) ) )
182190 }
183191 pub fn env_mut ( & mut self ) -> & mut CommandEnv {
184192 & mut self . env
@@ -203,6 +211,10 @@ impl Command {
203211 self . force_quotes_enabled = enabled;
204212 }
205213
214+ pub fn raw_arg ( & mut self , command_str_to_append : & OsStr ) {
215+ self . args . push ( Arg :: Raw ( command_str_to_append. to_os_string ( ) ) )
216+ }
217+
206218 pub fn get_program ( & self ) -> & OsStr {
207219 & self . program
208220 }
@@ -536,44 +548,63 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION {
536548 }
537549}
538550
551+ enum Quote {
552+ // Every arg is quoted
553+ Always ,
554+ // Whitespace and empty args are quoted
555+ Auto ,
556+ // Arg appended without any changes (#29494)
557+ Never ,
558+ }
559+
539560// Produces a wide string *without terminating null*; returns an error if
540561// `prog` or any of the `args` contain a nul.
541- fn make_command_line ( prog : & OsStr , args : & [ OsString ] , force_quotes : bool ) -> io:: Result < Vec < u16 > > {
562+ fn make_command_line ( prog : & OsStr , args : & [ Arg ] , force_quotes : bool ) -> io:: Result < Vec < u16 > > {
542563 // Encode the command and arguments in a command line string such
543564 // that the spawned process may recover them using CommandLineToArgvW.
544565 let mut cmd: Vec < u16 > = Vec :: new ( ) ;
545566 // Always quote the program name so CreateProcess doesn't interpret args as
546567 // part of the name if the binary wasn't found first time.
547- append_arg ( & mut cmd, prog, true ) ?;
568+ append_arg ( & mut cmd, prog, Quote :: Always ) ?;
548569 for arg in args {
549570 cmd. push ( ' ' as u16 ) ;
550- append_arg ( & mut cmd, arg, force_quotes) ?;
571+ let ( arg, quote) = match arg {
572+ Arg :: Regular ( arg) => ( arg, if force_quotes { Quote :: Always } else { Quote :: Auto } ) ,
573+ Arg :: Raw ( arg) => ( arg, Quote :: Never ) ,
574+ } ;
575+ append_arg ( & mut cmd, arg, quote) ?;
551576 }
552577 return Ok ( cmd) ;
553578
554- fn append_arg ( cmd : & mut Vec < u16 > , arg : & OsStr , force_quotes : bool ) -> io:: Result < ( ) > {
579+ fn append_arg ( cmd : & mut Vec < u16 > , arg : & OsStr , quote : Quote ) -> io:: Result < ( ) > {
555580 // If an argument has 0 characters then we need to quote it to ensure
556581 // that it actually gets passed through on the command line or otherwise
557582 // it will be dropped entirely when parsed on the other end.
558583 ensure_no_nuls ( arg) ?;
559584 let arg_bytes = & arg. as_inner ( ) . inner . as_inner ( ) ;
560- let quote = force_quotes
561- || arg_bytes. iter ( ) . any ( |c| * c == b' ' || * c == b'\t' )
562- || arg_bytes. is_empty ( ) ;
585+ let ( quote, escape) = match quote {
586+ Quote :: Always => ( true , true ) ,
587+ Quote :: Auto => {
588+ ( arg_bytes. iter ( ) . any ( |c| * c == b' ' || * c == b'\t' ) || arg_bytes. is_empty ( ) , true )
589+ }
590+ Quote :: Never => ( false , false ) ,
591+ } ;
563592 if quote {
564593 cmd. push ( '"' as u16 ) ;
565594 }
566595
567596 let mut backslashes: usize = 0 ;
568597 for x in arg. encode_wide ( ) {
569- if x == '\\' as u16 {
570- backslashes += 1 ;
571- } else {
572- if x == '"' as u16 {
573- // Add n+1 backslashes to total 2n+1 before internal '"'.
574- cmd. extend ( ( 0 ..=backslashes) . map ( |_| '\\' as u16 ) ) ;
598+ if escape {
599+ if x == '\\' as u16 {
600+ backslashes += 1 ;
601+ } else {
602+ if x == '"' as u16 {
603+ // Add n+1 backslashes to total 2n+1 before internal '"'.
604+ cmd. extend ( ( 0 ..=backslashes) . map ( |_| '\\' as u16 ) ) ;
605+ }
606+ backslashes = 0 ;
575607 }
576- backslashes = 0 ;
577608 }
578609 cmd. push ( x) ;
579610 }
@@ -626,13 +657,15 @@ fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> {
626657}
627658
628659pub struct CommandArgs < ' a > {
629- iter : crate :: slice:: Iter < ' a , OsString > ,
660+ iter : crate :: slice:: Iter < ' a , Arg > ,
630661}
631662
632663impl < ' a > Iterator for CommandArgs < ' a > {
633664 type Item = & ' a OsStr ;
634665 fn next ( & mut self ) -> Option < & ' a OsStr > {
635- self . iter . next ( ) . map ( |s| s. as_ref ( ) )
666+ self . iter . next ( ) . map ( |arg| match arg {
667+ Arg :: Regular ( s) | Arg :: Raw ( s) => s. as_ref ( ) ,
668+ } )
636669 }
637670 fn size_hint ( & self ) -> ( usize , Option < usize > ) {
638671 self . iter . size_hint ( )
0 commit comments