summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2019-12-13 20:19:08 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2019-12-13 20:19:33 +0900
commit82cc2843a92b286cc13afd0860a4e111d4ea2a0b (patch)
treea517dedd40d35540930cea7732f5a36e76a549e8
parent26774351dc5f494253ba031e4bc453dc4dddb2cf (diff)
Prepare to release RubyGems 3.1.0 final version.
-rw-r--r--lib/rubygems.rb18
-rw-r--r--lib/rubygems/command.rb34
-rw-r--r--lib/rubygems/commands/generate_index_command.rb3
-rw-r--r--lib/rubygems/commands/setup_command.rb2
-rw-r--r--lib/rubygems/commands/sources_command.rb14
-rw-r--r--lib/rubygems/core_ext/kernel_warn.rb16
-rw-r--r--lib/rubygems/ext/builder.rb2
-rw-r--r--lib/rubygems/remote_fetcher.rb51
-rw-r--r--lib/rubygems/request.rb2
-rw-r--r--lib/rubygems/source.rb8
-rw-r--r--lib/rubygems/specification_policy.rb73
-rw-r--r--lib/rubygems/uri_formatter.rb1
-rw-r--r--lib/rubygems/uri_parser.rb36
-rw-r--r--lib/rubygems/uri_parsing.rb23
-rw-r--r--test/rubygems/test_gem_command.rb47
-rw-r--r--test/rubygems/test_gem_commands_generate_index_command.rb38
-rw-r--r--test/rubygems/test_gem_commands_help_command.rb7
-rw-r--r--test/rubygems/test_gem_commands_sources_command.rb74
-rw-r--r--test/rubygems/test_gem_gem_runner.rb3
-rw-r--r--test/rubygems/test_gem_indexer.rb2
-rw-r--r--test/rubygems/test_gem_source.rb14
-rw-r--r--test/rubygems/test_remote_fetch_error.rb2
-rw-r--r--test/rubygems/test_require.rb65
23 files changed, 393 insertions, 142 deletions
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 474734bd40..6be55a0379 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -160,24 +160,14 @@ module Gem
].freeze
##
- # Exception classes used in a Gem.read_binary +rescue+ statement. Not all of
- # these are defined in Ruby 1.8.7, hence the need for this convoluted setup.
+ # Exception classes used in a Gem.read_binary +rescue+ statement
- READ_BINARY_ERRORS = begin
- read_binary_errors = [Errno::EACCES, Errno::EROFS, Errno::ENOSYS]
- read_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP)
- read_binary_errors
- end.freeze
+ READ_BINARY_ERRORS = [Errno::EACCES, Errno::EROFS, Errno::ENOSYS, Errno::ENOTSUP].freeze
##
- # Exception classes used in Gem.write_binary +rescue+ statement. Not all of
- # these are defined in Ruby 1.8.7.
+ # Exception classes used in Gem.write_binary +rescue+ statement
- WRITE_BINARY_ERRORS = begin
- write_binary_errors = [Errno::ENOSYS]
- write_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP)
- write_binary_errors
- end.freeze
+ WRITE_BINARY_ERRORS = [Errno::ENOSYS, Errno::ENOTSUP].freeze
@@win_platform = nil
diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb
index ab683f0049..c1e5e13c5a 100644
--- a/lib/rubygems/command.rb
+++ b/lib/rubygems/command.rb
@@ -369,22 +369,44 @@ class Gem::Command
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
- @deprecated_options[command].merge!({ long_name => { "rg_version_to_expire" => version } }) if long_name
+ ##
+ # Mark a command-line option as deprecated, and optionally specify a
+ # 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
def check_deprecated_options(options)
options.each do |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
- "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
- "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
+ extra_msg = deprecation["extra_msg"]
+
+ deprecate_option_msg += " #{extra_msg}" if extra_msg
+
alert_warning(deprecate_option_msg)
end
end
diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb
index 941637ea9c..6dccdcb946 100644
--- a/lib/rubygems/commands/generate_index_command.rb
+++ b/lib/rubygems/commands/generate_index_command.rb
@@ -25,6 +25,9 @@ class Gem::Commands::GenerateIndexCommand < Gem::Command
options[:build_modern] = value
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',
'Update modern indexes with gems added',
'since the last update' do |value, options|
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 3448fdfb4e..7844e9d199 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -98,7 +98,7 @@ class Gem::Commands::SetupCommand < Gem::Command
end
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
alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}"
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index af145fd7b4..feab23237d 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -43,6 +43,8 @@ class Gem::Commands::SourcesCommand < Gem::Command
source = Gem::Source.new source_uri
+ check_typo_squatting(source)
+
begin
if Gem.sources.include? source
say "source #{source_uri} already present in the cache"
@@ -62,6 +64,18 @@ class Gem::Commands::SourcesCommand < Gem::Command
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:
uri = URI source_uri
diff --git a/lib/rubygems/core_ext/kernel_warn.rb b/lib/rubygems/core_ext/kernel_warn.rb
index 96da272d66..6ea72b10c3 100644
--- a/lib/rubygems/core_ext/kernel_warn.rb
+++ b/lib/rubygems/core_ext/kernel_warn.rb
@@ -6,13 +6,17 @@ if RUBY_VERSION >= "2.5"
module Kernel
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)
- 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]
if Gem.java_platform?
return original_warn.call(*messages)
@@ -46,6 +50,6 @@ if RUBY_VERSION >= "2.5"
kw[:uplevel] = uplevel
original_warn.call(*messages, **kw)
- }
+ end
end
end
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index d91ecd25de..2fc1074d92 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -6,7 +6,6 @@
#++
require 'rubygems/user_interaction'
-require "open3"
class Gem::Ext::Builder
@@ -68,6 +67,7 @@ class Gem::Ext::Builder
results << "current directory: #{Dir.pwd}"
results << (command.respond_to?(:shelljoin) ? command.shelljoin : command)
+ require "open3"
output, status = Open3.capture2e(*command)
if verbose
puts output
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index c399cc9d95..2ed619f1bc 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -4,6 +4,7 @@ require 'rubygems/request'
require 'rubygems/request/connection_pools'
require 'rubygems/s3_uri_signer'
require 'rubygems/uri_formatter'
+require 'rubygems/uri_parsing'
require 'rubygems/user_interaction'
require 'resolv'
require 'rubygems/deprecate'
@@ -17,12 +18,16 @@ class Gem::RemoteFetcher
include Gem::UserInteraction
extend Gem::Deprecate
+ include Gem::UriParsing
+
##
# A FetchError exception wraps up the various possible IO and HTTP failures
# that could happen while downloading from the internet.
class FetchError < Gem::Exception
+ include Gem::UriParsing
+
##
# The URI which was being accessed when the exception happened.
@@ -30,13 +35,12 @@ class Gem::RemoteFetcher
def initialize(message, uri)
super message
- begin
- uri = URI(uri)
- uri.password = 'REDACTED' if uri.password
- @uri = uri.to_s
- rescue URI::InvalidURIError, ArgumentError
- @uri = uri
- end
+
+ uri = parse_uri(uri)
+
+ uri.password = 'REDACTED' if uri.respond_to?(:password) && uri.password
+
+ @uri = uri.to_s
end
def to_s # :nodoc:
@@ -107,7 +111,7 @@ class Gem::RemoteFetcher
spec, source = found.max_by { |(s,_)| s.version }
- download spec, source.uri.to_s
+ download spec, source.uri
end
##
@@ -130,18 +134,7 @@ class Gem::RemoteFetcher
FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir
- # 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.
- 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
+ source_uri = parse_uri(source_uri)
scheme = source_uri.scheme
@@ -159,7 +152,7 @@ class Gem::RemoteFetcher
remote_gem_path = source_uri + "gems/#{gem_file_name}"
self.cache_update_path remote_gem_path, local_gem_path
- rescue Gem::RemoteFetcher::FetchError
+ rescue FetchError
raise if spec.original_platform == spec.platform
alternate_name = "#{spec.original_name}.gem"
@@ -236,7 +229,7 @@ class Gem::RemoteFetcher
unless location = response['Location']
raise FetchError.new("redirecting but no redirect location was given", uri)
end
- location = URI.parse response['Location']
+ location = parse_uri location
if https?(uri) && !https?(location)
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.
def fetch_path(uri, mtime = nil, head = false)
- uri = URI.parse uri unless URI::Generic === uri
-
- raise ArgumentError, "bad uri: #{uri}" unless uri
+ uri = parse_uri uri
unless uri.scheme
raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}"
@@ -268,21 +259,19 @@ class Gem::RemoteFetcher
begin
data = Gem::Util.gunzip data
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
data
- rescue FetchError
- raise
rescue Timeout::Error
- raise UnknownHostError.new('timed out', uri.to_s)
+ raise UnknownHostError.new('timed out', uri)
rescue IOError, SocketError, SystemCallError,
*(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e
if e.message =~ /getaddrinfo/
- raise UnknownHostError.new('no such name', uri.to_s)
+ raise UnknownHostError.new('no such name', uri)
else
- raise FetchError.new("#{e.class}: #{e}", uri.to_s)
+ raise FetchError.new("#{e.class}: #{e}", uri)
end
end
diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb
index 435c7c53cb..b5a14ec34d 100644
--- a/lib/rubygems/request.rb
+++ b/lib/rubygems/request.rb
@@ -19,6 +19,7 @@ class Gem::Request
end
def self.proxy_uri(proxy) # :nodoc:
+ require "uri"
case proxy
when :no_proxy then nil
when URI::HTTP then proxy
@@ -173,6 +174,7 @@ class Gem::Request
:no_proxy : get_proxy_from_env('http')
end
+ require "uri"
uri = URI(Gem::UriFormatter.new(env_proxy).normalize)
if uri and uri.user.nil? and uri.password.nil?
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index b0cce5bea5..8572cb1806 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
autoload :FileUtils, 'fileutils'
-autoload :URI, 'uri'
+require "rubygems/text"
##
# A Source knows how to list and fetch gems from a RubyGems marshal index.
#
@@ -11,6 +11,7 @@ autoload :URI, 'uri'
class Gem::Source
include Comparable
+ include Gem::Text
FILES = { # :nodoc:
:released => 'specs',
@@ -219,6 +220,11 @@ class Gem::Source
end
end
+ def typo_squatting?(host, distance_threshold=4)
+ return if @uri.host.nil?
+ levenshtein_distance(@uri.host, host) <= distance_threshold
+ end
+
end
require 'rubygems/source/git'
diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb
index 24c1145907..c3c496db9b 100644
--- a/lib/rubygems/specification_policy.rb
+++ b/lib/rubygems/specification_policy.rb
@@ -1,8 +1,6 @@
-require 'delegate'
-require 'uri'
require 'rubygems/user_interaction'
-class Gem::SpecificationPolicy < SimpleDelegator
+class Gem::SpecificationPolicy
include Gem::UserInteraction
@@ -25,7 +23,7 @@ class Gem::SpecificationPolicy < SimpleDelegator
def initialize(specification)
@warnings = 0
- super(specification)
+ @specification = specification
end
##
@@ -51,7 +49,7 @@ class Gem::SpecificationPolicy < SimpleDelegator
validate_require_paths
- keep_only_files_and_directories
+ @specification.keep_only_files_and_directories
validate_non_files
@@ -92,6 +90,8 @@ class Gem::SpecificationPolicy < SimpleDelegator
# Implementation for Specification#validate_metadata
def validate_metadata
+ metadata = @specification.metadata
+
unless Hash === metadata
error 'metadata must be a hash'
end
@@ -130,7 +130,7 @@ class Gem::SpecificationPolicy < SimpleDelegator
error_messages = []
warning_messages = []
- dependencies.each do |dep|
+ @specification.dependencies.each do |dep|
if prev = seen[dep.type][dep.name]
error_messages << <<-MESSAGE
duplicate dependency on #{dep}, (#{prev.requirement}) use:
@@ -145,7 +145,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end
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|
not version.prerelease? and (op == '>' or op == '>=')
@@ -190,14 +190,14 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_permissions
return if Gem.win_platform?
- files.each do |file|
+ @specification.files.each do |file|
next unless File.file?(file)
next if File.stat(file).mode & 0444 == 0444
warning "#{file} is not world-readable"
end
- executables.each do |name|
- exec = File.join bindir, name
+ @specification.executables.each do |name|
+ exec = File.join @specification.bindir, name
next unless File.file?(exec)
next if File.stat(exec).executable?
warning "#{exec} is not executable"
@@ -208,7 +208,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_nil_attributes
nil_attributes = Gem::Specification.non_nil_attributes.select do |attrname|
- __getobj__.instance_variable_get("@#{attrname}").nil?
+ @specification.instance_variable_get("@#{attrname}").nil?
end
return if nil_attributes.empty?
error "#{nil_attributes.join ', '} must not be nil"
@@ -216,6 +216,9 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_rubygems_version
return unless packaging
+
+ rubygems_version = @specification.rubygems_version
+
return if rubygems_version == Gem::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
Gem::Specification.required_attributes.each do |symbol|
- unless send symbol
+ unless @specification.send symbol
error "missing value for attribute #{symbol}"
end
end
end
def validate_name
+ name = @specification.name
+
if !name.is_a?(String)
error "invalid value for attribute name: \"#{name.inspect}\" must be a string"
elsif name !~ /[a-zA-Z]/
@@ -242,14 +247,15 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end
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'
end
def validate_non_files
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?
error "[\"#{non_files.join "\", \""}\"] are not files"
@@ -257,18 +263,22 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end
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
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?)'
end
def validate_platform
+ platform = @specification.platform
+
case platform
when Gem::Platform, Gem::Platform::RUBY # ok
else
@@ -283,7 +293,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end
def validate_array_attribute(field)
- val = self.send(field)
+ val = @specification.send(field)
klass = case field
when :dependencies then
Gem::Dependency
@@ -298,12 +308,14 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end
def validate_authors_field
- return unless authors.empty?
+ return unless @specification.authors.empty?
error "authors may not be empty"
end
def validate_licenses
+ licenses = @specification.licenses
+
licenses.each do |license|
if license.length > 64
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
def validate_lazy_metadata
- unless authors.grep(LAZY_PATTERN).empty?
+ unless @specification.authors.grep(LAZY_PATTERN).empty?
error "#{LAZY} is not an author"
end
- unless Array(email).grep(LAZY_PATTERN).empty?
+ unless Array(@specification.email).grep(LAZY_PATTERN).empty?
error "#{LAZY} is not an email"
end
- if description =~ LAZY_PATTERN
+ if @specification.description =~ LAZY_PATTERN
error "#{LAZY} is not a description"
end
- if summary =~ LAZY_PATTERN
+ if @specification.summary =~ LAZY_PATTERN
error "#{LAZY} is not a summary"
end
+ homepage = @specification.homepage
+
# Make sure a homepage is valid HTTP/HTTPS URI
if homepage and not homepage.empty?
+ require 'uri'
begin
homepage_uri = URI.parse(homepage)
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)
end
- if description == summary
+ if @specification.description == @specification.summary
warning "description and summary are identical"
end
# 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)
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"
end
end
def validate_attribute_present(attribute)
- value = self.send attribute
+ value = @specification.send attribute
warning("no #{attribute} specified") if value.nil? || value.empty?
end
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) == '#!'
warning "#{executable_path} is missing #! line"
diff --git a/lib/rubygems/uri_formatter.rb b/lib/rubygems/uri_formatter.rb
index 0a24dde24d..f3d510470b 100644
--- a/lib/rubygems/uri_formatter.rb
+++ b/lib/rubygems/uri_formatter.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'cgi'
-require 'uri'
##
# The UriFormatter handles URIs from user-input and escaping.
diff --git a/lib/rubygems/uri_parser.rb b/lib/rubygems/uri_parser.rb
new file mode 100644
index 0000000000..5c7cabc436
--- /dev/null
+++ b/lib/rubygems/uri_parser.rb
@@ -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
diff --git a/lib/rubygems/uri_parsing.rb b/lib/rubygems/uri_parsing.rb
new file mode 100644
index 0000000000..941d7e023a
--- /dev/null
+++ b/lib/rubygems/uri_parsing.rb
@@ -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
diff --git a/test/rubygems/test_gem_command.rb b/test/rubygems/test_gem_command.rb
index d51371301e..230baa5356 100644
--- a/test/rubygems/test_gem_command.rb
+++ b/test/rubygems/test_gem_command.rb
@@ -197,9 +197,9 @@ class TestGemCommand < Gem::TestCase
assert_equal ['-h', 'command'], args
end
- def test_deprecate_option_long_name
+ def test_deprecate_option
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
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
end
- deprecate_option(long_name: '--test', version: '3.1')
+ deprecate_option('--test', version: '3.1')
end
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
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
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
end
- deprecate_option(long_name: '--test')
+ deprecate_option('--test')
end
def execute
@@ -255,9 +255,9 @@ WARNING: The \"--test\" option has been deprecated and will be removed in futur
end
end
- def test_deprecate_option_short_name
+ def test_deprecate_option_extra_message
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
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
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
def execute
@@ -279,7 +279,36 @@ WARNING: The \"-t\" option has been deprecated and will be removed in Rubygems
cmd = testCommand.new
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
end
end
diff --git a/test/rubygems/test_gem_commands_generate_index_command.rb b/test/rubygems/test_gem_commands_generate_index_command.rb
index b4276702f4..d8fda32fc0 100644
--- a/test/rubygems/test_gem_commands_generate_index_command.rb
+++ b/test/rubygems/test_gem_commands_generate_index_command.rb
@@ -3,6 +3,10 @@ require 'rubygems/test_case'
require 'rubygems/indexer'
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
def setup
@@ -22,6 +26,18 @@ class TestGemCommandsGenerateIndexCommand < Gem::TestCase
assert File.exist?(specs), specs
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
return if win_platform?
refute_equal '/nonexistent', @cmd.options[:directory]
@@ -47,4 +63,24 @@ class TestGemCommandsGenerateIndexCommand < Gem::TestCase
assert @cmd.options[:update]
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)
diff --git a/test/rubygems/test_gem_commands_help_command.rb b/test/rubygems/test_gem_commands_help_command.rb
index 8fcff6b1e7..f2a519775c 100644
--- a/test/rubygems/test_gem_commands_help_command.rb
+++ b/test/rubygems/test_gem_commands_help_command.rb
@@ -4,20 +4,15 @@ require "rubygems/test_case"
require "rubygems/commands/help_command"
require "rubygems/package"
require "rubygems/command_manager"
-require File.expand_path('../rubygems_plugin', __FILE__)
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
super
@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
def test_gem_help_bad
diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb
index 36e6ee9293..b63fbce81f 100644
--- a/test/rubygems/test_gem_commands_sources_command.rb
+++ b/test/rubygems/test_gem_commands_sources_command.rb
@@ -74,6 +74,80 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
assert_equal '', @ui.error
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
spec_fetcher
diff --git a/test/rubygems/test_gem_gem_runner.rb b/test/rubygems/test_gem_gem_runner.rb
index 7c771de9e5..0c801847b0 100644
--- a/test/rubygems/test_gem_gem_runner.rb
+++ b/test/rubygems/test_gem_gem_runner.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'rubygems/test_case'
-require 'rubygems/gem_runner'
class TestGemGemRunner < Gem::TestCase
@@ -8,6 +7,8 @@ class TestGemGemRunner < Gem::TestCase
super
@orig_args = Gem::Command.build_args
+
+ require 'rubygems/gem_runner'
@runner = Gem::GemRunner.new
end
diff --git a/test/rubygems/test_gem_indexer.rb b/test/rubygems/test_gem_indexer.rb
index 43a04c3bb3..fdef33e464 100644
--- a/test/rubygems/test_gem_indexer.rb
+++ b/test/rubygems/test_gem_indexer.rb
@@ -3,7 +3,7 @@ require 'rubygems/test_case'
require 'rubygems/indexer'
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
class TestGemIndexer < Gem::TestCase
diff --git a/test/rubygems/test_gem_source.rb b/test/rubygems/test_gem_source.rb
index 2ed9fc78e7..30b45ea267 100644
--- a/test/rubygems/test_gem_source.rb
+++ b/test/rubygems/test_gem_source.rb
@@ -235,4 +235,18 @@ class TestGemSource < Gem::TestCase
refute @source.update_cache?
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
diff --git a/test/rubygems/test_remote_fetch_error.rb b/test/rubygems/test_remote_fetch_error.rb
index 780e5e79f7..766086756e 100644
--- a/test/rubygems/test_remote_fetch_error.rb
+++ b/test/rubygems/test_remote_fetch_error.rb
@@ -5,7 +5,7 @@ class TestRemoteFetchError < Gem::TestCase
def test_password_redacted
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
def test_invalid_url
diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb
index 69df9093c7..aa2675af5d 100644
--- a/test/rubygems/test_require.rb
+++ b/test/rubygems/test_require.rb
@@ -365,19 +365,16 @@ class TestGemRequire < Gem::TestCase
end
def test_realworld_default_gem
- begin
- gem 'json'
- rescue Gem::MissingSpecError
- skip "default gems are only available after ruby installation"
- end
+ testing_ruby_repo = !ENV["GEM_COMMAND"].nil?
+ skip "this test can't work under ruby-core setup" if testing_ruby_repo || java_platform?
cmd = <<-RUBY
$stderr = $stdout
require "json"
- puts Gem.loaded_specs["json"].default_gem?
+ puts Gem.loaded_specs["json"]
RUBY
output = Gem::Util.popen(Gem.ruby, "-e", cmd).strip
- assert_equal "true", output
+ refute_empty output
end
def test_default_gem_and_normal_gem
@@ -499,36 +496,38 @@ class TestGemRequire < Gem::TestCase
end
end
- # uplevel is 2.5+ only and jruby has some issues with it
- if RUBY_VERSION >= "2.5" && !java_platform?
- def test_no_kernel_require_in_warn_with_uplevel
- lib = File.realpath("../../../lib", __FILE__)
- Dir.mktmpdir("warn_test") do |dir|
- File.write(dir + "/sub.rb", "warn 'uplevel', 'test', uplevel: 1\n")
- File.write(dir + "/main.rb", "require 'sub'\n")
- _, err = capture_subprocess_io do
- system(@@ruby, "-w", "-rpp", "--disable=gems", "-I", lib, "-C", dir, "-I.", "main.rb")
- 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")
+ # uplevel is 2.5+ only
+ if RUBY_VERSION >= "2.5"
+ ["", "Kernel."].each do |prefix|
+ define_method "test_no_kernel_require_in_#{prefix.tr(".", "_")}warn_with_uplevel" do
+ lib = File.realpath("../../../lib", __FILE__)
+ Dir.mktmpdir("warn_test") do |dir|
+ File.write(dir + "/sub.rb", "#{prefix}warn 'uplevel', 'test', uplevel: 1\n")
+ File.write(dir + "/main.rb", "require 'sub'\n")
+ _, 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
- assert_equal "main.rb:1: warning: uplevel\ntest\n", err
end
- end
- def test_no_other_behavioral_changes_with_kernel_warn
- lib = File.realpath("../../../lib", __FILE__)
- Dir.mktmpdir("warn_test") do |dir|
- File.write(dir + "/main.rb", "warn({x:1}, {y:2}, [])\n")
- _, err = capture_subprocess_io do
- system(@@ruby, "-w", "-rpp", "--disable=gems", "-I", lib, "-C", dir, "-I.", "main.rb")
- 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")
+ define_method "test_no_other_behavioral_changes_with_#{prefix.tr(".", "_")}warn" do
+ lib = File.realpath("../../../lib", __FILE__)
+ Dir.mktmpdir("warn_test") do |dir|
+ File.write(dir + "/main.rb", "#{prefix}warn({x:1}, {y:2}, [])\n")
+ _, err = capture_subprocess_io do
+ 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
- assert_equal "{:x=>1}\n{:y=>2}\n", err
end
end
end