mirror of
https://github.com/ruby/ruby.git
synced 2026-08-03 13:08:59 +08:00
[ruby/rubygems] Unquote Gem.ruby when spawning it as a separate argv element
Gem.ruby wraps the interpreter path in double quotes when it contains whitespace. That suits single-string shell commands but breaks the multi-argument spawns that pass it as its own argv element. Under an install prefix containing a space, `gem update --system`, mkrf extension builds, `gem push` attestation, and the bundler auto-switch all fail to spawn with exit 127. Split it with Shellwords, matching the existing `Gem::Ext::Builder.ruby` idiom. https://github.com/ruby/rubygems/commit/00e189e851 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
290136b6df
commit
e5bf3bbc81
@ -75,7 +75,12 @@ module Bundler
|
||||
|
||||
argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0
|
||||
cmd = [argv0, *ARGV]
|
||||
cmd.unshift(Gem.ruby) unless File.executable?(argv0)
|
||||
unless File.executable?(argv0)
|
||||
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
||||
# elements to keep the quotes out of the exec'd command.
|
||||
require "shellwords"
|
||||
cmd.unshift(*Shellwords.split(Gem.ruby))
|
||||
end
|
||||
|
||||
Bundler.with_original_env do
|
||||
Kernel.exec(
|
||||
|
||||
@ -147,6 +147,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo
|
||||
|
||||
def attest!(name)
|
||||
require "open3"
|
||||
require "shellwords"
|
||||
require "tempfile"
|
||||
|
||||
tempfile = Tempfile.new([File.basename(name, ".*"), ".sigstore.json"])
|
||||
@ -154,9 +155,11 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo
|
||||
tempfile.close(false)
|
||||
|
||||
env = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h
|
||||
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
||||
# elements to keep the quotes out of the spawned command.
|
||||
out, st = Open3.capture2e(
|
||||
env,
|
||||
Gem.ruby, "-S", "gem", "exec", "--conservative",
|
||||
*Shellwords.split(Gem.ruby), "-S", "gem", "exec", "--conservative",
|
||||
"sigstore-cli", "sign", name, "--bundle", bundle,
|
||||
unsetenv_others: true
|
||||
)
|
||||
|
||||
@ -183,7 +183,10 @@ command to remove old versions.
|
||||
say "Installing RubyGems #{version}" unless options[:silent]
|
||||
|
||||
installed = preparing_gem_layout_for(version) do
|
||||
system Gem.ruby, "--disable-gems", "setup.rb", *args
|
||||
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
||||
# elements to keep the quotes out of the spawned command.
|
||||
require "shellwords"
|
||||
system(*Shellwords.split(Gem.ruby), "--disable-gems", "setup.rb", *args)
|
||||
end
|
||||
|
||||
unless options[:silent]
|
||||
|
||||
@ -14,7 +14,9 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder
|
||||
end
|
||||
|
||||
if /mkrf_conf/i.match?(File.basename(extension))
|
||||
run([Gem.ruby, File.basename(extension), *args], results, class_name, extension_dir)
|
||||
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
||||
# elements to keep the quotes out of the spawned command.
|
||||
run([*shellsplit(Gem.ruby), File.basename(extension), *args], results, class_name, extension_dir)
|
||||
end
|
||||
|
||||
rake = ENV["rake"]
|
||||
|
||||
29
spec/bundler/bundler/self_manager_spec.rb
Normal file
29
spec/bundler/bundler/self_manager_spec.rb
Normal file
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Bundler::SelfManager do
|
||||
describe "#restart_with" do
|
||||
# When the running Ruby lives under a path containing whitespace, Gem.ruby
|
||||
# returns a quoted string. That quoting must not leak into the argv passed
|
||||
# to Kernel.exec, or the interpreter can't be found and the auto-switch to
|
||||
# the locked bundler version fails to spawn.
|
||||
it "does not embed quotes in the ruby executable when Gem.ruby is quoted" do
|
||||
manager = described_class.new
|
||||
|
||||
allow(Gem).to receive(:ruby).and_return('"/path with space/bin/ruby"')
|
||||
|
||||
# Force the branch that prepends Gem.ruby to the command.
|
||||
allow(File).to receive(:executable?).and_return(false)
|
||||
allow(Bundler).to receive(:with_original_env).and_yield
|
||||
|
||||
captured = nil
|
||||
allow(Kernel).to receive(:exec) do |_env, *cmd|
|
||||
captured = cmd
|
||||
end
|
||||
|
||||
manager.send(:restart_with, Gem::Version.new("1.0.0"))
|
||||
|
||||
expect(captured.first).to eq("/path with space/bin/ruby")
|
||||
expect(captured.first).not_to include('"')
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -219,6 +219,36 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
# When the running Ruby lives under a path containing whitespace, Gem.ruby
|
||||
# returns a quoted string. That quoting must not leak into the argv passed to
|
||||
# Open3.capture2e, or signing spawns fail and push silently falls back to an
|
||||
# unsigned upload.
|
||||
def test_attest_unquotes_ruby
|
||||
require "open3"
|
||||
|
||||
fake_status = Object.new
|
||||
def fake_status.success?
|
||||
true
|
||||
end
|
||||
|
||||
captured = nil
|
||||
capture_stub = lambda do |*args, **_kwargs|
|
||||
captured = args
|
||||
["", fake_status]
|
||||
end
|
||||
Gem.stub(:ruby, '"/path with space/bin/ruby"') do
|
||||
Open3.stub(:capture2e, capture_stub) do
|
||||
@cmd.send(:attest!, @path)
|
||||
end
|
||||
end
|
||||
|
||||
refute_nil captured, "signing command should have been spawned"
|
||||
# captured[0] is the env hash; the interpreter follows it.
|
||||
assert_equal "/path with space/bin/ruby", captured[1]
|
||||
refute_includes captured[1], '"'
|
||||
assert_equal "-S", captured[2]
|
||||
end
|
||||
|
||||
def test_execute_allowed_push_host
|
||||
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
||||
spec.metadata["allowed_push_host"] = "https://privategemserver.example"
|
||||
|
||||
@ -816,6 +816,36 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
|
||||
assert_empty @cmd.updated
|
||||
end
|
||||
|
||||
# When the running Ruby lives under a path containing whitespace, Gem.ruby
|
||||
# returns a quoted string. That quoting must not leak into the argv passed to
|
||||
# the non-shell `system` call that runs setup.rb, or the interpreter can't be
|
||||
# found and `gem update --system` fails to spawn.
|
||||
def test_install_rubygems_unquotes_ruby
|
||||
version = Gem::Version.new("99.0.0")
|
||||
update_dir = File.join @tempdir, "gems", "rubygems-update-#{version}"
|
||||
FileUtils.mkdir_p update_dir
|
||||
|
||||
spec = Struct.new(:version, :base_dir).new(version, @tempdir)
|
||||
|
||||
captured = nil
|
||||
@cmd.define_singleton_method(:system) do |*args|
|
||||
captured = args
|
||||
true
|
||||
end
|
||||
|
||||
@cmd.options[:silent] = true
|
||||
|
||||
Gem.stub(:ruby, '"/path with space/bin/ruby"') do
|
||||
@cmd.install_rubygems(spec)
|
||||
end
|
||||
|
||||
refute_nil captured, "setup.rb should have been spawned"
|
||||
assert_equal "/path with space/bin/ruby", captured.first
|
||||
refute_includes captured.first, '"'
|
||||
assert_includes captured, "--disable-gems"
|
||||
assert_includes captured, "setup.rb"
|
||||
end
|
||||
|
||||
def test_update_rubygems_arguments
|
||||
@cmd.options[:system] = true
|
||||
|
||||
|
||||
@ -101,6 +101,24 @@ class TestGemExtRakeBuilder < Gem::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
# When the running Ruby lives under a path containing whitespace, Gem.ruby
|
||||
# returns a quoted string. That quoting must not leak into the argv passed to
|
||||
# the non-shell spawn that runs mkrf_conf.rb, or the interpreter can't be found.
|
||||
def test_class_build_mkrf_conf_unquotes_ruby
|
||||
commands = []
|
||||
|
||||
Gem.stub(:ruby, '"/path with space/bin/ruby"') do
|
||||
Gem::Ext::RakeBuilder.stub(:run, ->(command, *) { commands << command }) do
|
||||
Gem::Ext::RakeBuilder.build "mkrf_conf.rb", @dest_path, [], [], nil, @ext
|
||||
end
|
||||
end
|
||||
|
||||
mkrf_command = commands.find {|command| command.include?("mkrf_conf.rb") }
|
||||
refute_nil mkrf_command, "mkrf_conf.rb should have been spawned"
|
||||
assert_equal "/path with space/bin/ruby", mkrf_command.first
|
||||
refute_includes mkrf_command.first, '"'
|
||||
end
|
||||
|
||||
def create_temp_mkrf_file(rakefile_content)
|
||||
File.open File.join(@ext, "mkrf_conf.rb"), "w" do |mkrf_conf|
|
||||
mkrf_conf.puts <<-EO_MKRF
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user