Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use %COMSPEC% as the shell in Process.new(shell: true) #13567

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ describe Process do
it "sets working directory with string" do
parent = File.dirname(Dir.current)
command = {% if flag?(:win32) %}
"cmd.exe /c echo %cd%"
"echo %cd%"
{% else %}
"pwd"
{% end %}
Expand All @@ -202,7 +202,7 @@ describe Process do
it "sets working directory with path" do
parent = Path.new File.dirname(Dir.current)
command = {% if flag?(:win32) %}
"cmd.exe /c echo %cd%"
"echo %cd%"
{% else %}
"pwd"
{% end %}
Expand All @@ -212,30 +212,32 @@ describe Process do
value.should eq "#{parent}#{newline}"
end

pending_win32 "disallows passing arguments to nowhere" do
expect_raises ArgumentError, /args.+@/ do
Process.run("foo bar", {"baz"}, shell: true)
{% unless flag?(:win32) %}
it "disallows passing arguments to nowhere" do
expect_raises ArgumentError, /args.+@/ do
Process.run("foo bar", {"baz"}, shell: true)
end
end
end

pending_win32 "looks up programs in the $PATH with a shell" do
proc = Process.run(*exit_code_command(0), shell: true, output: Process::Redirect::Close)
proc.exit_code.should eq(0)
end
it "looks up programs in the $PATH with a shell" do
proc = Process.run(*exit_code_command(0), shell: true, output: Process::Redirect::Close)
proc.exit_code.should eq(0)
end

pending_win32 "allows passing huge argument lists to a shell" do
proc = Process.new(%(echo "${@}"), {"a", "b"}, shell: true, output: Process::Redirect::Pipe)
output = proc.output.gets_to_end
proc.wait
output.should eq "a b\n"
end
it "allows passing huge argument lists to a shell" do
proc = Process.new(%(echo "${@}"), {"a", "b"}, shell: true, output: Process::Redirect::Pipe)
output = proc.output.gets_to_end
proc.wait
output.should eq "a b\n"
end

pending_win32 "does not run shell code in the argument list" do
proc = Process.new("echo", {"`echo hi`"}, shell: true, output: Process::Redirect::Pipe)
output = proc.output.gets_to_end
proc.wait
output.should eq "`echo hi`\n"
end
it "does not run shell code in the argument list" do
proc = Process.new("echo", {"`echo hi`"}, shell: true, output: Process::Redirect::Pipe)
output = proc.output.gets_to_end
proc.wait
output.should eq "`echo hi`\n"
end
{% end %}

describe "environ" do
it "clears the environment" do
Expand Down
24 changes: 18 additions & 6 deletions src/crystal/system/win32/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ struct Crystal::System::Process
new_handle
end

private CMD_EXE = System.to_wstr(ENV["COMSPEC"])

def self.spawn(command_args, env, clear_env, input, output, error, chdir)
startup_info = LibC::STARTUPINFOW.new
startup_info.cb = sizeof(LibC::STARTUPINFOW)
Expand All @@ -253,13 +255,23 @@ struct Crystal::System::Process

process_info = LibC::PROCESS_INFORMATION.new

command_args = ::Process.quote_windows(command_args) unless command_args.is_a?(String)
if command_args.is_a?(String)
raise ArgumentError.new("Command line contains newline") if command_args.includes?('\n')
result = LibC.CreateProcessW(
CMD_EXE, System.to_wstr(%(/s /c "#{command_args}")), nil, nil, true, LibC::CREATE_UNICODE_ENVIRONMENT,
make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) } || Pointer(UInt16).null,
pointerof(startup_info), pointerof(process_info)
)
else
command_args = ::Process.quote_windows(command_args)
result = LibC.CreateProcessW(
nil, System.to_wstr(command_args), nil, nil, true, LibC::CREATE_UNICODE_ENVIRONMENT,
make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) } || Pointer(UInt16).null,
pointerof(startup_info), pointerof(process_info)
)
end

if LibC.CreateProcessW(
nil, System.to_wstr(command_args), nil, nil, true, LibC::CREATE_UNICODE_ENVIRONMENT,
make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) } || Pointer(UInt16).null,
pointerof(startup_info), pointerof(process_info)
) == 0
if result == 0
error = WinError.value
case error.to_errno
when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC
Expand Down
6 changes: 4 additions & 2 deletions src/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,10 @@ class Process
# * On POSIX this uses `/bin/sh` to process the command string. *args* are
# also passed to the shell, and you need to include the string `"${@}"` in
# the *command* to safely insert them there.
# * On Windows this is implemented by passing the string as-is to the
# process, and passing *args* is not supported.
# * On Windows this is implemented by passing the string as-is to `%COMSPEC%`,
# and passing *args* is not supported. The user is responsible for correctly
# quoting the command string, as `Process.quote_windows` does not handle
# this. Additionally, *command* cannot contain the newline character (`\n`).
#
# Raises `IO::Error` if executing the command fails (for example if the executable doesn't exist).
def initialize(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false,
Expand Down