diff options
Diffstat (limited to 'lib')
33 files changed, 329 insertions, 158 deletions
diff --git a/lib/rubygems.rb b/lib/rubygems.rb index d263f29dd2..858d910610 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -7,7 +7,6 @@ #++ require 'rbconfig' -require 'thread' module Gem VERSION = "3.0.0.beta1" @@ -526,8 +525,9 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end def self.find_files_from_load_path glob # :nodoc: + glob_with_suffixes = "#{glob}#{Gem.suffix_pattern}" $LOAD_PATH.map { |load_path| - Dir["#{File.expand_path glob, load_path}#{Gem.suffix_pattern}"] + Gem::Util.glob_files_in_dir(glob_with_suffixes, load_path) }.flatten.select { |file| File.file? file.untaint } end @@ -1119,8 +1119,9 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} path = "rubygems_plugin" files = [] + glob = "#{path}#{Gem.suffix_pattern}" $LOAD_PATH.each do |load_path| - globbed = Dir["#{File.expand_path path, load_path}#{Gem.suffix_pattern}"] + globbed = Gem::Util.glob_files_in_dir(glob, load_path) globbed.each do |load_path_file| files << load_path_file if File.file?(load_path_file.untaint) diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index 72954a7863..54242983ff 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -152,7 +152,7 @@ class Gem::BasicSpecification # The path to the data directory for this gem. def datadir -# TODO: drop the extra ", gem_name" which is uselessly redundant + # TODO: drop the extra ", gem_name" which is uselessly redundant File.expand_path(File.join(gems_dir, full_name, "data", name)).untaint end @@ -282,7 +282,7 @@ class Gem::BasicSpecification self.raw_require_paths.first end else - "lib" # default value for require_paths for bundler/inline + "lib" # default value for require_paths for bundler/inline end "#{self.full_gem_path}/#{dirs}".dup.untaint diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index e22dc5deb3..3dc5779c91 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -71,6 +71,10 @@ class Gem::CommandManager :yank, ] + ALIAS_COMMANDS = { + 'i' => 'install' + } + ## # Return the authoritative instance of the command manager. @@ -174,6 +178,8 @@ class Gem::CommandManager end def find_command(cmd_name) + cmd_name = find_alias_command cmd_name + possibilities = find_command_possibilities cmd_name if possibilities.size > 1 then @@ -186,6 +192,11 @@ class Gem::CommandManager self[possibilities.first] end + def find_alias_command(cmd_name) + alias_name = ALIAS_COMMANDS[cmd_name] + alias_name ? alias_name : cmd_name + end + def find_command_possibilities(cmd_name) len = cmd_name.length diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 0ba24e5ea3..f1d700349f 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -10,6 +10,10 @@ class Gem::Commands::BuildCommand < Gem::Command add_option '--force', 'skip validation of the spec' do |value, options| options[:force] = true end + + add_option '--strict', 'consider warnings as errors when validating the spec' do |value, options| + options[:strict] = true + end end def arguments # :nodoc: @@ -51,7 +55,7 @@ with gem spec: spec = Gem::Specification.load File.basename(gemspec) if spec then - Gem::Package.build spec, options[:force] + Gem::Package.build spec, options[:force], options[:strict] else alert_error "Error loading gemspec. Aborting." terminate_interaction 1 diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 5542262a50..aa26f340ff 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -87,7 +87,7 @@ class Gem::Commands::CertCommand < Gem::Command add_option('-d', '--days NUMBER_OF_DAYS', 'Days before the certificate expires') do |days, options| - options[:expiration_length_days] = days.to_i + options[:expiration_length_days] = days.to_i end end diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 79c23c840d..fe85deddda 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -22,6 +22,12 @@ class Gem::Commands::CleanupCommand < Gem::Command options[:check_dev] = value end + add_option('--[no-]user-install', + 'Cleanup in user\'s home directory instead', + 'of GEM_HOME.') do |value, options| + options[:user_install] = value + end + @candidate_gems = nil @default_gems = [] @full = nil @@ -124,8 +130,10 @@ If no gems are named all gems in GEM_HOME are cleaned. spec.default_gem? } + uninstall_from = options[:user_install] ? Gem.user_dir : @original_home + gems_to_cleanup = gems_to_cleanup.select { |spec| - spec.base_dir == @original_home + spec.base_dir == uninstall_from } @default_gems += default_gems diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index c6abf9cd7c..9fe131c290 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -117,6 +117,13 @@ to write the specification by hand. For example: some_extension_gem (1.0) $ +Command Alias +========================== + +You can use `i` command instead of `install`. + + $ gem i GEMNAME + EOF end diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb index 059635e835..fdac19dc1f 100644 --- a/lib/rubygems/commands/open_command.rb +++ b/lib/rubygems/commands/open_command.rb @@ -60,8 +60,14 @@ class Gem::Commands::OpenCommand < Gem::Command def open_gem name spec = spec_for name + return false unless spec + if spec.default_gem? + say "'#{name}' is a default gem and can't be opened." + return false + end + open_editor(spec.full_gem_path) end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 817e752266..575c344130 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -46,6 +46,12 @@ class Gem::Commands::PristineCommand < Gem::Command options[:env_shebang] = value end + add_option('-n', '--bindir DIR', + 'Directory where executables are', + 'located') do |value, options| + options[:bin_dir] = File.expand_path(value) + end + add_version_option('restore to', 'pristine condition') end @@ -160,12 +166,15 @@ extensions will be restored. install_defaults.to_s['--env-shebang'] end + bin_dir = options[:bin_dir] if options[:bin_dir] + installer_options = { :wrappers => true, :force => true, :install_dir => spec.base_dir, :env_shebang => env_shebang, :build_args => spec.build_args, + :bin_dir => bin_dir } if options[:only_executables] then diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 613c4e6ddf..83c7131afc 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -29,6 +29,8 @@ command. For further discussion see the help for the yank command. def initialize super 'push', 'Push a gem up to the gem server', :host => self.host + @user_defined_host = false + add_proxy_option add_key_option @@ -36,20 +38,41 @@ command. For further discussion see the help for the yank command. 'Push to another gemcutter-compatible host', ' (e.g. https://rubygems.org)') do |value, options| options[:host] = value + @user_defined_host = true end @host = nil end def execute - @host = options[:host] + gem_name = get_one_gem_name + default_gem_server, push_host = get_hosts_for(gem_name) + + default_host = nil + user_defined_host = nil + + if @user_defined_host + user_defined_host = options[:host] + else + default_host = options[:host] + end + + @host = if user_defined_host + user_defined_host + elsif default_gem_server + default_gem_server + elsif push_host + push_host + else + default_host + end sign_in @host - send_gem get_one_gem_name + send_gem(gem_name) end - def send_gem name + def send_gem(name) args = [:post, "api/v1/gems"] latest_rubygems_version = Gem.latest_rubygems_version @@ -100,5 +123,15 @@ You can upgrade or downgrade to the latest release version with: with_response response end + private + + def get_hosts_for(name) + gem_metadata = Gem::Package.new(name).spec.metadata + + [ + gem_metadata["default_gem_server"], + gem_metadata["allowed_push_host"] + ] + end end diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index fc87063fe3..281108ea1f 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -84,7 +84,7 @@ class Gem::Commands::SetupCommand < Gem::Command add_option '--[no-]regenerate-binstubs', 'Regenerate gem binstubs' do |value, options| - options[:regenerate_binstubs] = value + options[:regenerate_binstubs] = value end add_option('-E', '--[no-]env-shebang', @@ -468,8 +468,8 @@ By default, this RubyGems will install gem as: (prefix == RbConfig::CONFIG['libdir'] or # this one is important prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby')) then - lib_dir = RbConfig::CONFIG[site_or_vendor] - bin_dir = RbConfig::CONFIG['bindir'] + lib_dir = RbConfig::CONFIG[site_or_vendor] + bin_dir = RbConfig::CONFIG['bindir'] else lib_dir = File.join prefix, 'lib' bin_dir = File.join prefix, 'bin' diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb index 6556db5a89..a48c32b52e 100644 --- a/lib/rubygems/commands/signin_command.rb +++ b/lib/rubygems/commands/signin_command.rb @@ -10,7 +10,7 @@ class Gem::Commands::SigninCommand < Gem::Command 'It defaults to https://rubygems.org' add_option('--host HOST', 'Push to another gemcutter-compatible host') do |value, options| - options[:host] = value + options[:host] = value end end diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index 55a052284a..1ddc12c737 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -48,7 +48,7 @@ class Gem::Commands::UninstallCommand < Gem::Command end add_option('-n', '--bindir DIR', - 'Directory to remove binaries from') do |value, options| + 'Directory to remove executables from') do |value, options| options[:bin_dir] = File.expand_path(value) end diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index 4b474e1d19..89ce9afe29 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -458,7 +458,7 @@ class Gem::DependencyInstaller rescue Gem::Package::FormatError end end - # else This is a dependency. InstallerSet handles this case + # else This is a dependency. InstallerSet handles this case end end diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 805ef02422..6382a8f5c7 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -6,7 +6,6 @@ #++ require 'rubygems/user_interaction' -require 'thread' class Gem::Ext::Builder diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index 2e59e790d4..5607bf3c77 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -271,7 +271,7 @@ class Gem::Indexer # List of gem file names to index. def gem_file_list - Dir[File.join(@dest_directory, "gems", '*.gem')] + Gem::Util.glob_files_in_dir("*.gem", File.join(@dest_directory, "gems")) end ## diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 190372739b..75968605f1 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -25,7 +25,7 @@ module Gem::InstallUpdateOptions end add_option(:"Install/Update", '-n', '--bindir DIR', - 'Directory where binary files are', + 'Directory where executables are', 'located') do |value, options| options[:bin_dir] = File.expand_path(value) end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index b142454c09..e9ad4d03d4 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -187,6 +187,8 @@ class Gem::Installer @package.prog_mode = options[:prog_mode] @package.data_mode = options[:data_mode] + @bin_dir = options[:bin_dir] if options[:bin_dir] + if options[:user_install] and not options[:unpack] then @gem_home = Gem.user_dir @bin_dir = Gem.bindir gem_home unless options[:bin_dir] @@ -379,7 +381,7 @@ class Gem::Installer @specs ||= begin specs = [] - Dir[File.join(gem_home, "specifications", "*.gemspec")].each do |path| + Gem::Util.glob_files_in_dir("*.gemspec", File.join(gem_home, "specifications")).each do |path| spec = Gem::Specification.load path.untaint specs << spec if spec end @@ -769,15 +771,30 @@ TEXT # return the stub script text used to launch the true Ruby script def windows_stub_script(bindir, bin_file_name) - ruby = Gem.ruby.gsub(/^\"|\"$/, "").tr(File::SEPARATOR, "\\") - return <<-TEXT + # All comparisons should be case insensitive + if bindir.downcase == RbConfig::CONFIG["bindir"].downcase + # stub & ruby.exe withing same folder. Portable + <<-TEXT @ECHO OFF -IF NOT "%~f0" == "~f0" GOTO :WinNT -@"#{ruby}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9 -GOTO :EOF -:WinNT -@"#{ruby}" "%~dpn0" %* -TEXT +@"%~dp0ruby.exe" "%~dpn0" %* + TEXT + elsif bindir.downcase.start_with? RbConfig::TOPDIR.downcase + # stub within ruby folder, but not standard bin. Not portable + require 'pathname' + from = Pathname.new bindir + to = Pathname.new RbConfig::CONFIG["bindir"] + rel = to.relative_path_from from + <<-TEXT +@ECHO OFF +@"%~dp0#{rel}/ruby.exe" "%~dpn0" %* + TEXT + else + # outside ruby folder, maybe -user-install or bundler. Portable + <<-TEXT +@ECHO OFF [email protected] "%~dpn0" %* + TEXT + end end ## diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index b20334c8ca..ec9541d19b 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -119,12 +119,12 @@ class Gem::Package # Permission for other files attr_accessor :data_mode - def self.build spec, skip_validation=false + def self.build spec, skip_validation=false, strict_validation=false gem_file = spec.file_name package = new gem_file package.spec = spec - package.build skip_validation + package.build skip_validation, strict_validation gem_file end @@ -254,12 +254,14 @@ class Gem::Package ## # Builds this package based on the specification set by #spec= - def build skip_validation = false + def build skip_validation = false, strict_validation = false + raise ArgumentError, "skip_validation = true and strict_validation = true are incompatible" if skip_validation && strict_validation + Gem.load_yaml require 'rubygems/security' @spec.mark_version - @spec.validate unless skip_validation + @spec.validate true, strict_validation unless skip_validation setup_signer diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb index b6fb8c3a3a..77b06af233 100644 --- a/lib/rubygems/package/tar_reader/entry.rb +++ b/lib/rubygems/package/tar_reader/entry.rb @@ -119,6 +119,12 @@ class Gem::Package::TarReader::Entry bytes_read end + def size + @header.size + end + + alias length size + ## # Reads +len+ bytes from the tar file entry, or the rest of the entry if # nil @@ -137,7 +143,19 @@ class Gem::Package::TarReader::Entry ret end - alias readpartial read # :nodoc: + def readpartial(maxlen = nil, outbuf = "".b) + check_closed + + raise EOFError if @read >= @header.size + + maxlen ||= @header.size - @read + max_read = [maxlen, @header.size - @read].min + + @io.readpartial(max_read, outbuf) + @read += outbuf.size + + outbuf + end ## # Rewinds to the beginning of the tar file entry diff --git a/lib/rubygems/path_support.rb b/lib/rubygems/path_support.rb index 618bc793c4..02332cef80 100644 --- a/lib/rubygems/path_support.rb +++ b/lib/rubygems/path_support.rb @@ -23,12 +23,14 @@ class Gem::PathSupport # hashtable, or defaults to ENV, the system environment. # def initialize(env) - @home = env["GEM_HOME"] || Gem.default_dir + @home = env["GEM_HOME"] || Gem.default_dir if File::ALT_SEPARATOR then - @home = @home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) + @home = @home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) end + @home = expand(@home) + @path = split_gem_path env["GEM_PATH"], @home @spec_cache_dir = env["GEM_SPEC_CACHE"] || Gem.default_spec_cache_dir @@ -65,7 +67,7 @@ class Gem::PathSupport gem_path = default_path end - gem_path.uniq + gem_path.map { |path| expand(path) }.uniq end # Return the default Gem path @@ -77,4 +79,12 @@ class Gem::PathSupport end gem_path end + + def expand(path) + if File.directory?(path) + File.realpath(path) + else + path + end + end end diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index a829268f39..940523f246 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -384,17 +384,15 @@ class Gem::RemoteFetcher require 'base64' require 'openssl' - unless uri.user && uri.password - raise FetchError.new("credentials needed in s3 source, like s3://key:secret@bucket-name/", uri.to_s) - end + id, secret = s3_source_auth uri expiration ||= s3_expiration canonical_path = "/#{uri.host}#{uri.path}" payload = "GET\n\n\n#{expiration}\n#{canonical_path}" - digest = OpenSSL::HMAC.digest('sha1', uri.password, payload) + digest = OpenSSL::HMAC.digest('sha1', secret, payload) # URI.escape is deprecated, and there isn't yet a replacement that does quite what we want signature = Base64.encode64(digest).gsub("\n", '').gsub(/[\+\/=]/) { |c| BASE64_URI_TRANSLATE[c] } - URI.parse("https://#{uri.host}.s3.amazonaws.com#{uri.path}?AWSAccessKeyId=#{uri.user}&Expires=#{expiration}&Signature=#{signature}") + URI.parse("https://#{uri.host}.s3.amazonaws.com#{uri.path}?AWSAccessKeyId=#{id}&Expires=#{expiration}&Signature=#{signature}") end def s3_expiration @@ -414,4 +412,21 @@ class Gem::RemoteFetcher @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files end end + + def s3_source_auth(uri) + return [uri.user, uri.password] if uri.user && uri.password + + s3_source = Gem.configuration[:s3_source] || Gem.configuration['s3_source'] + host = uri.host + raise FetchError.new("no s3_source key exists in .gemrc", "s3://#{host}") unless s3_source + + auth = s3_source[host] || s3_source[host.to_sym] + raise FetchError.new("no key for host #{host} in s3_source in .gemrc", "s3://#{host}") unless auth + + id = auth[:id] || auth['id'] + secret = auth[:secret] || auth['secret'] + raise FetchError.new("s3_source for #{host} missing id or secret", "s3://#{host}") unless id and secret + + [id, secret] + end end diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 81699b98fe..d8d5d1bc31 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require 'net/http' -require 'thread' require 'time' require 'rubygems/user_interaction' diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index d70dea785a..f95dd8aabf 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'thread' class Gem::Request::ConnectionPools # :nodoc: diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 89f47616ec..ed99d29295 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -417,7 +417,7 @@ class Gem::RequestSet end def specs_in dir - Dir["#{dir}/specifications/*.gemspec"].map do |g| + Gem::Util.glob_files_in_dir("*.gemspec", File.join(dir, "specifications")).map do |g| Gem::Specification.load g end end diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 0717739dc0..430f351280 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -284,7 +284,7 @@ class Gem::Requirement end def sort_requirements! # :nodoc: - @requirements.sort! do |l, r| + @requirements.sort! do |l, r| comp = l.last <=> r.last # first, sort by the requirement's version next comp unless comp == 0 l.first <=> r.first # then, sort by the operator (for stability) diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index 0c6ef60a9a..1ee9c31be6 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -2,8 +2,12 @@ ## # Basic OpenSSL-based package signing class. +require "rubygems/user_interaction" + class Gem::Security::Signer + include Gem::UserInteraction + ## # The chain of certificates for signing including the signing certificate @@ -33,6 +37,7 @@ class Gem::Security::Signer def initialize key, cert_chain, passphrase = nil @cert_chain = cert_chain @key = key + @passphrase = passphrase unless @key then default_key = File.join Gem.default_key_path @@ -47,8 +52,10 @@ class Gem::Security::Signer @digest_algorithm = Gem::Security::DIGEST_ALGORITHM @digest_name = Gem::Security::DIGEST_NAME - @key = OpenSSL::PKey::RSA.new File.read(@key), passphrase if - @key and not OpenSSL::PKey::RSA === @key + if @key && [email protected]_a?(OpenSSL::PKey::RSA) + @passphrase ||= ask_for_password("Enter PEM pass phrase:") + @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase) + end if @cert_chain then @cert_chain = @cert_chain.compact.map do |cert| @@ -121,6 +128,7 @@ class Gem::Security::Signer # The key will be re-signed if: # * The expired certificate is self-signed # * The expired certificate is saved at ~/.gem/gem-public_cert.pem + # and the private key is saved at ~/.gem/gem-private_key.pem # * There is no file matching the expiry date at # ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S # @@ -131,22 +139,29 @@ class Gem::Security::Signer def re_sign_key # :nodoc: old_cert = @cert_chain.last - disk_cert_path = File.join Gem.default_cert_path - disk_cert = File.read disk_cert_path rescue nil - disk_key = - File.read File.join(Gem.default_key_path) rescue nil + disk_cert_path = File.join(Gem.default_cert_path) + disk_cert = File.read(disk_cert_path) rescue nil - if disk_key == @key.to_pem and disk_cert == old_cert.to_pem then - expiry = old_cert.not_after.strftime '%Y%m%d%H%M%S' + disk_key_path = File.join(Gem.default_key_path) + disk_key = + OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil + + return unless disk_key + + if disk_key.to_pem == @key.to_pem && disk_cert == old_cert.to_pem + expiry = old_cert.not_after.strftime('%Y%m%d%H%M%S') old_cert_file = "gem-public_cert.pem.expired.#{expiry}" - old_cert_path = File.join Gem.user_home, ".gem", old_cert_file + old_cert_path = File.join(Gem.user_home, ".gem", old_cert_file) + + unless File.exist?(old_cert_path) + Gem::Security.write(old_cert, old_cert_path) - unless File.exist? old_cert_path then - Gem::Security.write old_cert, old_cert_path + cert = Gem::Security.re_sign(old_cert, @key) - cert = Gem::Security.re_sign old_cert, @key + Gem::Security.write(cert, disk_cert_path) - Gem::Security.write cert, disk_cert_path + alert("Your cert: #{disk_cert_path} has been auto re-signed with the key: #{disk_key_path}") + alert("Your expired cert will be located at: #{old_cert_path}") @cert_chain = [cert] end diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index 919276e113..4d224ca173 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -202,7 +202,7 @@ class Gem::SpecFetcher }.compact matches = if matches.empty? && type != :prerelease - suggest_gems_from_name gem_name, :prerelease + suggest_gems_from_name gem_name, :prerelease else matches.uniq.sort_by { |name, dist| dist } end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 2fe315f4f6..1ec4ed9227 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -172,9 +172,9 @@ class Gem::Specification < Gem::BasicSpecification when String v.dump when Numeric - "default_value(:#{k})" + "default_value(:#{k})" else - "default_value(:#{k}).dup" + "default_value(:#{k}).dup" end end @@ -761,14 +761,14 @@ class Gem::Specification < Gem::BasicSpecification def self.each_gemspec(dirs) # :nodoc: dirs.each do |dir| - Dir[File.join(dir, "*.gemspec")].each do |path| + Gem::Util.glob_files_in_dir("*.gemspec", dir).each do |path| yield path.untaint end end end def self.gemspec_stubs_in dir, pattern - Dir[File.join(dir, pattern)].map { |path| yield path }.select(&:valid?) + Gem::Util.glob_files_in_dir(pattern, dir).map { |path| yield path }.select(&:valid?) end private_class_method :gemspec_stubs_in @@ -820,11 +820,11 @@ class Gem::Specification < Gem::BasicSpecification def self.stubs @@stubs ||= begin pattern = "*.gemspec" - stubs = default_stubs(pattern).concat installed_stubs(dirs, pattern) + stubs = Gem.loaded_specs.values + default_stubs(pattern) + installed_stubs(dirs, pattern) stubs = uniq_by(stubs) { |stub| stub.full_name } _resort!(stubs) - @@stubs_by_name = stubs.group_by(&:name) + @@stubs_by_name = stubs.select { |s| Gem::Platform.match s.platform }.group_by(&:name) stubs end end @@ -833,13 +833,15 @@ class Gem::Specification < Gem::BasicSpecification ## # Returns a Gem::StubSpecification for installed gem named +name+ + # only returns stubs that match Gem.platforms def self.stubs_for name if @@stubs @@stubs_by_name[name] || [] else pattern = "#{name}-*.gemspec" - stubs = default_stubs(pattern) + installed_stubs(dirs, pattern) + stubs = Gem.loaded_specs.values + default_stubs(pattern) + + installed_stubs(dirs, pattern).select { |s| Gem::Platform.match s.platform } stubs = uniq_by(stubs) { |stub| stub.full_name }.group_by(&:name) stubs.each_value { |v| _resort!(v) } @@ -1280,11 +1282,17 @@ class Gem::Specification < Gem::BasicSpecification unresolved = unresolved_deps unless unresolved.empty? then w = "W" + "ARN" - warn "#{w}: Unresolved specs during Gem::Specification.reset:" + warn "#{w}: Unresolved or ambigious specs during Gem::Specification.reset:" unresolved.values.each do |dep| warn " #{dep}" + + versions = find_all_by_name(dep.name) + unless versions.empty? + warn " Available/installed versions of this gem:" + versions.each { |s| warn " - #{s.version}" } + end end - warn "#{w}: Clearing out unresolved specs." + warn "#{w}: Clearing out unresolved specs. Try 'gem cleanup <gem>'" warn "Please report a bug if this causes problems." unresolved.clear end @@ -2645,19 +2653,14 @@ class Gem::Specification < Gem::BasicSpecification # Raises InvalidSpecificationException if the spec does not pass the # checks.. - def validate packaging = true - @warnings = 0 + def validate packaging = true, strict = false require 'rubygems/user_interaction' extend Gem::UserInteraction normalize validation_policy = Gem::SpecificationPolicy.new(self) validation_policy.packaging = packaging - validation_policy.validate - ensure - if $! or @warnings > 0 then - alert_warning "See http://guides.rubygems.org/specification-reference/ for help" - end + validation_policy.validate(strict) end def keep_only_files_and_directories @@ -2744,12 +2747,6 @@ class Gem::Specification < Gem::BasicSpecification @installed_by_version ||= nil end - def warning statement # :nodoc: - @warnings += 1 - - alert_warning statement - end - def raw_require_paths # :nodoc: @require_paths end diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index cb8457ed62..853cca5126 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -16,6 +16,12 @@ class Gem::SpecificationPolicy < SimpleDelegator wiki_uri ] # :nodoc: + def initialize(specification) + @warnings = 0 + + super(specification) + end + ## # If set to true, run packaging-specific checks, as well. @@ -28,7 +34,7 @@ class Gem::SpecificationPolicy < SimpleDelegator # Raises InvalidSpecificationException if the spec does not pass the # checks. - def validate + def validate(strict = false) validate_nil_attributes validate_rubygems_version @@ -64,6 +70,15 @@ class Gem::SpecificationPolicy < SimpleDelegator validate_values validate_dependencies + + if @warnings > 0 + if strict + error "specification has warnings" + else + alert_warning help_text + end + end + true end @@ -72,35 +87,29 @@ class Gem::SpecificationPolicy < SimpleDelegator def validate_metadata unless Hash === metadata then - raise Gem::InvalidSpecificationException, - 'metadata must be a hash' + error 'metadata must be a hash' end metadata.each do |key, value| if !key.kind_of?(String) then - raise Gem::InvalidSpecificationException, - "metadata keys must be a String" + error "metadata keys must be a String" end if key.size > 128 then - raise Gem::InvalidSpecificationException, - "metadata key too large (#{key.size} > 128)" + error "metadata key too large (#{key.size} > 128)" end if !value.kind_of?(String) then - raise Gem::InvalidSpecificationException, - "metadata values must be a String" + error "metadata values must be a String" end if value.size > 1024 then - raise Gem::InvalidSpecificationException, - "metadata value too large (#{value.size} > 1024)" + error "metadata value too large (#{value.size} > 1024)" end if METADATA_LINK_KEYS.include? key then if value !~ VALID_URI_PATTERN then - raise Gem::InvalidSpecificationException, - "metadata['#{key}'] has invalid link: #{value.inspect}" + error "metadata['#{key}'] has invalid link: #{value.inspect}" end end end @@ -132,30 +141,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: warning_messages << "prerelease dependency on #{dep} is not recommended" if prerelease_dep && !version.prerelease? - overly_strict = dep.requirement.requirements.length == 1 && - dep.requirement.requirements.any? do |op, version| - op == '~>' and - not version.prerelease? and - version.segments.length > 2 and - version.segments.first != 0 - end - - if overly_strict then - _, dep_version = dep.requirement.requirements.first - - base = dep_version.segments.first 2 - upper_bound = dep_version.segments.first(dep_version.segments.length - 1) - upper_bound[-1] += 1 - - warning_messages << <<-WARNING -pessimistic dependency on #{dep} may be overly strict - if #{dep.name} is semantically versioned, use: - add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}', '>= #{dep_version}' - if #{dep.name} is not semantically versioned, you can bypass this warning with: - add_#{dep.type}_dependency '#{dep.name}', '>= #{dep_version}', '< #{upper_bound.join '.'}.a' - WARNING - end - open_ended = dep.requirement.requirements.all? do |op, version| not version.prerelease? and (op == '>' or op == '>=') end @@ -179,7 +164,7 @@ open-ended dependency on #{dep} is not recommended end end if error_messages.any? then - raise Gem::InvalidSpecificationException, error_messages.join + error error_messages.join end if warning_messages.any? then warning_messages.each { |warning_message| warning warning_message } @@ -215,45 +200,38 @@ open-ended dependency on #{dep} is not recommended __getobj__.instance_variable_get("@#{attrname}").nil? end return if nil_attributes.empty? - raise Gem::InvalidSpecificationException, - "#{nil_attributes.join ', '} must not be nil" + error "#{nil_attributes.join ', '} must not be nil" end def validate_rubygems_version return unless packaging return if rubygems_version == Gem::VERSION - raise Gem::InvalidSpecificationException, - "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" + error "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" end def validate_required_attributes Gem::Specification.required_attributes.each do |symbol| unless send symbol then - raise Gem::InvalidSpecificationException, - "missing value for attribute #{symbol}" + error "missing value for attribute #{symbol}" end end end def validate_name if !name.is_a?(String) then - raise Gem::InvalidSpecificationException, - "invalid value for attribute name: \"#{name.inspect}\" must be a string" + error "invalid value for attribute name: \"#{name.inspect}\" must be a string" elsif name !~ /[a-zA-Z]/ then - raise Gem::InvalidSpecificationException, - "invalid value for attribute name: #{name.dump} must include at least one letter" + error "invalid value for attribute name: #{name.dump} must include at least one letter" elsif name !~ VALID_NAME_PATTERN then - raise Gem::InvalidSpecificationException, - "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores" + error "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores" end end def validate_require_paths return unless raw_require_paths.empty? - raise Gem::InvalidSpecificationException, - 'specification must have at least one require_path' + error 'specification must have at least one require_path' end def validate_non_files @@ -261,31 +239,27 @@ open-ended dependency on #{dep} is not recommended non_files = files.reject {|x| File.file?(x) || File.symlink?(x)} unless non_files.empty? then - raise Gem::InvalidSpecificationException, - "[\"#{non_files.join "\", \""}\"] are not files" + error "[\"#{non_files.join "\", \""}\"] are not files" end end def validate_self_inclusion_in_files_list return unless files.include?(file_name) - - raise Gem::InvalidSpecificationException, - "#{full_name} contains itself (#{file_name}), check your files list" + + error "#{full_name} contains itself (#{file_name}), check your files list" end def validate_specification_version return if specification_version.is_a?(Integer) - - raise Gem::InvalidSpecificationException, - 'specification_version must be an Integer (did you mean version?)' + + error 'specification_version must be an Integer (did you mean version?)' end def validate_platform case platform when Gem::Platform, Gem::Platform::RUBY then # ok else - raise Gem::InvalidSpecificationException, - "invalid platform #{platform.inspect}, see Gem::Platform" + error "invalid platform #{platform.inspect}, see Gem::Platform" end end @@ -313,15 +287,13 @@ open-ended dependency on #{dep} is not recommended def validate_authors_field return unless authors.empty? - raise Gem::InvalidSpecificationException, - "authors may not be empty" + error "authors may not be empty" end def validate_licenses licenses.each { |license| if license.length > 64 then - raise Gem::InvalidSpecificationException, - "each license must be 64 characters or less" + error "each license must be 64 characters or less" end if !Gem::Licenses.match?(license) then @@ -347,19 +319,19 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li def validate_lazy_metadata unless authors.grep(LAZY_PATTERN).empty? then - raise Gem::InvalidSpecificationException, "#{LAZY} is not an author" + error "#{LAZY} is not an author" end unless Array(email).grep(LAZY_PATTERN).empty? then - raise Gem::InvalidSpecificationException, "#{LAZY} is not an email" + error "#{LAZY} is not an email" end if description =~ LAZY_PATTERN then - raise Gem::InvalidSpecificationException, "#{LAZY} is not a description" + error "#{LAZY} is not a description" end if summary =~ LAZY_PATTERN then - raise Gem::InvalidSpecificationException, "#{LAZY} is not a summary" + error "#{LAZY} is not a summary" end # Make sure a homepage is valid HTTP/HTTPS URI @@ -367,10 +339,10 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li begin homepage_uri = URI.parse(homepage) unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class - raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" + error "\"#{homepage}\" is not a valid HTTP URI" end rescue URI::InvalidURIError - raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" + error "\"#{homepage}\" is not a valid HTTP URI" end end end @@ -407,4 +379,20 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li warning "#{executable_path} is missing #! line" end + + def warning statement # :nodoc: + @warnings += 1 + + alert_warning statement + end + + def error statement # :nodoc: + raise Gem::InvalidSpecificationException, statement + ensure + alert_warning help_text + end + + def help_text # :nodoc: + "See http://guides.rubygems.org/specification-reference/ for help" + end end diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index f1cd3d274c..ced33c4d11 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -13,6 +13,15 @@ else require 'rubygems' end +# If bundler gemspec exists, add to stubs +bundler_gemspec = File.expand_path("../../../bundler/bundler.gemspec", __FILE__) +if File.exist?(bundler_gemspec) + Gem::Specification.dirs.unshift File.dirname(bundler_gemspec) + Gem::Specification.class_variable_set :@@stubs, nil + Gem::Specification.stubs + Gem::Specification.dirs.shift +end + begin gem 'minitest' rescue Gem::LoadError @@ -382,6 +391,11 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni util_set_arch 'i686-darwin8.10.1' end + @orig_hooks = {} + %w[post_install_hooks done_installing_hooks post_uninstall_hooks pre_uninstall_hooks pre_install_hooks pre_reset_hooks post_reset_hooks post_build_hooks].each do |name| + @orig_hooks[name] = Gem.send(name).dup + end + @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" @orig_LOADED_FEATURES = $LOADED_FEATURES.dup end @@ -449,6 +463,10 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni Gem::Specification.unresolved_deps.clear Gem::refresh + @orig_hooks.each do |name, hooks| + Gem.send(name).replace hooks + end + @back_ui.close end diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 9f5b9a2239..6dbcd4ba21 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -80,8 +80,6 @@ module Gem::Util end return system(*(cmds << opt)) rescue TypeError - require 'thread' - @silent_mutex ||= Mutex.new @silent_mutex.synchronize do @@ -118,4 +116,16 @@ module Gem::Util end end + ## + # Globs for files matching +pattern+ inside of +directory+, + # returning absolute paths to the matching files. + + def self.glob_files_in_dir(glob, base_path) + if RUBY_VERSION >= "2.5" + Dir.glob(glob, base: base_path).map! {|f| File.join(base_path, f) } + else + Dir.glob(File.expand_path(glob, base_path)) + end + end + end diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index fbf75996cd..08f0d1e7a5 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -170,7 +170,10 @@ class Gem::Version # True if the +version+ string matches RubyGems' requirements. def self.correct? version - return false if version.nil? + unless Gem::Deprecate.skip + warn "nil versions are discouraged and will be deprecated in Rubygems 4" if version.nil? + end + !!(version.to_s =~ ANCHORED_VERSION_PATTERN) end @@ -325,7 +328,9 @@ class Gem::Version segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 - "~> #{segments.join(".")}" + recommendation = "~> #{segments.join(".")}" + recommendation += ".a" if prerelease? + recommendation end ## |