@@ -256,19 +256,68 @@ shell_escape_posixly(args::AbstractString...) =
256256 shell_escape_wincmd(s::AbstractString)
257257 shell_escape_wincmd(io::IO, s::AbstractString)
258258
259- The unexported `shell_escape_wincmd` function escapes Windows
260- `cmd.exe` shell meta characters. It escapes `()!^<>&|` by placing a
261- `^` in front. An `@` is only escaped at the start of the string. Pairs
262- of `"` characters and the strings they enclose are passed through
263- unescaped. Any remaining `"` is escaped with `^` to ensure that the
264- number of unescaped `"` characters in the result remains even.
265-
266- Since `cmd.exe` substitutes variable references (like `%USER%`)
267- _before_ processing the escape characters `^` and `"`, this function
268- makes no attempt to escape the percent sign (`%`).
269-
270- Input strings with ASCII control characters that cannot be escaped
271- (NUL, CR, LF) will cause an `ArgumentError` exception.
259+ The unexported `shell_escape_wincmd` function escapes Windows `cmd.exe` shell
260+ meta characters. It escapes `()!^<>&|` by placing a `^` in front. An `@` is
261+ only escaped at the start of the string. Pairs of `"` characters and the
262+ strings they enclose are passed through unescaped. Any remaining `"` is escaped
263+ with `^` to ensure that the number of unescaped `"` characters in the result
264+ remains even.
265+
266+ Since `cmd.exe` substitutes variable references (like `%USER%`) _before_
267+ processing the escape characters `^` and `"`, this function makes no attempt to
268+ escape the percent sign (`%`), the presence of `%` in the input may cause
269+ severe breakage, depending on where the result is used.
270+
271+ Input strings with ASCII control characters that cannot be escaped (NUL, CR,
272+ LF) will cause an `ArgumentError` exception.
273+
274+ The result is safe to pass as an argument to a command call being processed by
275+ `CMD.exe /S /C " ... "` (with surrounding double-quote pair) and will be
276+ received verbatim by the target application if the input does not contain `%`
277+ (else this function will fail with an ArgumentError). The presence of `%` in
278+ the input string may result in command injection vulnerabilities and may
279+ invalidate any claim of suitability of the output of this function for use as
280+ an argument to cmd (due to the ordering described above), so use caution when
281+ assembling a string from various sources.
282+
283+ This function may be useful in concert with the `windows_verbatim` flag to
284+ [`Cmd`](@ref) when constructing process pipelines.
285+
286+ ```julia
287+ wincmd(c::String) =
288+ run(Cmd(Cmd(["cmd.exe", "/s /c \" \$ c \" "]);
289+ windows_verbatim=true))
290+ wincmd_echo(s::String) =
291+ wincmd("echo " * Base.shell_escape_wincmd(s))
292+ wincmd_echo("hello \$ (ENV["USER"]) & the \" whole\" world! (=^I^=)")
293+ ```
294+
295+ But take head that if the input string `s` contains a `%`, the argument list
296+ and echo'ed text may get corrupted, resulting in arbitrary command execution.
297+ The argument can alternatively be passed as an environment variable, which
298+ avoids the problem with `%` and the need for the `windows_verbatim` flag:
299+
300+ ```julia
301+ cmdargs = Base.shell_escape_wincmd("Passing args with %cmdargs% works 100%!")
302+ run(setenv(`cmd /C echo %cmdargs%`, "cmdargs" => cmdargs))
303+ ```
304+
305+ !warning
306+ The argument parsing done by CMD when calling batch files (either inside
307+ `.bat` files or as arguments to them) is not fully compatible with the
308+ output of this function. In particular, the processing of `%` is different.
309+
310+ !important
311+ Due to a peculiar behavior of the CMD parser/interpreter, each command
312+ after a literal `|` character (indicating a command pipeline) must have
313+ `shell_escape_wincmd` applied twice since it will be parsed twice by CMD.
314+ This implies ENV variables would also be expanded twice!
315+ For example:
316+ ```julia
317+ to_print = "All for 1 & 1 for all!"
318+ to_print_esc = Base.shell_escape_wincmd(Base.shell_escape_wincmd(to_print))
319+ run(Cmd(Cmd(["cmd", "/S /C \" break | echo \$ (to_print_esc) \" "]), windows_verbatim=true))
320+ ```
272321
273322With an I/O stream parameter `io`, the result will be written there,
274323rather than returned as a string.
0 commit comments