summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2021-11-11 11:00:30 +0900
committernagachika <nagachika@ruby-lang.org>2021-11-22 10:51:35 +0900
commite27381d289cbdbdca434bcc957c2cd1beab1c82c (patch)
treec50fe7f308ee10afc2e797b2f4760acc49ea64fc /lib
parente262272b6a50c1a92cdcfee684e82f9242ef8171 (diff)
Merge RubyGems 3.2.30 and Bundler 2.2.30
Diffstat (limited to 'lib')
-rw-r--r--lib/bundler.rb3
-rw-r--r--lib/bundler/cli/info.rb15
-rw-r--r--lib/bundler/cli/issue.rb7
-rw-r--r--lib/bundler/compact_index_client.rb4
-rw-r--r--lib/bundler/definition.rb16
-rw-r--r--lib/bundler/digest.rb71
-rw-r--r--lib/bundler/errors.rb20
-rw-r--r--lib/bundler/fetcher.rb3
-rw-r--r--lib/bundler/friendly_errors.rb35
-rw-r--r--lib/bundler/gem_helper.rb21
-rw-r--r--lib/bundler/rubygems_ext.rb4
-rw-r--r--lib/bundler/rubygems_gem_installer.rb24
-rw-r--r--lib/bundler/rubygems_integration.rb35
-rw-r--r--lib/bundler/runtime.rb2
-rw-r--r--lib/bundler/source/git.rb4
-rw-r--r--lib/bundler/source/rubygems.rb106
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool.rb113
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb66
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb40
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb2
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb57
-rw-r--r--lib/bundler/version.rb2
-rw-r--r--lib/bundler/worker.rb4
-rw-r--r--lib/rubygems.rb4
-rw-r--r--lib/rubygems/commands/cert_command.rb19
-rw-r--r--lib/rubygems/commands/setup_command.rb2
-rw-r--r--lib/rubygems/core_ext/tcpsocket_init.rb4
-rw-r--r--lib/rubygems/installer.rb2
-rw-r--r--lib/rubygems/package.rb58
-rw-r--r--lib/rubygems/query_utils.rb8
-rw-r--r--lib/rubygems/remote_fetcher.rb4
-rw-r--r--lib/rubygems/request.rb2
-rw-r--r--lib/rubygems/request/connection_pools.rb2
-rw-r--r--lib/rubygems/request/http_pool.rb2
-rw-r--r--lib/rubygems/request_set.rb2
-rw-r--r--lib/rubygems/s3_uri_signer.rb9
-rw-r--r--lib/rubygems/security.rb64
-rw-r--r--lib/rubygems/security/policy.rb8
-rw-r--r--lib/rubygems/security/signer.rb7
-rw-r--r--lib/rubygems/source.rb2
-rw-r--r--lib/rubygems/source/git.rb4
-rw-r--r--lib/rubygems/specification.rb2
-rw-r--r--lib/rubygems/user_interaction.rb2
-rw-r--r--lib/rubygems/util.rb2
44 files changed, 459 insertions, 404 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb
index a7676cd497..1ab87ac299 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -37,12 +37,13 @@ module Bundler
environment_preserver = EnvironmentPreserver.from_env
ORIGINAL_ENV = environment_preserver.restore
environment_preserver.replace_with_backup
- SUDO_MUTEX = Mutex.new
+ SUDO_MUTEX = Thread::Mutex.new
autoload :Definition, File.expand_path("bundler/definition", __dir__)
autoload :Dependency, File.expand_path("bundler/dependency", __dir__)
autoload :DepProxy, File.expand_path("bundler/dep_proxy", __dir__)
autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__)
+ autoload :Digest, File.expand_path("bundler/digest", __dir__)
autoload :Dsl, File.expand_path("bundler/dsl", __dir__)
autoload :EndpointSpecification, File.expand_path("bundler/endpoint_specification", __dir__)
autoload :Env, File.expand_path("bundler/env", __dir__)
diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb
index 3111b64a33..3afed89ceb 100644
--- a/lib/bundler/cli/info.rb
+++ b/lib/bundler/cli/info.rb
@@ -40,12 +40,13 @@ module Bundler
end
def print_gem_path(spec)
- if spec.name == "bundler"
+ name = spec.name
+ if name == "bundler"
path = File.expand_path("../../../..", __FILE__)
else
path = spec.full_gem_path
- unless File.directory?(path)
- return Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at: #{path}"
+ if spec.deleted_gem?
+ return Bundler.ui.warn "The gem #{name} has been deleted. It was installed at: #{path}"
end
end
@@ -54,8 +55,9 @@ module Bundler
def print_gem_info(spec)
metadata = spec.metadata
+ name = spec.name
gem_info = String.new
- gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n"
+ gem_info << " * #{name} (#{spec.version}#{spec.git_version})\n"
gem_info << "\tSummary: #{spec.summary}\n" if spec.summary
gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage
gem_info << "\tDocumentation: #{metadata["documentation_uri"]}\n" if metadata.key?("documentation_uri")
@@ -67,6 +69,11 @@ module Bundler
gem_info << "\tMailing List: #{metadata["mailing_list_uri"]}\n" if metadata.key?("mailing_list_uri")
gem_info << "\tPath: #{spec.full_gem_path}\n"
gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem?
+
+ if spec.deleted_gem?
+ return Bundler.ui.warn "The gem #{name} has been deleted. Gemspec information is still available though:\n#{gem_info}"
+ end
+
Bundler.ui.info gem_info
end
end
diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb
index f4cd5ac4df..b891ecb1d2 100644
--- a/lib/bundler/cli/issue.rb
+++ b/lib/bundler/cli/issue.rb
@@ -20,9 +20,10 @@ module Bundler
Hopefully the troubleshooting steps above resolved your problem! If things
still aren't working the way you expect them to, please let us know so
- that we can diagnose and help fix the problem you're having. Please
- view the Filing Issues guide for more information:
- https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md
+ that we can diagnose and help fix the problem you're having, by filling
+ in the new issue form located at
+ https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md,
+ and copy and pasting the information below.
EOS
diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb
index cf67f0e7a0..d5dbeb3b10 100644
--- a/lib/bundler/compact_index_client.rb
+++ b/lib/bundler/compact_index_client.rb
@@ -5,7 +5,7 @@ require "set"
module Bundler
class CompactIndexClient
- DEBUG_MUTEX = Mutex.new
+ DEBUG_MUTEX = Thread::Mutex.new
def self.debug
return unless ENV["DEBUG_COMPACT_INDEX"]
DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
@@ -25,7 +25,7 @@ module Bundler
@endpoints = Set.new
@info_checksums_by_name = {}
@parsed_checksums = false
- @mutex = Mutex.new
+ @mutex = Thread::Mutex.new
end
def execution_mode=(block)
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 4a27c438f5..9736d6551b 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -73,7 +73,6 @@ module Bundler
@lockfile_contents = String.new
@locked_bundler_version = nil
@locked_ruby_version = nil
- @locked_specs_incomplete_for_platform = false
@new_platform = nil
if lockfile && File.exist?(lockfile)
@@ -139,6 +138,8 @@ module Bundler
@dependency_changes = converge_dependencies
@local_changes = converge_locals
+ @locked_specs_incomplete_for_platform = !@locked_specs.for(expand_dependencies(requested_dependencies & locked_dependencies), true, true)
+
@requires = compute_requires
end
@@ -228,6 +229,10 @@ module Bundler
end
end
+ def locked_dependencies
+ @locked_deps.values
+ end
+
def specs_for(groups)
groups = requested_groups if groups.empty?
deps = dependencies_for(groups)
@@ -367,8 +372,8 @@ module Bundler
new_sources = gemfile_sources - @locked_sources
deleted_sources = @locked_sources - gemfile_sources
- new_deps = @dependencies - @locked_deps.values
- deleted_deps = @locked_deps.values - @dependencies
+ new_deps = @dependencies - locked_dependencies
+ deleted_deps = locked_dependencies - @dependencies
# Check if it is possible that the source is only changed thing
if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
@@ -560,7 +565,7 @@ module Bundler
def dependencies_for_source_changed?(source, locked_source = source)
deps_for_source = @dependencies.select {|s| s.source == source }
- locked_deps_for_source = @locked_deps.values.select {|dep| dep.source == locked_source }
+ locked_deps_for_source = locked_dependencies.select {|dep| dep.source == locked_source }
deps_for_source.uniq.sort != locked_deps_for_source.sort
end
@@ -644,7 +649,7 @@ module Bundler
def converge_dependencies
frozen = Bundler.frozen_bundle?
- (@dependencies + @locked_deps.values).each do |dep|
+ (@dependencies + locked_dependencies).each do |dep|
locked_source = @locked_deps[dep.name]
# This is to make sure that if bundler is installing in deployment mode and
# after locked_source and sources don't match, we still use locked_source.
@@ -751,7 +756,6 @@ module Bundler
end
resolve = SpecSet.new(converged)
- @locked_specs_incomplete_for_platform = !resolve.for(expand_dependencies(requested_dependencies & deps), true, true)
resolve = SpecSet.new(resolve.for(expand_dependencies(deps, true), false, false).reject{|s| @unlock[:gems].include?(s.name) })
diff = nil
diff --git a/lib/bundler/digest.rb b/lib/bundler/digest.rb
new file mode 100644
index 0000000000..759f609416
--- /dev/null
+++ b/lib/bundler/digest.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+# This code was extracted from https://github.com/Solistra/ruby-digest which is under public domain
+module Bundler
+ module Digest
+ # The initial constant values for the 32-bit constant words A, B, C, D, and
+ # E, respectively.
+ SHA1_WORDS = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0].freeze
+
+ # The 8-bit field used for bitwise `AND` masking. Defaults to `0xFFFFFFFF`.
+ SHA1_MASK = 0xFFFFFFFF
+
+ class << self
+ def sha1(string)
+ unless string.is_a?(String)
+ raise TypeError, "can't convert #{string.class.inspect} into String"
+ end
+
+ buffer = string.b
+
+ words = SHA1_WORDS.dup
+ generate_split_buffer(buffer) do |chunk|
+ w = []
+ chunk.each_slice(4) do |a, b, c, d|
+ w << (((a << 8 | b) << 8 | c) << 8 | d)
+ end
+ a, b, c, d, e = *words
+ (16..79).each do |i|
+ w[i] = SHA1_MASK & rotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1)
+ end
+ 0.upto(79) do |i|
+ case i
+ when 0..19
+ f = ((b & c) | (~b & d))
+ k = 0x5A827999
+ when 20..39
+ f = (b ^ c ^ d)
+ k = 0x6ED9EBA1
+ when 40..59
+ f = ((b & c) | (b & d) | (c & d))
+ k = 0x8F1BBCDC
+ when 60..79
+ f = (b ^ c ^ d)
+ k = 0xCA62C1D6
+ end
+ t = SHA1_MASK & (SHA1_MASK & rotate(a, 5) + f + e + k + w[i])
+ a, b, c, d, e = t, a, SHA1_MASK & rotate(b, 30), c, d # rubocop:disable Style/ParallelAssignment
+ end
+ mutated = [a, b, c, d, e]
+ words.map!.with_index {|word, index| SHA1_MASK & (word + mutated[index]) }
+ end
+
+ words.pack("N*").unpack("H*").first
+ end
+
+ private
+
+ def generate_split_buffer(string, &block)
+ size = string.bytesize * 8
+ buffer = string.bytes << 128
+ buffer << 0 while buffer.size % 64 != 56
+ buffer.concat([size].pack("Q>").bytes)
+ buffer.each_slice(64, &block)
+ end
+
+ def rotate(value, spaces)
+ value << spaces | value >> (32 - spaces)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb
index 565aaeeb9d..9ad7460e58 100644
--- a/lib/bundler/errors.rb
+++ b/lib/bundler/errors.rb
@@ -75,10 +75,26 @@ module Bundler
end
end
+ def permission_type
+ case @permission_type
+ when :create
+ "executable permissions for all parent directories and write permissions for `#{parent_folder}`"
+ when :delete
+ permissions = "executable permissions for all parent directories and write permissions for `#{parent_folder}`"
+ permissions += ", and the same thing for all subdirectories inside #{@path}" if File.directory?(@path)
+ permissions
+ else
+ "#{@permission_type} permissions for that path"
+ end
+ end
+
+ def parent_folder
+ File.dirname(@path)
+ end
+
def message
"There was an error while trying to #{action} `#{@path}`. " \
- "It is likely that you need to grant #{@permission_type} permissions " \
- "for that path."
+ "It is likely that you need to grant #{permission_type}."
end
status_code(23)
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
index 2bbe53aa25..e3253a942f 100644
--- a/lib/bundler/fetcher.rb
+++ b/lib/bundler/fetcher.rb
@@ -28,7 +28,8 @@ module Bundler
" is a chance you are experiencing a man-in-the-middle attack, but" \
" most likely your system doesn't have the CA certificates needed" \
" for verification. For information about OpenSSL certificates, see" \
- " http://bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \
+ " https://railsapps.github.io/openssl-certificate-verify-failed.html." \
+ " To connect without using SSL, edit your Gemfile" \
" sources and change 'https' to 'http'."
end
end
diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb
index db43e0f654..cc615db60c 100644
--- a/lib/bundler/friendly_errors.rb
+++ b/lib/bundler/friendly_errors.rb
@@ -63,34 +63,6 @@ module Bundler
def request_issue_report_for(e)
Bundler.ui.error <<-EOS.gsub(/^ {8}/, ""), nil, nil
--- ERROR REPORT TEMPLATE -------------------------------------------------------
- # Error Report
-
- ## Questions
-
- Please fill out answers to these questions, it'll help us figure out
- why things are going wrong.
-
- - **What did you do?**
-
- I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}`
-
- - **What did you expect to happen?**
-
- I expected Bundler to...
-
- - **What happened instead?**
-
- Instead, what happened was...
-
- - **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?**
-
- I tried...
-
- - **Have you read our issues document, https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md?**
-
- ...
-
- ## Backtrace
```
#{e.class}: #{e.message}
@@ -109,8 +81,7 @@ module Bundler
First, try this link to see if there are any existing issue reports for this error:
#{issues_url(e)}
- If there aren't any reports for this error yet, please copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at:
- https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md
+ If there aren't any reports for this error yet, please fill in the new issue form located at #{new_issue_url}, and copy and paste the report template above in there.
EOS
end
@@ -121,6 +92,10 @@ module Bundler
"https://github.com/rubygems/rubygems/search?q=" \
"#{CGI.escape(message)}&type=Issues"
end
+
+ def new_issue_url
+ "https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md"
+ end
end
def self.with_friendly_errors
diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb
index 8e1dd7c4f2..60b9e57887 100644
--- a/lib/bundler/gem_helper.rb
+++ b/lib/bundler/gem_helper.rb
@@ -98,10 +98,7 @@ module Bundler
built_gem_path ||= build_gem
cmd = [*gem_command, "install", built_gem_path.to_s]
cmd << "--local" if local
- _, status = sh_with_status(cmd)
- unless status.success?
- raise "Couldn't install gem, run `gem install #{built_gem_path}' for more detailed output"
- end
+ sh(cmd)
Bundler.ui.confirm "#{name} (#{version}) installed."
end
@@ -110,7 +107,7 @@ module Bundler
SharedHelpers.filesystem_access(File.join(base, "checksums")) {|p| FileUtils.mkdir_p(p) }
file_name = "#{File.basename(built_gem_path)}.sha512"
require "digest/sha2"
- checksum = Digest::SHA512.new.hexdigest(built_gem_path.to_s)
+ checksum = ::Digest::SHA512.new.hexdigest(built_gem_path.to_s)
target = File.join(base, "checksums", file_name)
File.write(target, checksum)
Bundler.ui.confirm "#{name} #{version} checksum written to checksums/#{file_name}."
@@ -132,8 +129,8 @@ module Bundler
def git_push(remote = nil)
remote ||= default_remote
- perform_git_push "#{remote} refs/heads/#{current_branch}"
- perform_git_push "#{remote} refs/tags/#{version_tag}"
+ sh(%W[git push #{remote} refs/heads/#{current_branch}])
+ sh(%W[git push #{remote} refs/tags/#{version_tag}])
Bundler.ui.confirm "Pushed git commits and release tag."
end
@@ -161,13 +158,6 @@ module Bundler
allowed_push_host || env_rubygems_host || "rubygems.org"
end
- def perform_git_push(options = "")
- cmd = "git push #{options}"
- out, status = sh_with_status(cmd.shellsplit)
- return if status.success?
- raise "Couldn't git push. `#{cmd}' failed with the following output:\n\n#{out}\n"
- end
-
def already_tagged?
return false unless sh(%w[git tag]).split(/\n/).include?(version_tag)
Bundler.ui.confirm "Tag #{version_tag} has already been created."
@@ -218,8 +208,7 @@ module Bundler
def sh(cmd, &block)
out, status = sh_with_status(cmd, &block)
unless status.success?
- cmd = cmd.shelljoin if cmd.respond_to?(:shelljoin)
- raise(out.empty? ? "Running `#{cmd}` failed. Run this command directly for more detailed output." : out)
+ raise("Running `#{cmd.shelljoin}` failed with the following output:\n\n#{out}\n")
end
out
end
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index a59ffe2dec..a3efff1e86 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -85,6 +85,10 @@ module Gem
dependencies - development_dependencies
end
+ def deleted_gem?
+ !default_gem? && !File.directory?(full_gem_path)
+ end
+
private
def dependencies_to_gemfile(dependencies, group = nil)
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
index 8890582318..bb9f1cb3f5 100644
--- a/lib/bundler/rubygems_gem_installer.rb
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -16,10 +16,12 @@ module Bundler
spec.loaded_from = spec_file
# Completely remove any previous gem files
- FileUtils.rm_rf gem_dir
- FileUtils.rm_rf spec.extension_dir
+ strict_rm_rf gem_dir
+ strict_rm_rf spec.extension_dir
- FileUtils.mkdir_p gem_dir, :mode => 0o755
+ SharedHelpers.filesystem_access(gem_dir, :create) do
+ FileUtils.mkdir_p gem_dir, :mode => 0o755
+ end
extract_files
@@ -31,7 +33,10 @@ module Bundler
generate_plugins
write_spec
- write_cache_file
+
+ SharedHelpers.filesystem_access("#{gem_home}/cache", :write) do
+ write_cache_file
+ end
say spec.post_install_message unless spec.post_install_message.nil?
@@ -87,6 +92,17 @@ module Bundler
private
+ def strict_rm_rf(dir)
+ # FileUtils.rm_rf should probably rise in case of permission issues like
+ # `rm -rf` does. However, it fails to delete the folder silently due to
+ # https://github.com/ruby/fileutils/issues/57. It should probably be fixed
+ # inside `fileutils` but for now I`m checking whether the folder was
+ # removed after it completes, and raising otherwise.
+ FileUtils.rm_rf dir
+
+ raise PermissionError.new(dir, :delete) if File.directory?(dir)
+ end
+
def validate_bundler_checksum(checksum)
return true if Bundler.settings[:disable_checksum_validation]
return true unless checksum
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index 98982b0f07..f6d59baf15 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -86,16 +86,12 @@ module Bundler
def spec_missing_extensions?(spec, default = true)
return spec.missing_extensions? if spec.respond_to?(:missing_extensions?)
- return false if spec_default_gem?(spec)
+ return false if spec.default_gem?
return false if spec.extensions.empty?
default
end
- def spec_default_gem?(spec)
- spec.respond_to?(:default_gem?) && spec.default_gem?
- end
-
def spec_matches_for_glob(spec, glob)
return spec.matches_for_glob(glob) if spec.respond_to?(:matches_for_glob)
@@ -504,14 +500,15 @@ module Bundler
end
def fetch_specs(remote, name)
+ require "rubygems/remote_fetcher"
path = remote.uri.to_s + "#{name}.#{Gem.marshal_version}.gz"
fetcher = gem_remote_fetcher
fetcher.headers = { "X-Gemfile-Source" => remote.original_uri.to_s } if remote.original_uri
string = fetcher.fetch_path(path)
Bundler.load_marshal(string)
- rescue Gem::RemoteFetcher::FetchError => e
+ rescue Gem::RemoteFetcher::FetchError
# it's okay for prerelease to fail
- raise e unless name == "prerelease_specs"
+ raise unless name == "prerelease_specs"
end
def fetch_all_remote_specs(remote)
@@ -521,12 +518,32 @@ module Bundler
specs.concat(pres)
end
- def download_gem(spec, uri, path)
+ def download_gem(spec, uri, cache_dir)
+ require "rubygems/remote_fetcher"
uri = Bundler.settings.mirror_for(uri)
fetcher = gem_remote_fetcher
fetcher.headers = { "X-Gemfile-Source" => spec.remote.original_uri.to_s } if spec.remote.original_uri
Bundler::Retry.new("download gem from #{uri}").attempts do
- fetcher.download(spec, uri, path)
+ gem_file_name = spec.file_name
+ local_gem_path = File.join cache_dir, gem_file_name
+ return if File.exist? local_gem_path
+
+ begin
+ remote_gem_path = uri + "gems/#{gem_file_name}"
+ remote_gem_path = remote_gem_path.to_s if provides?("< 3.2.0.rc.1")
+
+ SharedHelpers.filesystem_access(local_gem_path) do
+ fetcher.cache_update_path remote_gem_path, local_gem_path
+ end
+ rescue Gem::RemoteFetcher::FetchError
+ raise if spec.original_platform == spec.platform
+
+ original_gem_file_name = "#{spec.original_name}.gem"
+ raise if gem_file_name == original_gem_file_name
+
+ gem_file_name = original_gem_file_name
+ retry
+ end
end
rescue Gem::RemoteFetcher::FetchError => e
raise Bundler::HTTPError, "Could not download gem from #{uri} due to underlying error <#{e.message}>"
diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb
index fbb8833cfb..31e71388a6 100644
--- a/lib/bundler/runtime.rb
+++ b/lib/bundler/runtime.rb
@@ -291,7 +291,7 @@ module Bundler
return unless activated_spec = Bundler.rubygems.loaded_specs(spec.name)
return if activated_spec.version == spec.version
- suggestion = if Bundler.rubygems.spec_default_gem?(activated_spec)
+ suggestion = if activated_spec.default_gem?
"Since #{spec.name} is a default gem, you can either remove your dependency on it" \
" or try updating to a newer version of bundler that supports #{spec.name} as a default gem."
else
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index 679fb22574..a41a2f23e9 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -307,7 +307,9 @@ module Bundler
# If there is no URI scheme, assume it is an ssh/git URI
input = uri
end
- SharedHelpers.digest(:SHA1).hexdigest(input)
+ # We use SHA1 here for historical reason and to preserve backward compatibility.
+ # But a transition to a simpler mangling algorithm would be welcome.
+ Bundler::Digest.sha1(input)
end
def cached_revision
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index 7f8699b91c..11b506b324 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -135,7 +135,7 @@ module Bundler
force = opts[:force]
ensure_builtin_gems_cached = opts[:ensure_builtin_gems_cached]
- if ensure_builtin_gems_cached && builtin_gem?(spec)
+ if ensure_builtin_gems_cached && spec.default_gem?
if !cached_path(spec)
cached_built_in_gem(spec) unless spec.remote
force = true
@@ -174,6 +174,7 @@ module Bundler
Bundler.ui.confirm message
path = cached_gem(spec)
+ raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path
if requires_sudo?
install_path = Bundler.tmp(spec.full_name)
bin_path = install_path.join("bin")
@@ -233,12 +234,8 @@ module Bundler
end
def cache(spec, custom_path = nil)
- if builtin_gem?(spec)
- cached_path = cached_built_in_gem(spec)
- else
- cached_path = cached_gem(spec)
- end
- raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
+ cached_path = cached_gem(spec)
+ raise GemNotFound, "Missing gem file '#{spec.file_name}'." unless cached_path
return if File.dirname(cached_path) == Bundler.app_cache.to_s
Bundler.ui.info " * #{File.basename(cached_path)}"
FileUtils.cp(cached_path, Bundler.app_cache(custom_path))
@@ -352,14 +349,17 @@ module Bundler
end
def cached_gem(spec)
- cached_gem = cached_path(spec)
- unless cached_gem
- raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation"
+ if spec.default_gem?
+ cached_built_in_gem(spec)
+ else
+ cached_path(spec)
end
- cached_gem
end
def cached_path(spec)
+ global_cache_path = download_cache_path(spec)
+ @caches << global_cache_path if global_cache_path
+
possibilities = @caches.map {|p| "#{p}/#{spec.file_name}" }
possibilities.find {|p| File.exist?(p) }
end
@@ -459,19 +459,26 @@ module Bundler
spec.fetch_platform
- download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir
- gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem"
+ cache_path = download_cache_path(spec) || default_cache_path_for(rubygems_dir)
+ gem_path = "#{cache_path}/#{spec.file_name}"
+
+ if requires_sudo?
+ download_path = Bundler.tmp(spec.full_name)
+ download_cache_path = default_cache_path_for(download_path)
+ else
+ download_cache_path = cache_path
+ end
- SharedHelpers.filesystem_access("#{download_path}/cache") do |p|
+ SharedHelpers.filesystem_access(download_cache_path) do |p|
FileUtils.mkdir_p(p)
end
- download_gem(spec, download_path)
+ download_gem(spec, download_cache_path)
if requires_sudo?
- SharedHelpers.filesystem_access("#{rubygems_dir}/cache") do |p|
+ SharedHelpers.filesystem_access(cache_path) do |p|
Bundler.mkdir_p(p)
end
- Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}"
+ Bundler.sudo "mv #{download_cache_path}/#{spec.file_name} #{gem_path}"
end
gem_path
@@ -479,16 +486,8 @@ module Bundler
Bundler.rm_rf(download_path) if requires_sudo?
end
- def builtin_gem?(spec)
- # Ruby 2.1, where all included gems have this summary
- return true if spec.summary =~ /is bundled with Ruby/
-
- # Ruby 2.0, where gemspecs are stored in specifications/default/
- spec.loaded_from && spec.loaded_from.include?("specifications/default/")
- end
-
def installed?(spec)
- installed_specs[spec].any?
+ installed_specs[spec].any? && !spec.deleted_gem?
end
def requires_sudo?
@@ -499,6 +498,10 @@ module Bundler
Bundler.rubygems.gem_dir
end
+ def default_cache_path_for(dir)
+ "#{dir}/cache"
+ end
+
def cache_path
Bundler.app_cache
end
@@ -511,52 +514,13 @@ module Bundler
# @param [Specification] spec
# the spec we want to download or retrieve from the cache.
#
- # @param [String] download_path
+ # @param [String] download_cache_path
# the local directory the .gem will end up in.
#
- def download_gem(spec, download_path)
- local_path = File.join(download_path, "cache/#{spec.full_name}.gem")
-
- if (cache_path = download_cache_path(spec)) && cache_path.file?
- SharedHelpers.filesystem_access(local_path) do
- FileUtils.cp(cache_path, local_path)
- end
- else
- uri = spec.remote.uri
- Bundler.ui.confirm("Fetching #{version_message(spec)}")
- rubygems_local_path = Bundler.rubygems.download_gem(spec, uri, download_path)
-
- # older rubygems return varying file:// variants depending on version
- rubygems_local_path = rubygems_local_path.gsub(/\Afile:/, "") unless Bundler.rubygems.provides?(">= 3.2.0.rc.2")
- rubygems_local_path = rubygems_local_path.gsub(%r{\A//}, "") if Bundler.rubygems.provides?("< 3.1.0")
-
- if rubygems_local_path != local_path
- SharedHelpers.filesystem_access(local_path) do
- FileUtils.mv(rubygems_local_path, local_path)
- end
- end
- cache_globally(spec, local_path)
- end
- end
-
- # Checks if the requested spec exists in the global cache. If it does
- # not, we create the relevant global cache subdirectory if it does not
- # exist and copy the spec from the local cache to the global cache.
- #
- # @param [Specification] spec
- # the spec we want to copy to the global cache.
- #
- # @param [String] local_cache_path
- # the local directory from which we want to copy the .gem.
- #
- def cache_globally(spec, local_cache_path)
- return unless cache_path = download_cache_path(spec)
- return if cache_path.exist?
-
- SharedHelpers.filesystem_access(cache_path.dirname, &:mkpath)
- SharedHelpers.filesystem_access(cache_path) do
- FileUtils.cp(local_cache_path, cache_path)
- end
+ def download_gem(spec, download_cache_path)
+ uri = spec.remote.uri
+ Bundler.ui.confirm("Fetching #{version_message(spec)}")
+ Bundler.rubygems.download_gem(spec, uri, download_cache_path)
end
# Returns the global cache path of the calling Rubygems::Source object.
@@ -575,7 +539,7 @@ module Bundler
return unless remote = spec.remote
return unless cache_slug = remote.cache_slug
- Bundler.user_cache.join("gems", cache_slug, spec.file_name)
+ Bundler.user_cache.join("gems", cache_slug)
end
def extension_cache_slug(spec)
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb
index fbcd26c765..984c1c3dcb 100644
--- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb
+++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb
@@ -1,14 +1,18 @@
-require_relative 'connection_pool/version'
-require_relative 'connection_pool/timed_stack'
+require "timeout"
+require_relative "connection_pool/version"
+class Bundler::ConnectionPool
+ class Error < ::RuntimeError; end
+ class PoolShuttingDownError < ::Bundler::ConnectionPool::Error; end
+ class TimeoutError < ::Timeout::Error; end
+end
-# Generic connection pool class for e.g. sharing a limited number of network connections
-# among many threads. Note: Connections are lazily created.
+# Generic connection pool class for sharing a limited number of objects or network connections
+# among many threads. Note: pool elements are lazily created.
#
# Example usage with block (faster):
#
# @pool = Bundler::ConnectionPool.new { Redis.new }
-#
# @pool.with do |redis|
# redis.lpop('my-list') if redis.llen('my-list') > 0
# end
@@ -34,29 +38,23 @@ require_relative 'connection_pool/timed_stack'
class Bundler::ConnectionPool
DEFAULTS = {size: 5, timeout: 5}
- class Error < RuntimeError
- end
-
def self.wrap(options, &block)
Wrapper.new(options, &block)
end
def initialize(options = {}, &block)
- raise ArgumentError, 'Connection pool requires a block' unless block
+ raise ArgumentError, "Connection pool requires a block" unless block
options = DEFAULTS.merge(options)
- @size = options.fetch(:size)
+ @size = Integer(options.fetch(:size))
@timeout = options.fetch(:timeout)
@available = TimedStack.new(@size, &block)
- @key = :"current-#{@available.object_id}"
- @key_count = :"current-#{@available.object_id}-count"
+ @key = :"pool-#{@available.object_id}"
+ @key_count = :"pool-#{@available.object_id}-count"
end
-if Thread.respond_to?(:handle_interrupt)
-
- # MRI
def with(options = {})
Thread.handle_interrupt(Exception => :never) do
conn = checkout(options)
@@ -69,28 +67,15 @@ if Thread.respond_to?(:handle_interrupt)
end
end
end
-
-else
-
- # jruby 1.7.x
- def with(options = {})
- conn = checkout(options)
- begin
- yield conn
- ensure
- checkin
- end
- end
-
-end
+ alias then with
def checkout(options = {})
if ::Thread.current[@key]
- ::Thread.current[@key_count]+= 1
+ ::Thread.current[@key_count] += 1
::Thread.current[@key]
else
- ::Thread.current[@key_count]= 1
- ::Thread.current[@key]= @available.pop(options[:timeout] || @timeout)
+ ::Thread.current[@key_count] = 1
+ ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
end
end
@@ -98,64 +83,44 @@ end
if ::Thread.current[@key]
if ::Thread.current[@key_count] == 1
@available.push(::Thread.current[@key])
- ::Thread.current[@key]= nil
+ ::Thread.current[@key] = nil
+ ::Thread.current[@key_count] = nil
else
- ::Thread.current[@key_count]-= 1
+ ::Thread.current[@key_count] -= 1
end
else
- raise Bundler::ConnectionPool::Error, 'no connections are checked out'
+ raise Bundler::ConnectionPool::Error, "no connections are checked out"
end
nil
end
+ ##
+ # Shuts down the Bundler::ConnectionPool by passing each connection to +block+ and
+ # then removing it from the pool. Attempting to checkout a connection after
+ # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+.
+
def shutdown(&block)
@available.shutdown(&block)
end
- # Size of this connection pool
- def size
- @size
+ ##
+ # Reloads the Bundler::ConnectionPool by passing each connection to +block+ and then
+ # removing it the pool. Subsequent checkouts will create new connections as
+ # needed.
+
+ def reload(&block)
+ @available.shutdown(reload: true, &block)
end
+ # Size of this connection pool
+ attr_reader :size
+
# Number of pool entries available for checkout at this instant.
def available
@available.length
end
-
- private
-
- class Wrapper < ::BasicObject
- METHODS = [:with, :pool_shutdown]
-
- def initialize(options = {}, &block)
- @pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) }
- end
-
- def with(&block)
- @pool.with(&block)
- end
-
- def pool_shutdown(&block)
- @pool.shutdown(&block)
- end
-
- def pool_size
- @pool.size
- end
-
- def pool_available
- @pool.available
- end
-
- def respond_to?(id, *args)
- METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
- end
-
- def method_missing(name, *args, &block)
- with do |connection|
- connection.send(name, *args, &block)
- end
- end
- end
end
+
+require_relative "connection_pool/timed_stack"
+require_relative "connection_pool/wrapper"
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb
deleted file mode 100644
index 5a9c4a27bb..0000000000
--- a/lib/bundler/vendor/connection_pool/lib/connection_pool/monotonic_time.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# Global monotonic clock from Concurrent Ruby 1.0.
-# Copyright (c) Jerry D'Antonio -- released under the MIT license.
-# Slightly modified; used with permission.
-# https://github.com/ruby-concurrency/concurrent-ruby
-
-require 'thread'
-
-class Bundler::ConnectionPool
-
- class_definition = Class.new do
-
- if defined?(Process::CLOCK_MONOTONIC)
-
- # @!visibility private
- def get_time
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
- end
-
- elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
-
- # @!visibility private
- def get_time
- java.lang.System.nanoTime() / 1_000_000_000.0
- end
-
- else
-
- # @!visibility private
- def initialize
- @mutex = Mutex.new
- @last_time = Time.now.to_f
- end
-
- # @!visibility private
- def get_time
- @mutex.synchronize do
- now = Time.now.to_f
- if @last_time < now
- @last_time = now
- else # clock has moved back in time
- @last_time += 0.000_001
- end
- end
- end
- end
- end
-
- ##
- # Clock that cannot be set and represents monotonic time since
- # some unspecified starting point.
- #
- # @!visibility private
- GLOBAL_MONOTONIC_CLOCK = class_definition.new
- private_constant :GLOBAL_MONOTONIC_CLOCK
-
- class << self
- ##
- # Returns the current time a tracked by the application monotonic clock.
- #
- # @return [Float] The current monotonic time when `since` not given else
- # the elapsed monotonic time between `since` and the current time
- def monotonic_time
- GLOBAL_MONOTONIC_CLOCK.get_time
- end
- end
-end
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb
index f3fe1e04ad..a7b1cf06a8 100644
--- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb
+++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb
@@ -1,13 +1,3 @@
-require 'thread'
-require 'timeout'
-require_relative 'monotonic_time'
-
-##
-# Raised when you attempt to retrieve a connection from a pool that has been
-# shut down.
-
-class Bundler::ConnectionPool::PoolShuttingDownError < RuntimeError; end
-
##
# The TimedStack manages a pool of homogeneous connections (or any resource
# you wish to manage). Connections are created lazily up to a given maximum
@@ -25,7 +15,7 @@ class Bundler::ConnectionPool::PoolShuttingDownError < RuntimeError; end
#
# conn = ts.pop
# ts.pop timeout: 5
-# #=> raises Timeout::Error after 5 seconds
+# #=> raises Bundler::ConnectionPool::TimeoutError after 5 seconds
class Bundler::ConnectionPool::TimedStack
attr_reader :max
@@ -39,8 +29,8 @@ class Bundler::ConnectionPool::TimedStack
@created = 0
@que = []
@max = size
- @mutex = Mutex.new
- @resource = ConditionVariable.new
+ @mutex = Thread::Mutex.new
+ @resource = Thread::ConditionVariable.new
@shutdown_block = nil
end
@@ -59,12 +49,12 @@ class Bundler::ConnectionPool::TimedStack
@resource.broadcast
end
end
- alias_method :<<, :push
+ alias << push
##
# Retrieves a connection from the stack. If a connection is available it is
# immediately returned. If no connection is available within the given
- # timeout a Timeout::Error is raised.
+ # timeout a Bundler::ConnectionPool::TimeoutError is raised.
#
# +:timeout+ is the only checked entry in +options+ and is preferred over
# the +timeout+ argument (which will be removed in a future release). Other
@@ -74,7 +64,7 @@ class Bundler::ConnectionPool::TimedStack
options, timeout = timeout, 0.5 if Hash === timeout
timeout = options.fetch :timeout, timeout
- deadline = Bundler::ConnectionPool.monotonic_time + timeout
+ deadline = current_time + timeout
@mutex.synchronize do
loop do
raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block
@@ -83,18 +73,20 @@ class Bundler::ConnectionPool::TimedStack
connection = try_create(options)
return connection if connection
- to_wait = deadline - Bundler::ConnectionPool.monotonic_time
- raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0
+ to_wait = deadline - current_time
+ raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec" if to_wait <= 0
@resource.wait(@mutex, to_wait)
end
end
end
##
- # Shuts down the TimedStack which prevents connections from being checked
- # out. The +block+ is called once for each connection on the stack.
+ # Shuts down the TimedStack by passing each connection to +block+ and then
+ # removing it from the pool. Attempting to checkout a connection after
+ # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+ unless
+ # +:reload+ is +true+.
- def shutdown(&block)
+ def shutdown(reload: false, &block)
raise ArgumentError, "shutdown must receive a block" unless block_given?
@mutex.synchronize do
@@ -102,6 +94,7 @@ class Bundler::ConnectionPool::TimedStack
@resource.broadcast
shutdown_connections
+ @shutdown_block = nil if reload
end
end
@@ -121,6 +114,10 @@ class Bundler::ConnectionPool::TimedStack
private
+ def current_time
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ end
+
##
# This is an extension point for TimedStack and is called with a mutex.
#
@@ -149,6 +146,7 @@ class Bundler::ConnectionPool::TimedStack
conn = fetch_connection(options)
@shutdown_block.call(conn)
end
+ @created = 0
end
##
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb
index b149c0e242..56ebf69902 100644
--- a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb
+++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb
@@ -1,3 +1,3 @@
class Bundler::ConnectionPool
- VERSION = "2.2.2"
+ VERSION = "2.3.0"
end
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb
new file mode 100644
index 0000000000..880170c06b
--- /dev/null
+++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb
@@ -0,0 +1,57 @@
+class Bundler::ConnectionPool
+ class Wrapper < ::BasicObject
+ METHODS = [:with, :pool_shutdown, :wrapped_pool]
+
+ def initialize(options = {}, &block)
+ @pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) }
+ end
+
+ def wrapped_pool
+ @pool
+ end
+
+ def with(&block)
+ @pool.with(&block)
+ end
+
+ def pool_shutdown(&block)
+ @pool.shutdown(&block)
+ end
+
+ def pool_size
+ @pool.size
+ end
+
+ def pool_available
+ @pool.available
+ end
+
+ def respond_to?(id, *args)
+ METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
+ end
+
+ # rubocop:disable Style/MethodMissingSuper
+ # rubocop:disable Style/MissingRespondToMissing
+ if ::RUBY_VERSION >= "3.0.0"
+ def method_missing(name, *args, **kwargs, &block)
+ with do |connection|
+ connection.send(name, *args, **kwargs, &block)
+ end
+ end
+ elsif ::RUBY_VERSION >= "2.7.0"
+ ruby2_keywords def method_missing(name, *args, &block)
+ with do |connection|
+ connection.send(name, *args, &block)
+ end
+ end
+ else
+ def method_missing(name, *args, &block)
+ with do |connection|
+ connection.send(name, *args, &block)
+ end
+ end
+ end
+ # rubocop:enable Style/MethodMissingSuper
+ # rubocop:enable Style/MissingRespondToMissing
+ end
+end
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
index 23a568c8ba..5ee93a2266 100644
--- a/lib/bundler/version.rb
+++ b/lib/bundler/version.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
module Bundler
- VERSION = "2.2.29".freeze
+ VERSION = "2.2.30".freeze
def self.bundler_major_version
@bundler_major_version ||= VERSION.split(".").first.to_i
diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb
index ffa095b228..5e4ee21c51 100644
--- a/lib/bundler/worker.rb
+++ b/lib/bundler/worker.rb
@@ -21,8 +21,8 @@ module Bundler
# @param func [Proc] job to run in inside the worker pool
def initialize(size, name, func)
@name = name
- @request_queue = Queue.new
- @response_queue = Queue.new
+ @request_queue = Thread::Queue.new
+ @response_queue = Thread::Queue.new
@func = func
@size = size
@threads = nil
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 3c48f2a8ac..dc6746769a 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -8,7 +8,7 @@
require 'rbconfig'
module Gem
- VERSION = "3.2.29".freeze
+ VERSION = "3.2.30".freeze
end
# Must be first since it unloads the prelude from 1.9.2
@@ -178,7 +178,7 @@ module Gem
@configuration = nil
@gemdeps = nil
@loaded_specs = {}
- LOADED_SPECS_MUTEX = Mutex.new
+ LOADED_SPECS_MUTEX = Thread::Mutex.new
@path_to_default_spec_map = {}
@platforms = []
@ruby = nil
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index bdfeb0ba6e..867cb07cca 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -43,6 +43,11 @@ class Gem::Commands::CertCommand < Gem::Command
options[:key] = open_private_key(key_file)
end
+ add_option('-A', '--key-algorithm ALGORITHM',
+ 'Select which key algorithm to use for --build') do |algorithm, options|
+ options[:key_algorithm] = algorithm
+ end
+
add_option('-s', '--sign CERT',
'Signs CERT with the key from -K',
'and the certificate from -C') do |cert_file, options|
@@ -89,14 +94,14 @@ class Gem::Commands::CertCommand < Gem::Command
def open_private_key(key_file)
check_openssl
passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase
+ key = OpenSSL::PKey.read File.read(key_file), passphrase
raise OptionParser::InvalidArgument,
"#{key_file}: private key not found" unless key.private?
key
rescue Errno::ENOENT
raise OptionParser::InvalidArgument, "#{key_file}: does not exist"
- rescue OpenSSL::PKey::RSAError
- raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key"
+ rescue OpenSSL::PKey::PKeyError, ArgumentError
+ raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key"
end
def execute
@@ -170,7 +175,8 @@ class Gem::Commands::CertCommand < Gem::Command
raise Gem::CommandLineError,
"Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation
- key = Gem::Security.create_key
+ algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM
+ key = Gem::Security.create_key(algorithm)
key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase
return key, key_path
@@ -255,13 +261,14 @@ For further reading on signing gems see `ri Gem::Security`.
key_file = File.join Gem.default_key_path
key = File.read key_file
passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- options[:key] = OpenSSL::PKey::RSA.new key, passphrase
+ options[:key] = OpenSSL::PKey.read key, passphrase
+
rescue Errno::ENOENT
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
terminate_interaction 1
- rescue OpenSSL::PKey::RSAError
+ rescue OpenSSL::PKey::PKeyError
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem is not valid"
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 7ef0da4f36..92c9b93fed 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -618,7 +618,7 @@ abort "#{deprecation_message}"
end
def regenerate_plugins
- require "rubygems/commands/pristine_command"
+ require_relative "pristine_command"
say "Regenerating plugins"
args = %w[--all --only-plugins --silent]
diff --git a/lib/rubygems/core_ext/tcpsocket_init.rb b/lib/rubygems/core_ext/tcpsocket_init.rb
index 3d9740c579..2a79b63bd6 100644
--- a/lib/rubygems/core_ext/tcpsocket_init.rb
+++ b/lib/rubygems/core_ext/tcpsocket_init.rb
@@ -11,10 +11,10 @@ module CoreExtensions
IPV4_DELAY_SECONDS = 0.1
def initialize(host, serv, *rest)
- mutex = Mutex.new
+ mutex = Thread::Mutex.new
addrs = []
threads = []
- cond_var = ConditionVariable.new
+ cond_var = Thread::ConditionVariable.new
Addrinfo.foreach(host, serv, nil, :STREAM) do |addr|
Thread.report_on_exception = false if defined? Thread.report_on_exception = ()
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 26ebfaa846..cf5ed7b502 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -68,7 +68,7 @@ class Gem::Installer
@path_warning = false
- @install_lock = Mutex.new
+ @install_lock = Thread::Mutex.new
class << self
##
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index c4b29e4176..94705914af 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -71,6 +71,13 @@ class Gem::Package
end
end
+ class SymlinkError < Error
+ def initialize(name, destination, destination_dir)
+ super "installing symlink '%s' pointing to parent path %s of %s is not allowed" %
+ [name, destination, destination_dir]
+ end
+ end
+
class NonSeekableIO < Error; end
class TooLongFileName < Error; end
@@ -400,13 +407,21 @@ EOM
# extracted.
def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
- directories = [] if dir_mode
+ directories = []
open_tar_gz io do |tar|
tar.each do |entry|
next unless File.fnmatch pattern, entry.full_name, File::FNM_DOTMATCH
destination = install_location entry.full_name, destination_dir
+ if entry.symlink?
+ link_target = entry.header.linkname
+ real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination))
+
+ raise Gem::Package::SymlinkError.new(entry.full_name, real_destination, destination_dir) unless
+ normalize_path(real_destination).start_with? normalize_path(destination_dir + '/')
+ end
+
FileUtils.rm_rf destination
mkdir_options = {}
@@ -417,9 +432,11 @@ EOM
else
File.dirname destination
end
- directories << mkdir if directories
- mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name
+ unless directories.include?(mkdir)
+ FileUtils.mkdir_p mkdir, **mkdir_options
+ directories << mkdir
+ end
File.open destination, 'wb' do |out|
out.write entry.read
@@ -432,8 +449,7 @@ EOM
end
end
- if directories
- directories.uniq!
+ if dir_mode
File.chmod(dir_mode, *directories)
end
end
@@ -466,21 +482,11 @@ EOM
raise Gem::Package::PathError.new(filename, destination_dir) if
filename.start_with? '/'
- destination_dir = File.expand_path(File.realpath(destination_dir))
- destination = File.expand_path(File.join(destination_dir, filename))
+ destination_dir = File.realpath(destination_dir)
+ destination = File.expand_path(filename, destination_dir)
raise Gem::Package::PathError.new(destination, destination_dir) unless
- destination.start_with? destination_dir + '/'
-
- begin
- real_destination = File.expand_path(File.realpath(destination))
- rescue
- # it's fine if the destination doesn't exist, because rm -rf'ing it can't cause any damage
- nil
- else
- raise Gem::Package::PathError.new(real_destination, destination_dir) unless
- real_destination.start_with? destination_dir + '/'
- end
+ normalize_path(destination).start_with? normalize_path(destination_dir + '/')
destination.tap(&Gem::UNTAINT)
destination
@@ -494,22 +500,6 @@ EOM
end
end
- def mkdir_p_safe(mkdir, mkdir_options, destination_dir, file_name)
- destination_dir = File.realpath(File.expand_path(destination_dir))
- parts = mkdir.split(File::SEPARATOR)
- parts.reduce do |path, basename|
- path = File.realpath(path) unless path == ""
- path = File.expand_path(path + File::SEPARATOR + basename)
- lstat = File.lstat path rescue nil
- if !lstat || !lstat.directory?
- unless normalize_path(path).start_with? normalize_path(destination_dir) and (FileUtils.mkdir path, **mkdir_options rescue false)
- raise Gem::Package::PathError.new(file_name, destination_dir)
- end
- end
- path
- end
- end
-
##
# Loads a Gem::Specification from the TarEntry +entry+
diff --git a/lib/rubygems/query_utils.rb b/lib/rubygems/query_utils.rb
index ea0f260ab4..0acd5bf9c8 100644
--- a/lib/rubygems/query_utils.rb
+++ b/lib/rubygems/query_utils.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/local_remote_options'
-require 'rubygems/spec_fetcher'
-require 'rubygems/version_option'
-require 'rubygems/text'
+require_relative 'local_remote_options'
+require_relative 'spec_fetcher'
+require_relative 'version_option'
+require_relative 'text'
module Gem::QueryUtils
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index 22ad89b970..de6f88f39a 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -72,7 +72,7 @@ class Gem::RemoteFetcher
# fetching the gem.
def initialize(proxy=nil, dns=nil, headers={})
- require 'rubygems/core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled
+ require_relative 'core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled
require 'net/http'
require 'stringio'
require 'uri'
@@ -81,7 +81,7 @@ class Gem::RemoteFetcher
@proxy = proxy
@pools = {}
- @pool_lock = Mutex.new
+ @pool_lock = Thread::Mutex.new
@cert_files = Gem::Request.get_cert_files
@headers = headers
diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb
index 72e25e20ab..d6100c914b 100644
--- a/lib/rubygems/request.rb
+++ b/lib/rubygems/request.rb
@@ -96,8 +96,10 @@ class Gem::Request
return unless cert
case error_number
when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then
+ require 'time'
"Certificate #{cert.subject} expired at #{cert.not_after.iso8601}"
when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then
+ require 'time'
"Certificate #{cert.subject} not valid until #{cert.not_before.iso8601}"
when OpenSSL::X509::V_ERR_CERT_REJECTED then
"Certificate #{cert.subject} is rejected"
diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb
index 7f3988952c..a4c2929b38 100644
--- a/lib/rubygems/request/connection_pools.rb
+++ b/lib/rubygems/request/connection_pools.rb
@@ -11,7 +11,7 @@ class Gem::Request::ConnectionPools # :nodoc:
@proxy_uri = proxy_uri
@cert_files = cert_files
@pools = {}
- @pool_mutex = Mutex.new
+ @pool_mutex = Thread::Mutex.new
end
def pool_for(uri)
diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb
index 9985bbafa6..f028516db8 100644
--- a/lib/rubygems/request/http_pool.rb
+++ b/lib/rubygems/request/http_pool.rb
@@ -12,7 +12,7 @@ class Gem::Request::HTTPPool # :nodoc:
@http_args = http_args
@cert_files = cert_files
@proxy_uri = proxy_uri
- @queue = SizedQueue.new 1
+ @queue = Thread::SizedQueue.new 1
@queue << nil
end
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index cff960708c..9286c54221 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -151,7 +151,7 @@ class Gem::RequestSet
@prerelease = options[:prerelease]
requests = []
- download_queue = Queue.new
+ download_queue = Thread::Queue.new
# Create a thread-safe list of gems to download
sorted_requests.each do |req|
diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb
index bba9afc9ff..4d1deee997 100644
--- a/lib/rubygems/s3_uri_signer.rb
+++ b/lib/rubygems/s3_uri_signer.rb
@@ -1,5 +1,4 @@
-require 'digest'
-require 'rubygems/openssl'
+require_relative 'openssl'
##
# S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems
@@ -87,7 +86,7 @@ class Gem::S3URISigner
"AWS4-HMAC-SHA256",
date_time,
credential_info,
- Digest::SHA256.hexdigest(canonical_request),
+ OpenSSL::Digest::SHA256.hexdigest(canonical_request),
].join("\n")
end
@@ -140,8 +139,8 @@ class Gem::S3URISigner
def ec2_metadata_credentials_json
require 'net/http'
- require 'rubygems/request'
- require 'rubygems/request/connection_pools'
+ require_relative 'request'
+ require_relative 'request/connection_pools'
require 'json'
iam_info = ec2_metadata_request(EC2_IAM_INFO)
diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb
index 28f705549c..8240a1a059 100644
--- a/lib/rubygems/security.rb
+++ b/lib/rubygems/security.rb
@@ -152,6 +152,7 @@ require_relative 'openssl'
# certificate for EMAIL_ADDR
# -C, --certificate CERT Signing certificate for --sign
# -K, --private-key KEY Key for --sign or --build
+# -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA.
# -s, --sign CERT Signs CERT with the key from -K
# and the certificate from -C
# -d, --days NUMBER_OF_DAYS Days before the certificate expires
@@ -317,7 +318,6 @@ require_relative 'openssl'
# * Honor extension restrictions
# * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
# file, instead of an array embedded in the metadata.
-# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
#
# == Original author
#
@@ -337,17 +337,19 @@ module Gem::Security
DIGEST_NAME = 'SHA256' # :nodoc:
##
- # Algorithm for creating the key pair used to sign gems
+ # Length of keys created by RSA and DSA keys
- KEY_ALGORITHM =
- if defined?(OpenSSL::PKey::RSA)
- OpenSSL::PKey::RSA
- end
+ RSA_DSA_KEY_LENGTH = 3072
##
- # Length of keys created by KEY_ALGORITHM
+ # Default algorithm to use when building a key pair
- KEY_LENGTH = 3072
+ DEFAULT_KEY_ALGORITHM = 'RSA'
+
+ ##
+ # Named curve used for Elliptic Curve
+
+ EC_NAME = 'secp384r1'
##
# Cipher used to encrypt the key pair used to sign gems.
@@ -400,7 +402,7 @@ module Gem::Security
serial = 1)
cert = OpenSSL::X509::Certificate.new
- cert.public_key = key.public_key
+ cert.public_key = get_public_key(key)
cert.version = 2
cert.serial = serial
@@ -419,6 +421,24 @@ module Gem::Security
end
##
+ # Gets the right public key from a PKey instance
+
+ def self.get_public_key(key)
+ return key.public_key unless key.is_a?(OpenSSL::PKey::EC)
+
+ ec_key = OpenSSL::PKey::EC.new(key.group.curve_name)
+ ec_key.public_key = key.public_key
+ ec_key
+ end
+
+ ##
+ # In Ruby 2.3 EC doesn't implement the private_key? but not the private? method
+
+ if defined?(OpenSSL::PKey::EC) && Gem::Version.new(String.new(RUBY_VERSION)) < Gem::Version.new("2.4.0")
+ OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)
+ end
+
+ ##
# Creates a self-signed certificate with an issuer and subject from +email+,
# a subject alternative name of +email+ and the given +extensions+ for the
# +key+.
@@ -459,11 +479,25 @@ module Gem::Security
end
##
- # Creates a new key pair of the specified +length+ and +algorithm+. The
- # default is a 3072 bit RSA key.
-
- def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM)
- algorithm.new length
+ # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC
+ # are supported.
+
+ def self.create_key(algorithm)
+ if defined?(OpenSSL::PKey)
+ case algorithm.downcase
+ when 'dsa'
+ OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH)
+ when 'rsa'
+ OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH)
+ when 'ec'
+ domain_key = OpenSSL::PKey::EC.new(EC_NAME)
+ domain_key.generate_key
+ domain_key
+ else
+ raise Gem::Security::Exception,
+ "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported."
+ end
+ end
end
##
@@ -492,7 +526,7 @@ module Gem::Security
raise Gem::Security::Exception,
"incorrect signing key for re-signing " +
"#{expired_certificate.subject}" unless
- expired_certificate.public_key.to_pem == private_key.public_key.to_pem
+ expired_certificate.public_key.to_pem == get_public_key(private_key).to_pem
unless expired_certificate.subject.to_s ==
expired_certificate.issuer.to_s
diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb
index 9683e55b32..3c3cb647ee 100644
--- a/lib/rubygems/security/policy.rb
+++ b/lib/rubygems/security/policy.rb
@@ -115,9 +115,11 @@ class Gem::Security::Policy
raise Gem::Security::Exception, 'missing key or signature'
end
+ public_key = Gem::Security.get_public_key(key)
+
raise Gem::Security::Exception,
"certificate #{signer.subject} does not match the signing key" unless
- signer.public_key.to_pem == key.public_key.to_pem
+ signer.public_key.to_pem == public_key.to_pem
true
end
@@ -164,9 +166,9 @@ class Gem::Security::Policy
end
save_cert = OpenSSL::X509::Certificate.new File.read path
- save_dgst = digester.digest save_cert.public_key.to_s
+ save_dgst = digester.digest save_cert.public_key.to_pem
- pkey_str = root.public_key.to_s
+ pkey_str = root.public_key.to_pem
cert_dgst = digester.digest pkey_str
raise Gem::Security::Exception,
diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb
index c5c2c4f220..968cf88973 100644
--- a/lib/rubygems/security/signer.rb
+++ b/lib/rubygems/security/signer.rb
@@ -83,8 +83,8 @@ class Gem::Security::Signer
@digest_name = Gem::Security::DIGEST_NAME
@digest_algorithm = Gem::Security.create_digest(@digest_name)
- if @key && !@key.is_a?(OpenSSL::PKey::RSA)
- @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase)
+ if @key && !@key.is_a?(OpenSSL::PKey::PKey)
+ @key = OpenSSL::PKey.read(File.read(@key), @passphrase)
end
if @cert_chain
@@ -177,8 +177,7 @@ class Gem::Security::Signer
disk_cert = File.read(disk_cert_path) rescue nil
disk_key_path = File.join(Gem.default_key_path)
- disk_key =
- OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil
+ disk_key = OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue nil
return unless disk_key
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index 816385d139..b5bd6b80e6 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rubygems/text"
+require_relative "text"
##
# A Source knows how to list and fetch gems from a RubyGems marshal index.
#
diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb
index 9876adc24e..cda5aa8073 100644
--- a/lib/rubygems/source/git.rb
+++ b/lib/rubygems/source/git.rb
@@ -225,7 +225,7 @@ class Gem::Source::Git < Gem::Source
# A hash for the git gem based on the git repository URI.
def uri_hash # :nodoc:
- require 'digest' # required here to avoid deadlocking in Gem.activate_bin_path (because digest is a gem on 2.5+)
+ require_relative '../openssl'
normalized =
if @repository =~ %r{^\w+://(\w+@)?}
@@ -235,6 +235,6 @@ class Gem::Source::Git < Gem::Source
@repository
end
- Digest::SHA1.hexdigest normalized
+ OpenSSL::Digest::SHA1.hexdigest normalized
end
end
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index aaa3b8ce7b..37fa9d0a8e 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -105,7 +105,7 @@ class Gem::Specification < Gem::BasicSpecification
# rubocop:disable Style/MutableConstant
LOAD_CACHE = {} # :nodoc:
# rubocop:enable Style/MutableConstant
- LOAD_CACHE_MUTEX = Mutex.new
+ LOAD_CACHE_MUTEX = Thread::Mutex.new
private_constant :LOAD_CACHE if defined? private_constant
diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb
index 61d50a9ff8..0ab44fbf6c 100644
--- a/lib/rubygems/user_interaction.rb
+++ b/lib/rubygems/user_interaction.rb
@@ -543,7 +543,7 @@ class Gem::StreamUI
# A progress reporter that behaves nicely with threaded downloading.
class ThreadedDownloadReporter
- MUTEX = Mutex.new
+ MUTEX = Thread::Mutex.new
##
# The current file name being displayed
diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb
index 2a55305172..4363c5adce 100644
--- a/lib/rubygems/util.rb
+++ b/lib/rubygems/util.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require 'rubygems/deprecate'
+require_relative 'deprecate'
##
# This module contains various utility methods as module methods.