1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Prepare to release RubyGems 3.1.0 final version.

This commit is contained in:
Hiroshi SHIBATA 2019-12-13 20:19:08 +09:00
parent 26774351dc
commit 82cc2843a9
No known key found for this signature in database
GPG key ID: F9CF13417264FAC2
23 changed files with 393 additions and 142 deletions

View file

@ -160,24 +160,14 @@ module Gem
].freeze ].freeze
## ##
# Exception classes used in a Gem.read_binary +rescue+ statement. Not all of # Exception classes used in a Gem.read_binary +rescue+ statement
# these are defined in Ruby 1.8.7, hence the need for this convoluted setup.
READ_BINARY_ERRORS = begin READ_BINARY_ERRORS = [Errno::EACCES, Errno::EROFS, Errno::ENOSYS, Errno::ENOTSUP].freeze
read_binary_errors = [Errno::EACCES, Errno::EROFS, Errno::ENOSYS]
read_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP)
read_binary_errors
end.freeze
## ##
# Exception classes used in Gem.write_binary +rescue+ statement. Not all of # Exception classes used in Gem.write_binary +rescue+ statement
# these are defined in Ruby 1.8.7.
WRITE_BINARY_ERRORS = begin WRITE_BINARY_ERRORS = [Errno::ENOSYS, Errno::ENOTSUP].freeze
write_binary_errors = [Errno::ENOSYS]
write_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP)
write_binary_errors
end.freeze
@@win_platform = nil @@win_platform = nil

View file

@ -369,22 +369,44 @@ class Gem::Command
end end
end end
def deprecate_option(short_name: nil, long_name: nil, version: nil) ##
@deprecated_options[command].merge!({ short_name => { "rg_version_to_expire" => version } }) if short_name # Mark a command-line option as deprecated, and optionally specify a
@deprecated_options[command].merge!({ long_name => { "rg_version_to_expire" => version } }) if long_name # deprecation horizon.
#
# Note that with the current implementation, every version of the option needs
# to be explicitly deprecated, so to deprecate an option defined as
#
# add_option('-t', '--[no-]test', 'Set test mode') do |value, options|
# # ... stuff ...
# end
#
# you would need to explicitly add a call to `deprecate_option` for every
# version of the option you want to deprecate, like
#
# deprecate_option('-t')
# deprecate_option('--test')
# deprecate_option('--no-test')
def deprecate_option(name, version: nil, extra_msg: nil)
@deprecated_options[command].merge!({ name => { "rg_version_to_expire" => version, "extra_msg" => extra_msg } })
end end
def check_deprecated_options(options) def check_deprecated_options(options)
options.each do |option| options.each do |option|
if option_is_deprecated?(option) if option_is_deprecated?(option)
version_to_expire = @deprecated_options[command][option]["rg_version_to_expire"] deprecation = @deprecated_options[command][option]
version_to_expire = deprecation["rg_version_to_expire"]
deprecate_option_msg = if version_to_expire deprecate_option_msg = if version_to_expire
"The \"#{option}\" option has been deprecated and will be removed in Rubygems #{version_to_expire}, its use is discouraged." "The \"#{option}\" option has been deprecated and will be removed in Rubygems #{version_to_expire}."
else else
"The \"#{option}\" option has been deprecated and will be removed in future versions of Rubygems, its use is discouraged." "The \"#{option}\" option has been deprecated and will be removed in future versions of Rubygems."
end end
extra_msg = deprecation["extra_msg"]
deprecate_option_msg += " #{extra_msg}" if extra_msg
alert_warning(deprecate_option_msg) alert_warning(deprecate_option_msg)
end end
end end

View file

@ -25,6 +25,9 @@ class Gem::Commands::GenerateIndexCommand < Gem::Command
options[:build_modern] = value options[:build_modern] = value
end end
deprecate_option('--modern', version: '4.0', extra_msg: 'Modern indexes (specs, latest_specs, and prerelease_specs) are always generated, so this option is not needed.')
deprecate_option('--no-modern', version: '4.0', extra_msg: 'The `--no-modern` option is currently ignored. Modern indexes (specs, latest_specs, and prerelease_specs) are always generated.')
add_option '--update', add_option '--update',
'Update modern indexes with gems added', 'Update modern indexes with gems added',
'since the last update' do |value, options| 'since the last update' do |value, options|

View file

@ -98,7 +98,7 @@ class Gem::Commands::SetupCommand < Gem::Command
end end
def check_ruby_version def check_ruby_version
required_version = Gem::Requirement.new '>= 1.8.7' required_version = Gem::Requirement.new '>= 2.3.0'
unless required_version.satisfied_by? Gem.ruby_version unless required_version.satisfied_by? Gem.ruby_version
alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}"

View file

@ -43,6 +43,8 @@ class Gem::Commands::SourcesCommand < Gem::Command
source = Gem::Source.new source_uri source = Gem::Source.new source_uri
check_typo_squatting(source)
begin begin
if Gem.sources.include? source if Gem.sources.include? source
say "source #{source_uri} already present in the cache" say "source #{source_uri} already present in the cache"
@ -62,6 +64,18 @@ class Gem::Commands::SourcesCommand < Gem::Command
end end
end end
def check_typo_squatting(source)
if source.typo_squatting?("rubygems.org")
question = <<-QUESTION.chomp
#{source.uri.to_s} is too similar to https://rubygems.org
Do you want to add this source?
QUESTION
terminate_interaction 1 unless ask_yes_no question
end
end
def check_rubygems_https(source_uri) # :nodoc: def check_rubygems_https(source_uri) # :nodoc:
uri = URI source_uri uri = URI source_uri

View file

@ -6,13 +6,17 @@ if RUBY_VERSION >= "2.5"
module Kernel module Kernel
path = "#{__dir__}/" # Frames to be skipped start with this path. path = "#{__dir__}/" # Frames to be skipped start with this path.
# Suppress "method redefined" warning
original_warn = instance_method(:warn)
Module.new {define_method(:warn, original_warn)}
original_warn = method(:warn) original_warn = method(:warn)
module_function define_method(:warn) {|*messages, **kw| remove_method :warn
class << self
remove_method :warn
end
define_method(:warn) do |*messages, **kw|
unless uplevel = kw[:uplevel] unless uplevel = kw[:uplevel]
if Gem.java_platform? if Gem.java_platform?
return original_warn.call(*messages) return original_warn.call(*messages)
@ -46,6 +50,6 @@ if RUBY_VERSION >= "2.5"
kw[:uplevel] = uplevel kw[:uplevel] = uplevel
original_warn.call(*messages, **kw) original_warn.call(*messages, **kw)
} end
end end
end end

View file

@ -6,7 +6,6 @@
#++ #++
require 'rubygems/user_interaction' require 'rubygems/user_interaction'
require "open3"
class Gem::Ext::Builder class Gem::Ext::Builder
@ -68,6 +67,7 @@ class Gem::Ext::Builder
results << "current directory: #{Dir.pwd}" results << "current directory: #{Dir.pwd}"
results << (command.respond_to?(:shelljoin) ? command.shelljoin : command) results << (command.respond_to?(:shelljoin) ? command.shelljoin : command)
require "open3"
output, status = Open3.capture2e(*command) output, status = Open3.capture2e(*command)
if verbose if verbose
puts output puts output

View file

@ -4,6 +4,7 @@ require 'rubygems/request'
require 'rubygems/request/connection_pools' require 'rubygems/request/connection_pools'
require 'rubygems/s3_uri_signer' require 'rubygems/s3_uri_signer'
require 'rubygems/uri_formatter' require 'rubygems/uri_formatter'
require 'rubygems/uri_parsing'
require 'rubygems/user_interaction' require 'rubygems/user_interaction'
require 'resolv' require 'resolv'
require 'rubygems/deprecate' require 'rubygems/deprecate'
@ -17,12 +18,16 @@ class Gem::RemoteFetcher
include Gem::UserInteraction include Gem::UserInteraction
extend Gem::Deprecate extend Gem::Deprecate
include Gem::UriParsing
## ##
# A FetchError exception wraps up the various possible IO and HTTP failures # A FetchError exception wraps up the various possible IO and HTTP failures
# that could happen while downloading from the internet. # that could happen while downloading from the internet.
class FetchError < Gem::Exception class FetchError < Gem::Exception
include Gem::UriParsing
## ##
# The URI which was being accessed when the exception happened. # The URI which was being accessed when the exception happened.
@ -30,13 +35,12 @@ class Gem::RemoteFetcher
def initialize(message, uri) def initialize(message, uri)
super message super message
begin
uri = URI(uri) uri = parse_uri(uri)
uri.password = 'REDACTED' if uri.password
@uri = uri.to_s uri.password = 'REDACTED' if uri.respond_to?(:password) && uri.password
rescue URI::InvalidURIError, ArgumentError
@uri = uri @uri = uri.to_s
end
end end
def to_s # :nodoc: def to_s # :nodoc:
@ -107,7 +111,7 @@ class Gem::RemoteFetcher
spec, source = found.max_by { |(s,_)| s.version } spec, source = found.max_by { |(s,_)| s.version }
download spec, source.uri.to_s download spec, source.uri
end end
## ##
@ -130,18 +134,7 @@ class Gem::RemoteFetcher
FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir
# Always escape URI's to deal with potential spaces and such source_uri = parse_uri(source_uri)
# It should also be considered that source_uri may already be
# a valid URI with escaped characters. e.g. "{DESede}" is encoded
# as "%7BDESede%7D". If this is escaped again the percentage
# symbols will be escaped.
unless source_uri.is_a?(URI::Generic)
begin
source_uri = URI.parse(source_uri)
rescue
source_uri = URI.parse(URI::DEFAULT_PARSER.escape(source_uri.to_s))
end
end
scheme = source_uri.scheme scheme = source_uri.scheme
@ -159,7 +152,7 @@ class Gem::RemoteFetcher
remote_gem_path = source_uri + "gems/#{gem_file_name}" remote_gem_path = source_uri + "gems/#{gem_file_name}"
self.cache_update_path remote_gem_path, local_gem_path self.cache_update_path remote_gem_path, local_gem_path
rescue Gem::RemoteFetcher::FetchError rescue FetchError
raise if spec.original_platform == spec.platform raise if spec.original_platform == spec.platform
alternate_name = "#{spec.original_name}.gem" alternate_name = "#{spec.original_name}.gem"
@ -236,7 +229,7 @@ class Gem::RemoteFetcher
unless location = response['Location'] unless location = response['Location']
raise FetchError.new("redirecting but no redirect location was given", uri) raise FetchError.new("redirecting but no redirect location was given", uri)
end end
location = URI.parse response['Location'] location = parse_uri location
if https?(uri) && !https?(location) if https?(uri) && !https?(location)
raise FetchError.new("redirecting to non-https resource: #{location}", uri) raise FetchError.new("redirecting to non-https resource: #{location}", uri)
@ -254,9 +247,7 @@ class Gem::RemoteFetcher
# Downloads +uri+ and returns it as a String. # Downloads +uri+ and returns it as a String.
def fetch_path(uri, mtime = nil, head = false) def fetch_path(uri, mtime = nil, head = false)
uri = URI.parse uri unless URI::Generic === uri uri = parse_uri uri
raise ArgumentError, "bad uri: #{uri}" unless uri
unless uri.scheme unless uri.scheme
raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}"
@ -268,21 +259,19 @@ class Gem::RemoteFetcher
begin begin
data = Gem::Util.gunzip data data = Gem::Util.gunzip data
rescue Zlib::GzipFile::Error rescue Zlib::GzipFile::Error
raise FetchError.new("server did not return a valid file", uri.to_s) raise FetchError.new("server did not return a valid file", uri)
end end
end end
data data
rescue FetchError
raise
rescue Timeout::Error rescue Timeout::Error
raise UnknownHostError.new('timed out', uri.to_s) raise UnknownHostError.new('timed out', uri)
rescue IOError, SocketError, SystemCallError, rescue IOError, SocketError, SystemCallError,
*(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e *(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e
if e.message =~ /getaddrinfo/ if e.message =~ /getaddrinfo/
raise UnknownHostError.new('no such name', uri.to_s) raise UnknownHostError.new('no such name', uri)
else else
raise FetchError.new("#{e.class}: #{e}", uri.to_s) raise FetchError.new("#{e.class}: #{e}", uri)
end end
end end

View file

@ -19,6 +19,7 @@ class Gem::Request
end end
def self.proxy_uri(proxy) # :nodoc: def self.proxy_uri(proxy) # :nodoc:
require "uri"
case proxy case proxy
when :no_proxy then nil when :no_proxy then nil
when URI::HTTP then proxy when URI::HTTP then proxy
@ -173,6 +174,7 @@ class Gem::Request
:no_proxy : get_proxy_from_env('http') :no_proxy : get_proxy_from_env('http')
end end
require "uri"
uri = URI(Gem::UriFormatter.new(env_proxy).normalize) uri = URI(Gem::UriFormatter.new(env_proxy).normalize)
if uri and uri.user.nil? and uri.password.nil? if uri and uri.user.nil? and uri.password.nil?

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
autoload :FileUtils, 'fileutils' autoload :FileUtils, 'fileutils'
autoload :URI, 'uri'
require "rubygems/text"
## ##
# A Source knows how to list and fetch gems from a RubyGems marshal index. # A Source knows how to list and fetch gems from a RubyGems marshal index.
# #
@ -11,6 +11,7 @@ autoload :URI, 'uri'
class Gem::Source class Gem::Source
include Comparable include Comparable
include Gem::Text
FILES = { # :nodoc: FILES = { # :nodoc:
:released => 'specs', :released => 'specs',
@ -219,6 +220,11 @@ class Gem::Source
end end
end end
def typo_squatting?(host, distance_threshold=4)
return if @uri.host.nil?
levenshtein_distance(@uri.host, host) <= distance_threshold
end
end end
require 'rubygems/source/git' require 'rubygems/source/git'

View file

@ -1,8 +1,6 @@
require 'delegate'
require 'uri'
require 'rubygems/user_interaction' require 'rubygems/user_interaction'
class Gem::SpecificationPolicy < SimpleDelegator class Gem::SpecificationPolicy
include Gem::UserInteraction include Gem::UserInteraction
@ -25,7 +23,7 @@ class Gem::SpecificationPolicy < SimpleDelegator
def initialize(specification) def initialize(specification)
@warnings = 0 @warnings = 0
super(specification) @specification = specification
end end
## ##
@ -51,7 +49,7 @@ class Gem::SpecificationPolicy < SimpleDelegator
validate_require_paths validate_require_paths
keep_only_files_and_directories @specification.keep_only_files_and_directories
validate_non_files validate_non_files
@ -92,6 +90,8 @@ class Gem::SpecificationPolicy < SimpleDelegator
# Implementation for Specification#validate_metadata # Implementation for Specification#validate_metadata
def validate_metadata def validate_metadata
metadata = @specification.metadata
unless Hash === metadata unless Hash === metadata
error 'metadata must be a hash' error 'metadata must be a hash'
end end
@ -130,7 +130,7 @@ class Gem::SpecificationPolicy < SimpleDelegator
error_messages = [] error_messages = []
warning_messages = [] warning_messages = []
dependencies.each do |dep| @specification.dependencies.each do |dep|
if prev = seen[dep.type][dep.name] if prev = seen[dep.type][dep.name]
error_messages << <<-MESSAGE error_messages << <<-MESSAGE
duplicate dependency on #{dep}, (#{prev.requirement}) use: duplicate dependency on #{dep}, (#{prev.requirement}) use:
@ -145,7 +145,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end end
warning_messages << "prerelease dependency on #{dep} is not recommended" if warning_messages << "prerelease dependency on #{dep} is not recommended" if
prerelease_dep && !version.prerelease? prerelease_dep && !@specification.version.prerelease?
open_ended = dep.requirement.requirements.all? do |op, version| open_ended = dep.requirement.requirements.all? do |op, version|
not version.prerelease? and (op == '>' or op == '>=') not version.prerelease? and (op == '>' or op == '>=')
@ -190,14 +190,14 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_permissions def validate_permissions
return if Gem.win_platform? return if Gem.win_platform?
files.each do |file| @specification.files.each do |file|
next unless File.file?(file) next unless File.file?(file)
next if File.stat(file).mode & 0444 == 0444 next if File.stat(file).mode & 0444 == 0444
warning "#{file} is not world-readable" warning "#{file} is not world-readable"
end end
executables.each do |name| @specification.executables.each do |name|
exec = File.join bindir, name exec = File.join @specification.bindir, name
next unless File.file?(exec) next unless File.file?(exec)
next if File.stat(exec).executable? next if File.stat(exec).executable?
warning "#{exec} is not executable" warning "#{exec} is not executable"
@ -208,7 +208,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_nil_attributes def validate_nil_attributes
nil_attributes = Gem::Specification.non_nil_attributes.select do |attrname| nil_attributes = Gem::Specification.non_nil_attributes.select do |attrname|
__getobj__.instance_variable_get("@#{attrname}").nil? @specification.instance_variable_get("@#{attrname}").nil?
end end
return if nil_attributes.empty? return if nil_attributes.empty?
error "#{nil_attributes.join ', '} must not be nil" error "#{nil_attributes.join ', '} must not be nil"
@ -216,6 +216,9 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_rubygems_version def validate_rubygems_version
return unless packaging return unless packaging
rubygems_version = @specification.rubygems_version
return if rubygems_version == Gem::VERSION return if rubygems_version == Gem::VERSION
error "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" error "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}"
@ -223,13 +226,15 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_required_attributes def validate_required_attributes
Gem::Specification.required_attributes.each do |symbol| Gem::Specification.required_attributes.each do |symbol|
unless send symbol unless @specification.send symbol
error "missing value for attribute #{symbol}" error "missing value for attribute #{symbol}"
end end
end end
end end
def validate_name def validate_name
name = @specification.name
if !name.is_a?(String) if !name.is_a?(String)
error "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]/ elsif name !~ /[a-zA-Z]/
@ -242,14 +247,15 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end end
def validate_require_paths def validate_require_paths
return unless raw_require_paths.empty? return unless @specification.raw_require_paths.empty?
error 'specification must have at least one require_path' error 'specification must have at least one require_path'
end end
def validate_non_files def validate_non_files
return unless packaging return unless packaging
non_files = files.reject {|x| File.file?(x) || File.symlink?(x)}
non_files = @specification.files.reject {|x| File.file?(x) || File.symlink?(x)}
unless non_files.empty? unless non_files.empty?
error "[\"#{non_files.join "\", \""}\"] are not files" error "[\"#{non_files.join "\", \""}\"] are not files"
@ -257,18 +263,22 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end end
def validate_self_inclusion_in_files_list def validate_self_inclusion_in_files_list
return unless files.include?(file_name) file_name = @specification.file_name
error "#{full_name} contains itself (#{file_name}), check your files list" return unless @specification.files.include?(file_name)
error "#{@specification.full_name} contains itself (#{file_name}), check your files list"
end end
def validate_specification_version def validate_specification_version
return if specification_version.is_a?(Integer) return if @specification.specification_version.is_a?(Integer)
error 'specification_version must be an Integer (did you mean version?)' error 'specification_version must be an Integer (did you mean version?)'
end end
def validate_platform def validate_platform
platform = @specification.platform
case platform case platform
when Gem::Platform, Gem::Platform::RUBY # ok when Gem::Platform, Gem::Platform::RUBY # ok
else else
@ -283,7 +293,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end end
def validate_array_attribute(field) def validate_array_attribute(field)
val = self.send(field) val = @specification.send(field)
klass = case field klass = case field
when :dependencies then when :dependencies then
Gem::Dependency Gem::Dependency
@ -298,12 +308,14 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end end
def validate_authors_field def validate_authors_field
return unless authors.empty? return unless @specification.authors.empty?
error "authors may not be empty" error "authors may not be empty"
end end
def validate_licenses def validate_licenses
licenses = @specification.licenses
licenses.each do |license| licenses.each do |license|
if license.length > 64 if license.length > 64
error "each license must be 64 characters or less" error "each license must be 64 characters or less"
@ -331,24 +343,27 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li
HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i.freeze HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i.freeze
def validate_lazy_metadata def validate_lazy_metadata
unless authors.grep(LAZY_PATTERN).empty? unless @specification.authors.grep(LAZY_PATTERN).empty?
error "#{LAZY} is not an author" error "#{LAZY} is not an author"
end end
unless Array(email).grep(LAZY_PATTERN).empty? unless Array(@specification.email).grep(LAZY_PATTERN).empty?
error "#{LAZY} is not an email" error "#{LAZY} is not an email"
end end
if description =~ LAZY_PATTERN if @specification.description =~ LAZY_PATTERN
error "#{LAZY} is not a description" error "#{LAZY} is not a description"
end end
if summary =~ LAZY_PATTERN if @specification.summary =~ LAZY_PATTERN
error "#{LAZY} is not a summary" error "#{LAZY} is not a summary"
end end
homepage = @specification.homepage
# Make sure a homepage is valid HTTP/HTTPS URI # Make sure a homepage is valid HTTP/HTTPS URI
if homepage and not homepage.empty? if homepage and not homepage.empty?
require 'uri'
begin begin
homepage_uri = URI.parse(homepage) homepage_uri = URI.parse(homepage)
unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class
@ -365,29 +380,29 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li
validate_attribute_present(attribute) validate_attribute_present(attribute)
end end
if description == summary if @specification.description == @specification.summary
warning "description and summary are identical" warning "description and summary are identical"
end end
# TODO: raise at some given date # TODO: raise at some given date
warning "deprecated autorequire specified" if autorequire warning "deprecated autorequire specified" if @specification.autorequire
executables.each do |executable| @specification.executables.each do |executable|
validate_shebang_line_in(executable) validate_shebang_line_in(executable)
end end
files.select { |f| File.symlink?(f) }.each do |file| @specification.files.select { |f| File.symlink?(f) }.each do |file|
warning "#{file} is a symlink, which is not supported on all platforms" warning "#{file} is a symlink, which is not supported on all platforms"
end end
end end
def validate_attribute_present(attribute) def validate_attribute_present(attribute)
value = self.send attribute value = @specification.send attribute
warning("no #{attribute} specified") if value.nil? || value.empty? warning("no #{attribute} specified") if value.nil? || value.empty?
end end
def validate_shebang_line_in(executable) def validate_shebang_line_in(executable)
executable_path = File.join(bindir, executable) executable_path = File.join(@specification.bindir, executable)
return if File.read(executable_path, 2) == '#!' return if File.read(executable_path, 2) == '#!'
warning "#{executable_path} is missing #! line" warning "#{executable_path} is missing #! line"

View file

@ -1,6 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'cgi' require 'cgi'
require 'uri'
## ##
# The UriFormatter handles URIs from user-input and escaping. # The UriFormatter handles URIs from user-input and escaping.

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
##
# The UriParser handles parsing URIs.
#
class Gem::UriParser
##
# Parses the #uri, raising if it's invalid
def parse!(uri)
raise URI::InvalidURIError unless uri
# Always escape URI's to deal with potential spaces and such
# It should also be considered that source_uri may already be
# a valid URI with escaped characters. e.g. "{DESede}" is encoded
# as "%7BDESede%7D". If this is escaped again the percentage
# symbols will be escaped.
begin
URI.parse(uri)
rescue URI::InvalidURIError
URI.parse(URI::DEFAULT_PARSER.escape(uri))
end
end
##
# Parses the #uri, returning the original uri if it's invalid
def parse(uri)
parse!(uri)
rescue URI::InvalidURIError
uri
end
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
require "rubygems/uri_parser"
module Gem::UriParsing
def parse_uri(source_uri)
return source_uri unless source_uri.is_a?(String)
uri_parser.parse(source_uri)
end
private :parse_uri
def uri_parser
require "uri"
Gem::UriParser.new
end
private :uri_parser
end

View file

@ -197,9 +197,9 @@ class TestGemCommand < Gem::TestCase
assert_equal ['-h', 'command'], args assert_equal ['-h', 'command'], args
end end
def test_deprecate_option_long_name def test_deprecate_option
deprecate_msg = <<-EXPECTED deprecate_msg = <<-EXPECTED
WARNING: The \"--test\" option has been deprecated and will be removed in Rubygems 3.1, its use is discouraged. WARNING: The \"--test\" option has been deprecated and will be removed in Rubygems 3.1.
EXPECTED EXPECTED
testCommand = Class.new(Gem::Command) do testCommand = Class.new(Gem::Command) do
@ -210,7 +210,7 @@ WARNING: The \"--test\" option has been deprecated and will be removed in Rubyg
options[:test] = true options[:test] = true
end end
deprecate_option(long_name: '--test', version: '3.1') deprecate_option('--test', version: '3.1')
end end
def execute def execute
@ -228,7 +228,7 @@ WARNING: The \"--test\" option has been deprecated and will be removed in Rubyg
def test_deprecate_option_no_version def test_deprecate_option_no_version
deprecate_msg = <<-EXPECTED deprecate_msg = <<-EXPECTED
WARNING: The \"--test\" option has been deprecated and will be removed in future versions of Rubygems, its use is discouraged. WARNING: The \"--test\" option has been deprecated and will be removed in future versions of Rubygems.
EXPECTED EXPECTED
testCommand = Class.new(Gem::Command) do testCommand = Class.new(Gem::Command) do
@ -239,7 +239,7 @@ WARNING: The \"--test\" option has been deprecated and will be removed in futur
options[:test] = true options[:test] = true
end end
deprecate_option(long_name: '--test') deprecate_option('--test')
end end
def execute def execute
@ -255,9 +255,9 @@ WARNING: The \"--test\" option has been deprecated and will be removed in futur
end end
end end
def test_deprecate_option_short_name def test_deprecate_option_extra_message
deprecate_msg = <<-EXPECTED deprecate_msg = <<-EXPECTED
WARNING: The \"-t\" option has been deprecated and will be removed in Rubygems 3.5, its use is discouraged. WARNING: The \"--test\" option has been deprecated and will be removed in Rubygems 3.1. Whether you set `--test` mode or not, this dummy app always runs in test mode.
EXPECTED EXPECTED
testCommand = Class.new(Gem::Command) do testCommand = Class.new(Gem::Command) do
@ -268,7 +268,7 @@ WARNING: The \"-t\" option has been deprecated and will be removed in Rubygems
options[:test] = true options[:test] = true
end end
deprecate_option(short_name: '-t', version: '3.5') deprecate_option('--test', version: '3.1', extra_msg: 'Whether you set `--test` mode or not, this dummy app always runs in test mode.')
end end
def execute def execute
@ -279,7 +279,36 @@ WARNING: The \"-t\" option has been deprecated and will be removed in Rubygems
cmd = testCommand.new cmd = testCommand.new
use_ui @ui do use_ui @ui do
cmd.invoke("-t") cmd.invoke("--test")
assert_equal deprecate_msg, @ui.error
end
end
def test_deprecate_option_extra_message_and_no_version
deprecate_msg = <<-EXPECTED
WARNING: The \"--test\" option has been deprecated and will be removed in future versions of Rubygems. Whether you set `--test` mode or not, this dummy app always runs in test mode.
EXPECTED
testCommand = Class.new(Gem::Command) do
def initialize
super('test', 'Gem::Command instance for testing')
add_option('-t', '--test', 'Test command') do |value, options|
options[:test] = true
end
deprecate_option('--test', extra_msg: 'Whether you set `--test` mode or not, this dummy app always runs in test mode.')
end
def execute
true
end
end
cmd = testCommand.new
use_ui @ui do
cmd.invoke("--test")
assert_equal deprecate_msg, @ui.error assert_equal deprecate_msg, @ui.error
end end
end end

View file

@ -3,6 +3,10 @@ require 'rubygems/test_case'
require 'rubygems/indexer' require 'rubygems/indexer'
require 'rubygems/commands/generate_index_command' require 'rubygems/commands/generate_index_command'
unless defined?(Builder::XChar)
warn "generate_index tests are being skipped. Install builder gem."
end
class TestGemCommandsGenerateIndexCommand < Gem::TestCase class TestGemCommandsGenerateIndexCommand < Gem::TestCase
def setup def setup
@ -22,6 +26,18 @@ class TestGemCommandsGenerateIndexCommand < Gem::TestCase
assert File.exist?(specs), specs assert File.exist?(specs), specs
end end
def test_execute_no_modern
@cmd.options[:modern] = false
use_ui @ui do
@cmd.execute
end
specs = File.join @gemhome, "specs.4.8.gz"
assert File.exist?(specs), specs
end
def test_handle_options_directory def test_handle_options_directory
return if win_platform? return if win_platform?
refute_equal '/nonexistent', @cmd.options[:directory] refute_equal '/nonexistent', @cmd.options[:directory]
@ -47,4 +63,24 @@ class TestGemCommandsGenerateIndexCommand < Gem::TestCase
assert @cmd.options[:update] assert @cmd.options[:update]
end end
end if ''.respond_to? :to_xs def test_handle_options_modern
use_ui @ui do
@cmd.handle_options %w[--modern]
end
assert_equal \
"WARNING: The \"--modern\" option has been deprecated and will be removed in Rubygems 4.0. Modern indexes (specs, latest_specs, and prerelease_specs) are always generated, so this option is not needed.\n",
@ui.error
end
def test_handle_options_no_modern
use_ui @ui do
@cmd.handle_options %w[--no-modern]
end
assert_equal \
"WARNING: The \"--no-modern\" option has been deprecated and will be removed in Rubygems 4.0. The `--no-modern` option is currently ignored. Modern indexes (specs, latest_specs, and prerelease_specs) are always generated.\n",
@ui.error
end
end if defined?(Builder::XChar)

View file

@ -4,20 +4,15 @@ require "rubygems/test_case"
require "rubygems/commands/help_command" require "rubygems/commands/help_command"
require "rubygems/package" require "rubygems/package"
require "rubygems/command_manager" require "rubygems/command_manager"
require File.expand_path('../rubygems_plugin', __FILE__)
class TestGemCommandsHelpCommand < Gem::TestCase class TestGemCommandsHelpCommand < Gem::TestCase
# previously this was calc'd in setup, but 1.8.7 had
# intermittent failures, but no issues with above require
PLUGIN = File.expand_path('../rubygems_plugin.rb', __FILE__)
def setup def setup
super super
@cmd = Gem::Commands::HelpCommand.new @cmd = Gem::Commands::HelpCommand.new
load PLUGIN unless Gem::Commands.const_defined? :InterruptCommand load File.expand_path('../rubygems_plugin.rb', __FILE__) unless Gem::Commands.const_defined? :InterruptCommand
end end
def test_gem_help_bad def test_gem_help_bad

View file

@ -74,6 +74,80 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
assert_equal '', @ui.error assert_equal '', @ui.error
end end
def test_execute_add_allow_typo_squatting_source
rubygems_org = "https://rubyems.org"
spec_fetcher do |fetcher|
fetcher.spec("a", 1)
end
specs = Gem::Specification.map do |spec|
[spec.name, spec.version, spec.original_platform]
end
specs_dump_gz = StringIO.new
Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
Marshal.dump(specs, io)
end
@fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
@cmd.handle_options %W[--add #{rubygems_org}]
ui = Gem::MockGemUi.new("y")
use_ui ui do
@cmd.execute
end
expected = "https://rubyems.org is too similar to https://rubygems.org\n\nDo you want to add this source? [yn] https://rubyems.org added to sources\n"
assert_equal expected, ui.output
source = Gem::Source.new(rubygems_org)
assert Gem.sources.include?(source)
assert_empty ui.error
end
def test_execute_add_deny_typo_squatting_source
rubygems_org = "https://rubyems.org"
spec_fetcher do |fetcher|
fetcher.spec("a", 1)
end
specs = Gem::Specification.map do |spec|
[spec.name, spec.version, spec.original_platform]
end
specs_dump_gz = StringIO.new
Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
Marshal.dump(specs, io)
end
@fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] =
specs_dump_gz.string
@cmd.handle_options %W[--add #{rubygems_org}]
ui = Gem::MockGemUi.new("n")
use_ui ui do
assert_raises Gem::MockGemUi::TermError do
@cmd.execute
end
end
expected = "https://rubyems.org is too similar to https://rubygems.org\n\nDo you want to add this source? [yn] "
assert_equal expected, ui.output
source = Gem::Source.new(rubygems_org)
refute Gem.sources.include?(source)
assert_empty ui.error
end
def test_execute_add_nonexistent_source def test_execute_add_nonexistent_source
spec_fetcher spec_fetcher

View file

@ -1,6 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rubygems/test_case' require 'rubygems/test_case'
require 'rubygems/gem_runner'
class TestGemGemRunner < Gem::TestCase class TestGemGemRunner < Gem::TestCase
@ -8,6 +7,8 @@ class TestGemGemRunner < Gem::TestCase
super super
@orig_args = Gem::Command.build_args @orig_args = Gem::Command.build_args
require 'rubygems/gem_runner'
@runner = Gem::GemRunner.new @runner = Gem::GemRunner.new
end end

View file

@ -3,7 +3,7 @@ require 'rubygems/test_case'
require 'rubygems/indexer' require 'rubygems/indexer'
unless defined?(Builder::XChar) unless defined?(Builder::XChar)
warn "Gem::Indexer tests are being skipped. Install builder gem." if $VERBOSE warn "Gem::Indexer tests are being skipped. Install builder gem."
end end
class TestGemIndexer < Gem::TestCase class TestGemIndexer < Gem::TestCase

View file

@ -235,4 +235,18 @@ class TestGemSource < Gem::TestCase
refute @source.update_cache? refute @source.update_cache?
end end
def test_typo_squatting
rubygems_source = Gem::Source.new("https://rubgems.org")
assert rubygems_source.typo_squatting?("rubygems.org")
assert rubygems_source.typo_squatting?("rubyagems.org")
assert rubygems_source.typo_squatting?("rubyasgems.org")
refute rubygems_source.typo_squatting?("rubysertgems.org")
end
def test_typo_squatting_custom_distance_threshold
rubygems_source = Gem::Source.new("https://rubgems.org")
distance_threshold = 5
assert rubygems_source.typo_squatting?("rubysertgems.org", distance_threshold)
end
end end

View file

@ -5,7 +5,7 @@ class TestRemoteFetchError < Gem::TestCase
def test_password_redacted def test_password_redacted
error = Gem::RemoteFetcher::FetchError.new('There was an error fetching', 'https://user:secret@gemsource.org') error = Gem::RemoteFetcher::FetchError.new('There was an error fetching', 'https://user:secret@gemsource.org')
refute_match error.to_s, 'secret' refute_match 'secret', error.to_s
end end
def test_invalid_url def test_invalid_url

View file

@ -365,19 +365,16 @@ class TestGemRequire < Gem::TestCase
end end
def test_realworld_default_gem def test_realworld_default_gem
begin testing_ruby_repo = !ENV["GEM_COMMAND"].nil?
gem 'json' skip "this test can't work under ruby-core setup" if testing_ruby_repo || java_platform?
rescue Gem::MissingSpecError
skip "default gems are only available after ruby installation"
end
cmd = <<-RUBY cmd = <<-RUBY
$stderr = $stdout $stderr = $stdout
require "json" require "json"
puts Gem.loaded_specs["json"].default_gem? puts Gem.loaded_specs["json"]
RUBY RUBY
output = Gem::Util.popen(Gem.ruby, "-e", cmd).strip output = Gem::Util.popen(Gem.ruby, "-e", cmd).strip
assert_equal "true", output refute_empty output
end end
def test_default_gem_and_normal_gem def test_default_gem_and_normal_gem
@ -499,36 +496,38 @@ class TestGemRequire < Gem::TestCase
end end
end end
# uplevel is 2.5+ only and jruby has some issues with it # uplevel is 2.5+ only
if RUBY_VERSION >= "2.5" && !java_platform? if RUBY_VERSION >= "2.5"
def test_no_kernel_require_in_warn_with_uplevel ["", "Kernel."].each do |prefix|
lib = File.realpath("../../../lib", __FILE__) define_method "test_no_kernel_require_in_#{prefix.tr(".", "_")}warn_with_uplevel" do
Dir.mktmpdir("warn_test") do |dir| lib = File.realpath("../../../lib", __FILE__)
File.write(dir + "/sub.rb", "warn 'uplevel', 'test', uplevel: 1\n") Dir.mktmpdir("warn_test") do |dir|
File.write(dir + "/main.rb", "require 'sub'\n") File.write(dir + "/sub.rb", "#{prefix}warn 'uplevel', 'test', uplevel: 1\n")
_, err = capture_subprocess_io do File.write(dir + "/main.rb", "require 'sub'\n")
system(@@ruby, "-w", "-rpp", "--disable=gems", "-I", lib, "-C", dir, "-I.", "main.rb") _, err = capture_subprocess_io do
system(@@ruby, "-w", "--disable=gems", "-I", lib, "-C", dir, "-I.", "main.rb")
end
assert_match(/main\.rb:1: warning: uplevel\ntest\n$/, err)
_, err = capture_subprocess_io do
system(@@ruby, "-w", "--enable=gems", "-I", lib, "-C", dir, "-I.", "main.rb")
end
assert_match(/main\.rb:1: warning: uplevel\ntest\n$/, err)
end end
assert_equal "main.rb:1: warning: uplevel\ntest\n", err
_, err = capture_subprocess_io do
system(@@ruby, "-w", "-rpp", "--enable=gems", "-I", lib, "-C", dir, "-I.", "main.rb")
end
assert_equal "main.rb:1: warning: uplevel\ntest\n", err
end end
end
def test_no_other_behavioral_changes_with_kernel_warn define_method "test_no_other_behavioral_changes_with_#{prefix.tr(".", "_")}warn" do
lib = File.realpath("../../../lib", __FILE__) lib = File.realpath("../../../lib", __FILE__)
Dir.mktmpdir("warn_test") do |dir| Dir.mktmpdir("warn_test") do |dir|
File.write(dir + "/main.rb", "warn({x:1}, {y:2}, [])\n") File.write(dir + "/main.rb", "#{prefix}warn({x:1}, {y:2}, [])\n")
_, err = capture_subprocess_io do _, err = capture_subprocess_io do
system(@@ruby, "-w", "-rpp", "--disable=gems", "-I", lib, "-C", dir, "-I.", "main.rb") system(@@ruby, "-w", "--disable=gems", "-I", lib, "-C", dir, "main.rb")
end
assert_match(/{:x=>1}\n{:y=>2}\n$/, err)
_, err = capture_subprocess_io do
system(@@ruby, "-w", "--enable=gems", "-I", lib, "-C", dir, "main.rb")
end
assert_match(/{:x=>1}\n{:y=>2}\n$/, err)
end end
assert_equal "{:x=>1}\n{:y=>2}\n", err
_, err = capture_subprocess_io do
system(@@ruby, "-w", "-rpp", "--enable=gems", "-I", lib, "-C", dir, "-I.", "main.rb")
end
assert_equal "{:x=>1}\n{:y=>2}\n", err
end end
end end
end end