summaryrefslogtreecommitdiff
path: root/lib/rubygems
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems')
-rw-r--r--lib/rubygems/available_set.rb15
-rw-r--r--lib/rubygems/basic_specification.rb99
-rw-r--r--lib/rubygems/bundler_version_finder.rb60
-rw-r--r--lib/rubygems/ci_detector.rb75
-rw-r--r--lib/rubygems/command.rb138
-rw-r--r--lib/rubygems/command_manager.rb60
-rw-r--r--lib/rubygems/commands/build_command.rb33
-rw-r--r--lib/rubygems/commands/cert_command.rb153
-rw-r--r--lib/rubygems/commands/check_command.rb55
-rw-r--r--lib/rubygems/commands/cleanup_command.rb67
-rw-r--r--lib/rubygems/commands/contents_command.rb39
-rw-r--r--lib/rubygems/commands/dependency_command.rb93
-rw-r--r--lib/rubygems/commands/environment_command.rb24
-rw-r--r--lib/rubygems/commands/exec_command.rb249
-rw-r--r--lib/rubygems/commands/fetch_command.rb46
-rw-r--r--lib/rubygems/commands/generate_index_command.rb114
-rw-r--r--lib/rubygems/commands/help_command.rb29
-rw-r--r--lib/rubygems/commands/info_command.rb10
-rw-r--r--lib/rubygems/commands/install_command.rb68
-rw-r--r--lib/rubygems/commands/list_command.rb11
-rw-r--r--lib/rubygems/commands/lock_command.rb11
-rw-r--r--lib/rubygems/commands/mirror_command.rb7
-rw-r--r--lib/rubygems/commands/open_command.rb23
-rw-r--r--lib/rubygems/commands/outdated_command.rb11
-rw-r--r--lib/rubygems/commands/owner_command.rb50
-rw-r--r--lib/rubygems/commands/pristine_command.rb134
-rw-r--r--lib/rubygems/commands/push_command.rb35
-rw-r--r--lib/rubygems/commands/query_command.rb22
-rw-r--r--lib/rubygems/commands/rdoc_command.rb59
-rw-r--r--lib/rubygems/commands/rebuild_command.rb262
-rw-r--r--lib/rubygems/commands/search_command.rb11
-rw-r--r--lib/rubygems/commands/server_command.rb92
-rw-r--r--lib/rubygems/commands/setup_command.rb397
-rw-r--r--lib/rubygems/commands/signin_command.rb19
-rw-r--r--lib/rubygems/commands/signout_command.rb15
-rw-r--r--lib/rubygems/commands/sources_command.rb69
-rw-r--r--lib/rubygems/commands/specification_command.rb43
-rw-r--r--lib/rubygems/commands/stale_command.rb9
-rw-r--r--lib/rubygems/commands/uninstall_command.rb112
-rw-r--r--lib/rubygems/commands/unpack_command.rb53
-rw-r--r--lib/rubygems/commands/update_command.rb162
-rw-r--r--lib/rubygems/commands/which_command.rb17
-rw-r--r--lib/rubygems/commands/yank_command.rb27
-rw-r--r--lib/rubygems/compatibility.rb17
-rw-r--r--lib/rubygems/config_file.rb213
-rw-r--r--lib/rubygems/core_ext/kernel_gem.rb13
-rw-r--r--lib/rubygems/core_ext/kernel_require.rb202
-rw-r--r--lib/rubygems/core_ext/kernel_warn.rb73
-rw-r--r--lib/rubygems/core_ext/tcpsocket_init.rb10
-rw-r--r--lib/rubygems/defaults.rb95
-rw-r--r--lib/rubygems/dependency.rb64
-rw-r--r--lib/rubygems/dependency_installer.rb108
-rw-r--r--lib/rubygems/dependency_list.rb17
-rw-r--r--lib/rubygems/deprecate.rb96
-rw-r--r--lib/rubygems/doctor.rb51
-rw-r--r--lib/rubygems/errors.rb17
-rw-r--r--lib/rubygems/exceptions.rb42
-rw-r--r--lib/rubygems/ext.rb14
-rw-r--r--lib/rubygems/ext/build_error.rb3
-rw-r--r--lib/rubygems/ext/builder.rb90
-rw-r--r--lib/rubygems/ext/cargo_builder.rb359
-rw-r--r--lib/rubygems/ext/cargo_builder/link_flag_converter.rb27
-rw-r--r--lib/rubygems/ext/cmake_builder.rb4
-rw-r--r--lib/rubygems/ext/configure_builder.rb3
-rw-r--r--lib/rubygems/ext/ext_conf_builder.rb105
-rw-r--r--lib/rubygems/ext/rake_builder.rb15
-rw-r--r--lib/rubygems/gem_runner.rb27
-rw-r--r--lib/rubygems/gemcutter_utilities.rb207
-rw-r--r--lib/rubygems/gemcutter_utilities/webauthn_listener.rb105
-rw-r--r--lib/rubygems/gemcutter_utilities/webauthn_listener/response.rb163
-rw-r--r--lib/rubygems/gemcutter_utilities/webauthn_poller.rb78
-rw-r--r--lib/rubygems/gemspec_helpers.rb19
-rw-r--r--lib/rubygems/indexer.rb425
-rw-r--r--lib/rubygems/install_default_message.rb5
-rw-r--r--lib/rubygems/install_message.rb5
-rw-r--r--lib/rubygems/install_update_options.rb134
-rw-r--r--lib/rubygems/installer.rb370
-rw-r--r--lib/rubygems/installer_test_case.rb247
-rw-r--r--lib/rubygems/installer_uninstaller_utils.rb6
-rw-r--r--lib/rubygems/local_remote_options.rb58
-rw-r--r--lib/rubygems/mock_gem_ui.rb85
-rw-r--r--lib/rubygems/name_tuple.rb26
-rw-r--r--lib/rubygems/package.rb258
-rw-r--r--lib/rubygems/package/digest_io.rb3
-rw-r--r--lib/rubygems/package/file_source.rb5
-rw-r--r--lib/rubygems/package/io_source.rb5
-rw-r--r--lib/rubygems/package/old.rb23
-rw-r--r--lib/rubygems/package/source.rb1
-rw-r--r--lib/rubygems/package/tar_header.rb185
-rw-r--r--lib/rubygems/package/tar_reader.rb55
-rw-r--r--lib/rubygems/package/tar_reader/entry.rb129
-rw-r--r--lib/rubygems/package/tar_test_case.rb139
-rw-r--r--lib/rubygems/package/tar_writer.rb55
-rw-r--r--lib/rubygems/package_task.rb13
-rw-r--r--lib/rubygems/path_support.rb29
-rw-r--r--lib/rubygems/platform.rb186
-rw-r--r--lib/rubygems/psych_additions.rb10
-rw-r--r--lib/rubygems/psych_tree.rb7
-rw-r--r--lib/rubygems/query_utils.rb122
-rw-r--r--lib/rubygems/rdoc.rb5
-rw-r--r--lib/rubygems/remote_fetcher.rb118
-rw-r--r--lib/rubygems/request.rb94
-rw-r--r--lib/rubygems/request/connection_pools.rb14
-rw-r--r--lib/rubygems/request/http_pool.rb5
-rw-r--r--lib/rubygems/request/https_pool.rb1
-rw-r--r--lib/rubygems/request_set.rb54
-rw-r--r--lib/rubygems/request_set/gem_dependency_api.rb283
-rw-r--r--lib/rubygems/request_set/lockfile.rb34
-rw-r--r--lib/rubygems/request_set/lockfile/parser.rb61
-rw-r--r--lib/rubygems/request_set/lockfile/tokenizer.rb38
-rw-r--r--lib/rubygems/requirement.rb67
-rw-r--r--lib/rubygems/resolver.rb104
-rw-r--r--lib/rubygems/resolver/activation_request.rb15
-rw-r--r--lib/rubygems/resolver/api_set.rb14
-rw-r--r--lib/rubygems/resolver/api_set/gem_parser.rb10
-rw-r--r--lib/rubygems/resolver/api_specification.rb15
-rw-r--r--lib/rubygems/resolver/best_set.rb15
-rw-r--r--lib/rubygems/resolver/composed_set.rb3
-rw-r--r--lib/rubygems/resolver/conflict.rb35
-rw-r--r--lib/rubygems/resolver/current_set.rb1
-rw-r--r--lib/rubygems/resolver/dependency_request.rb5
-rw-r--r--lib/rubygems/resolver/git_set.rb5
-rw-r--r--lib/rubygems/resolver/git_specification.rb13
-rw-r--r--lib/rubygems/resolver/index_set.rb15
-rw-r--r--lib/rubygems/resolver/index_specification.rb14
-rw-r--r--lib/rubygems/resolver/installed_specification.rb11
-rw-r--r--lib/rubygems/resolver/installer_set.rb67
-rw-r--r--lib/rubygems/resolver/local_specification.rb7
-rw-r--r--lib/rubygems/resolver/lock_set.rb11
-rw-r--r--lib/rubygems/resolver/lock_specification.rb9
-rw-r--r--lib/rubygems/resolver/molinillo.rb2
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb57
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb6
-rw-r--r--lib/rubygems/resolver/requirement_list.rb1
-rw-r--r--lib/rubygems/resolver/set.rb2
-rw-r--r--lib/rubygems/resolver/source_set.rb2
-rw-r--r--lib/rubygems/resolver/spec_specification.rb8
-rw-r--r--lib/rubygems/resolver/specification.rb3
-rw-r--r--lib/rubygems/resolver/stats.rb3
-rw-r--r--lib/rubygems/resolver/vendor_set.rb3
-rw-r--r--lib/rubygems/resolver/vendor_specification.rb7
-rw-r--r--lib/rubygems/s3_uri_signer.rb38
-rw-r--r--lib/rubygems/safe_marshal.rb74
-rw-r--r--lib/rubygems/safe_marshal/elements.rb146
-rw-r--r--lib/rubygems/safe_marshal/reader.rb308
-rw-r--r--lib/rubygems/safe_marshal/visitors/stream_printer.rb31
-rw-r--r--lib/rubygems/safe_marshal/visitors/to_ruby.rb415
-rw-r--r--lib/rubygems/safe_marshal/visitors/visitor.rb74
-rw-r--r--lib/rubygems/safe_yaml.rb42
-rw-r--r--lib/rubygems/security.rb147
-rw-r--r--lib/rubygems/security/policies.rb97
-rw-r--r--lib/rubygems/security/policy.rb59
-rw-r--r--lib/rubygems/security/signer.rb30
-rw-r--r--lib/rubygems/security/trust_dir.rb25
-rw-r--r--lib/rubygems/security_option.rb15
-rw-r--r--lib/rubygems/server.rb882
-rw-r--r--lib/rubygems/shellwords.rb3
-rw-r--r--lib/rubygems/source.rb79
-rw-r--r--lib/rubygems/source/git.rb57
-rw-r--r--lib/rubygems/source/installed.rb5
-rw-r--r--lib/rubygems/source/local.rb88
-rw-r--r--lib/rubygems/source/lock.rb5
-rw-r--r--lib/rubygems/source/specific_file.rb4
-rw-r--r--lib/rubygems/source/vendor.rb3
-rw-r--r--lib/rubygems/source_list.rb16
-rw-r--r--lib/rubygems/spec_fetcher.rb113
-rw-r--r--lib/rubygems/specification.rb713
-rw-r--r--lib/rubygems/specification_policy.rb203
-rw-r--r--lib/rubygems/stub_specification.rb87
-rw-r--r--lib/rubygems/syck_hack.rb77
-rw-r--r--lib/rubygems/test_case.rb1584
-rw-r--r--lib/rubygems/test_utilities.rb373
-rw-r--r--lib/rubygems/text.rb7
-rw-r--r--lib/rubygems/uninstaller.rb115
-rw-r--r--lib/rubygems/update_suggestion.rb56
-rw-r--r--lib/rubygems/uri.rb126
-rw-r--r--lib/rubygems/uri_formatter.rb4
-rw-r--r--lib/rubygems/uri_parser.rb34
-rw-r--r--lib/rubygems/uri_parsing.rb23
-rw-r--r--lib/rubygems/user_interaction.rb84
-rw-r--r--lib/rubygems/util.rb39
-rw-r--r--lib/rubygems/util/licenses.rb422
-rw-r--r--lib/rubygems/util/list.rb5
-rw-r--r--lib/rubygems/validator.rb29
-rw-r--r--lib/rubygems/vendor/molinillo/.document1
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo.rb)4
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb57
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb)22
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb)8
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/errors.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb)60
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/gem_metadata.rb6
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb)4
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/modules/ui.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/resolution.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb)8
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/resolver.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb)2
-rw-r--r--lib/rubygems/vendor/molinillo/lib/molinillo/state.rb (renamed from lib/rubygems/resolver/molinillo/lib/molinillo/state.rb)2
-rw-r--r--lib/rubygems/vendor/net-http/.document1
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http.rb2496
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/backward.rb40
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb34
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb414
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/header.rb981
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/proxy_delta.rb17
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/request.rb88
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/requests.rb425
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/response.rb738
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/responses.rb1174
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/status.rb84
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/https.rb23
-rw-r--r--lib/rubygems/vendor/net-protocol/.document1
-rw-r--r--lib/rubygems/vendor/net-protocol/lib/net/protocol.rb544
-rw-r--r--lib/rubygems/vendor/optparse/.document1
-rw-r--r--lib/rubygems/vendor/optparse/lib/optionparser.rb2
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse.rb2330
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse/ac.rb54
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse/date.rb18
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb22
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse/shellwords.rb7
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse/time.rb11
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse/uri.rb7
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse/version.rb71
-rw-r--r--lib/rubygems/vendor/resolv/.document1
-rw-r--r--lib/rubygems/vendor/resolv/lib/resolv.rb3442
-rw-r--r--lib/rubygems/vendor/timeout/.document1
-rw-r--r--lib/rubygems/vendor/timeout/lib/timeout.rb199
-rw-r--r--lib/rubygems/vendor/tsort/.document1
-rw-r--r--lib/rubygems/vendor/tsort/lib/tsort.rb455
-rw-r--r--lib/rubygems/vendor/uri/.document1
-rw-r--r--lib/rubygems/vendor/uri/lib/uri.rb104
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/common.rb853
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/file.rb100
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/ftp.rb267
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/generic.rb1588
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/http.rb125
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/https.rb23
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/ldap.rb261
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/ldaps.rb22
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/mailto.rb293
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb539
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb183
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/version.rb6
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/ws.rb83
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/wss.rb23
-rw-r--r--lib/rubygems/vendored_molinillo.rb3
-rw-r--r--lib/rubygems/vendored_net_http.rb5
-rw-r--r--lib/rubygems/vendored_optparse.rb3
-rw-r--r--lib/rubygems/vendored_timeout.rb5
-rw-r--r--lib/rubygems/vendored_tsort.rb3
-rw-r--r--lib/rubygems/version.rb110
-rw-r--r--lib/rubygems/version_option.rb18
-rw-r--r--lib/rubygems/yaml_serializer.rb105
260 files changed, 26209 insertions, 8265 deletions
diff --git a/lib/rubygems/available_set.rb b/lib/rubygems/available_set.rb
index 499483d9e9..0af80cc3db 100644
--- a/lib/rubygems/available_set.rb
+++ b/lib/rubygems/available_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
class Gem::AvailableSet
include Enumerable
@@ -26,7 +27,7 @@ class Gem::AvailableSet
s = o.set
when Array
s = o.map do |sp,so|
- if !sp.kind_of?(Gem::Specification) or !so.kind_of?(Gem::Source)
+ if !sp.is_a?(Gem::Specification) || !so.is_a?(Gem::Source)
raise TypeError, "Array must be in [[spec, source], ...] form"
end
@@ -69,7 +70,7 @@ class Gem::AvailableSet
end
def all_specs
- @set.map {|t| t.spec }
+ @set.map(&:spec)
end
def match_platform!
@@ -104,14 +105,14 @@ class Gem::AvailableSet
def to_request_set(development = :none)
request_set = Gem::RequestSet.new
- request_set.development = :all == development
+ request_set.development = development == :all
each_spec do |spec|
request_set.always_install << spec
request_set.gem spec.name, spec.version
request_set.import spec.development_dependencies if
- :shallow == development
+ development == :shallow
end
request_set
@@ -146,11 +147,11 @@ class Gem::AvailableSet
end
def remove_installed!(dep)
- @set.reject! do |t|
+ @set.reject! do |_t|
# already locally installed
Gem::Specification.any? do |installed_spec|
- dep.name == installed_spec.name and
- dep.requirement.satisfied_by? installed_spec.version
+ dep.name == installed_spec.name &&
+ dep.requirement.satisfied_by?(installed_spec.version)
end
end
diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb
index 665b87fc0e..0380fceece 100644
--- a/lib/rubygems/basic_specification.rb
+++ b/lib/rubygems/basic_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# BasicSpecification is an abstract class which implements some common code
# used by both Specification and StubSpecification.
@@ -47,7 +48,7 @@ class Gem::BasicSpecification
# directory.
def gem_build_complete_path # :nodoc:
- File.join extension_dir, 'gem.build_complete'
+ File.join extension_dir, "gem.build_complete"
end
##
@@ -75,15 +76,21 @@ class Gem::BasicSpecification
elsif missing_extensions?
@ignored = true
- if Gem::Platform::RUBY == platform || Gem::Platform.local === platform
- warn "Ignoring #{full_name} because its extensions are not built. " +
- "Try: gem pristine #{name} --version #{version}"
+ if platform == Gem::Platform::RUBY || Gem::Platform.local === platform
+ warn "Ignoring #{full_name} because its extensions are not built. " \
+ "Try: gem pristine #{name} --version #{version}"
end
return false
end
- have_file? file, Gem.suffixes
+ is_soext = file.end_with?(".so", ".o")
+
+ if is_soext
+ have_file? file.delete_suffix(File.extname(file)), Gem.dynamic_library_suffixes
+ else
+ have_file? file, Gem.suffixes
+ end
end
def default_gem?
@@ -95,7 +102,7 @@ class Gem::BasicSpecification
# Returns full path to the directory where gem's extensions are installed.
def extension_dir
- @extension_dir ||= File.expand_path(File.join(extensions_dir, full_name)).tap(&Gem::UNTAINT)
+ @extension_dir ||= File.expand_path(File.join(extensions_dir, full_name))
end
##
@@ -103,15 +110,13 @@ class Gem::BasicSpecification
def extensions_dir
Gem.default_ext_dir_for(base_dir) ||
- File.join(base_dir, 'extensions', Gem::Platform.local.to_s,
+ File.join(base_dir, "extensions", Gem::Platform.local.to_s,
Gem.extension_api_version)
end
def find_full_gem_path # :nodoc:
# TODO: also, shouldn't it default to full_name if it hasn't been written?
- path = File.expand_path File.join(gems_dir, full_name)
- path.tap(&Gem::UNTAINT)
- path
+ File.expand_path File.join(gems_dir, full_name)
end
private :find_full_gem_path
@@ -131,10 +136,10 @@ class Gem::BasicSpecification
# default Ruby platform.
def full_name
- if platform == Gem::Platform::RUBY or platform.nil?
- "#{name}-#{version}".dup.tap(&Gem::UNTAINT)
+ if platform == Gem::Platform::RUBY || platform.nil?
+ "#{name}-#{version}"
else
- "#{name}-#{version}-#{platform}".dup.tap(&Gem::UNTAINT)
+ "#{name}-#{version}-#{platform}"
end
end
@@ -144,15 +149,15 @@ class Gem::BasicSpecification
def full_require_paths
@full_require_paths ||=
- begin
- full_paths = raw_require_paths.map do |path|
- File.join full_gem_path, path.tap(&Gem::UNTAINT)
- end
+ begin
+ full_paths = raw_require_paths.map do |path|
+ File.join full_gem_path, path
+ end
- full_paths << extension_dir if have_extensions?
+ full_paths << extension_dir if have_extensions?
- full_paths
- end
+ full_paths
+ end
end
##
@@ -160,7 +165,7 @@ class Gem::BasicSpecification
def datadir
# TODO: drop the extra ", gem_name" which is uselessly redundant
- File.expand_path(File.join(gems_dir, full_name, "data", name)).tap(&Gem::UNTAINT)
+ File.expand_path(File.join(gems_dir, full_name, "data", name))
end
##
@@ -170,18 +175,14 @@ class Gem::BasicSpecification
def to_fullpath(path)
if activated?
@paths_map ||= {}
- @paths_map[path] ||=
- begin
- fullpath = nil
- suffixes = Gem.suffixes
- suffixes.find do |suf|
- full_require_paths.find do |dir|
- File.file?(fullpath = "#{dir}/#{path}#{suf}")
- end
- end ? fullpath : nil
+ Gem.suffixes.each do |suf|
+ full_require_paths.each do |dir|
+ fullpath = "#{dir}/#{path}#{suf}"
+ next unless File.file?(fullpath)
+ @paths_map[path] ||= fullpath
+ end
end
- else
- nil
+ @paths_map[path]
end
end
@@ -271,9 +272,9 @@ class Gem::BasicSpecification
# Return all files in this gem that match for +glob+.
def matches_for_glob(glob) # TODO: rename?
- glob = File.join(self.lib_dirs_glob, glob)
+ glob = File.join(lib_dirs_glob, glob)
- Dir[glob].map {|f| f.tap(&Gem::UNTAINT) } # FIX our tests are broken, run w/ SAFE=1
+ Dir[glob]
end
##
@@ -288,17 +289,17 @@ class Gem::BasicSpecification
# for this spec.
def lib_dirs_glob
- dirs = if self.raw_require_paths
- if self.raw_require_paths.size > 1
- "{#{self.raw_require_paths.join(',')}}"
- else
- self.raw_require_paths.first
- end
- else
- "lib" # default value for require_paths for bundler/inline
- end
+ dirs = if raw_require_paths
+ if raw_require_paths.size > 1
+ "{#{raw_require_paths.join(",")}}"
+ else
+ raw_require_paths.first
+ end
+ else
+ "lib" # default value for require_paths for bundler/inline
+ end
- "#{self.full_gem_path}/#{dirs}".dup.tap(&Gem::UNTAINT)
+ "#{full_gem_path}/#{dirs}"
end
##
@@ -323,15 +324,19 @@ class Gem::BasicSpecification
raise NotImplementedError
end
- def this; self; end
+ def this
+ self
+ end
private
- def have_extensions?; !extensions.empty?; end
+ def have_extensions?
+ !extensions.empty?
+ end
def have_file?(file, suffixes)
return true if raw_require_paths.any? do |path|
- base = File.join(gems_dir, full_name, path.tap(&Gem::UNTAINT), file).tap(&Gem::UNTAINT)
+ base = File.join(gems_dir, full_name, path, file)
suffixes.any? {|suf| File.file? base + suf }
end
diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb
index 9ce0a2378e..dd2fd77418 100644
--- a/lib/rubygems/bundler_version_finder.rb
+++ b/lib/rubygems/bundler_version_finder.rb
@@ -2,48 +2,18 @@
module Gem::BundlerVersionFinder
def self.bundler_version
- version, _ = bundler_version_with_reason
+ v = ENV["BUNDLER_VERSION"]
- return unless version
+ v ||= bundle_update_bundler_version
+ return if v == true
- Gem::Version.new(version)
- end
-
- def self.bundler_version_with_reason
- if v = ENV["BUNDLER_VERSION"]
- return [v, "`$BUNDLER_VERSION`"]
- end
- if v = bundle_update_bundler_version
- return if v == true
- return [v, "`bundle update --bundler`"]
- end
- v, lockfile = lockfile_version
- if v
- return [v, "your #{lockfile}"]
- end
- end
+ v ||= lockfile_version
+ return unless v
- def self.missing_version_message
- return unless vr = bundler_version_with_reason
- <<-EOS
-Could not find 'bundler' (#{vr.first}) required by #{vr.last}.
-To update to the latest version installed on your system, run `bundle update --bundler`.
-To install the missing version, run `gem install bundler:#{vr.first}`
- EOS
+ Gem::Version.new(v)
end
- def self.compatible?(spec)
- return true unless spec.name == "bundler".freeze
- return true unless bundler_version = self.bundler_version
-
- spec.version.segments.first == bundler_version.segments.first
- end
-
- def self.filter!(specs)
- return unless bundler_version = self.bundler_version
-
- specs.reject! {|spec| spec.version.segments.first != bundler_version.segments.first }
-
+ def self.prioritize!(specs)
exact_match_index = specs.find_index {|spec| spec.version == bundler_version }
return unless exact_match_index
@@ -51,7 +21,7 @@ To install the missing version, run `gem install bundler:#{vr.first}`
end
def self.bundle_update_bundler_version
- return unless File.basename($0) == "bundle".freeze
+ return unless File.basename($0) == "bundle"
return unless "update".start_with?(ARGV.first || " ")
bundler_version = nil
update_index = nil
@@ -68,23 +38,21 @@ To install the missing version, run `gem install bundler:#{vr.first}`
private_class_method :bundle_update_bundler_version
def self.lockfile_version
- return unless lockfile = lockfile_contents
- lockfile, contents = lockfile
- lockfile ||= "lockfile"
+ return unless contents = lockfile_contents
regexp = /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
return unless contents =~ regexp
- [$1, lockfile]
+ $1
end
private_class_method :lockfile_version
def self.lockfile_contents
gemfile = ENV["BUNDLE_GEMFILE"]
- gemfile = nil if gemfile && gemfile.empty?
+ gemfile = nil if gemfile&.empty?
unless gemfile
begin
Gem::Util.traverse_parents(Dir.pwd) do |directory|
- next unless gemfile = Gem::GEM_DEP_FILES.find {|f| File.file?(f.tap(&Gem::UNTAINT)) }
+ next unless gemfile = Gem::GEM_DEP_FILES.find {|f| File.file?(f) }
gemfile = File.join directory, gemfile
break
@@ -99,11 +67,11 @@ To install the missing version, run `gem install bundler:#{vr.first}`
lockfile = case gemfile
when "gems.rb" then "gems.locked"
else "#{gemfile}.lock"
- end.dup.tap(&Gem::UNTAINT)
+ end
return unless File.file?(lockfile)
- [lockfile, File.read(lockfile)]
+ File.read(lockfile)
end
private_class_method :lockfile_contents
end
diff --git a/lib/rubygems/ci_detector.rb b/lib/rubygems/ci_detector.rb
new file mode 100644
index 0000000000..7a2d4ee29a
--- /dev/null
+++ b/lib/rubygems/ci_detector.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Gem
+ module CIDetector
+ # NOTE: Any changes made here will need to be made to both lib/rubygems/ci_detector.rb and
+ # bundler/lib/bundler/ci_detector.rb (which are enforced duplicates).
+ # TODO: Drop that duplication once bundler drops support for RubyGems 3.4
+ #
+ # ## Recognized CI providers, their signifiers, and the relevant docs ##
+ #
+ # Travis CI - CI, TRAVIS https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
+ # Cirrus CI - CI, CIRRUS_CI https://cirrus-ci.org/guide/writing-tasks/#environment-variables
+ # Circle CI - CI, CIRCLECI https://circleci.com/docs/variables/#built-in-environment-variables
+ # Gitlab CI - CI, GITLAB_CI https://docs.gitlab.com/ee/ci/variables/
+ # AppVeyor - CI, APPVEYOR https://www.appveyor.com/docs/environment-variables/
+ # CodeShip - CI_NAME https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables#_default_environment_variables
+ # dsari - CI, DSARI https://github.com/rfinnie/dsari#running
+ # Jenkins - BUILD_NUMBER https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
+ # TeamCity - TEAMCITY_VERSION https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters
+ # Appflow - CI_BUILD_ID https://ionic.io/docs/appflow/automation/environments#predefined-environments
+ # TaskCluster - TASKCLUSTER_ROOT_URL https://docs.taskcluster.net/docs/manual/design/env-vars
+ # Semaphore - CI, SEMAPHORE https://docs.semaphoreci.com/ci-cd-environment/environment-variables/
+ # BuildKite - CI, BUILDKITE https://buildkite.com/docs/pipelines/environment-variables
+ # GoCD - GO_SERVER_URL https://docs.gocd.org/current/faq/dev_use_current_revision_in_build.html
+ # GH Actions - CI, GITHUB_ACTIONS https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
+ #
+ # ### Some "standard" ENVs that multiple providers may set ###
+ #
+ # * CI - this is set by _most_ (but not all) CI providers now; it's approaching a standard.
+ # * CI_NAME - Not as frequently used, but some providers set this to specify their own name
+
+ # Any of these being set is a reasonably reliable indicator that we are
+ # executing in a CI environment.
+ ENV_INDICATORS = [
+ "CI",
+ "CI_NAME",
+ "CONTINUOUS_INTEGRATION",
+ "BUILD_NUMBER",
+ "CI_APP_ID",
+ "CI_BUILD_ID",
+ "CI_BUILD_NUMBER",
+ "RUN_ID",
+ "TASKCLUSTER_ROOT_URL",
+ ].freeze
+
+ # For each CI, this env suffices to indicate that we're on _that_ CI's
+ # containers. (A few of them only supply a CI_NAME variable, which is also
+ # nice). And if they set "CI" but we can't tell which one they are, we also
+ # want to know that - a bare "ci" without another token tells us as much.
+ ENV_DESCRIPTORS = {
+ "TRAVIS" => "travis",
+ "CIRCLECI" => "circle",
+ "CIRRUS_CI" => "cirrus",
+ "DSARI" => "dsari",
+ "SEMAPHORE" => "semaphore",
+ "JENKINS_URL" => "jenkins",
+ "BUILDKITE" => "buildkite",
+ "GO_SERVER_URL" => "go",
+ "GITLAB_CI" => "gitlab",
+ "GITHUB_ACTIONS" => "github",
+ "TASKCLUSTER_ROOT_URL" => "taskcluster",
+ "CI" => "ci",
+ }.freeze
+
+ def self.ci?
+ ENV_INDICATORS.any? {|var| ENV.include?(var) }
+ end
+
+ def self.ci_strings
+ matching_names = ENV_DESCRIPTORS.select {|env, _| ENV[env] }.values
+ matching_names << ENV["CI_NAME"].downcase if ENV["CI_NAME"]
+ matching_names.reject(&:empty?).sort.uniq
+ end
+ end
+end
diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb
index 9f935e6285..ec498a8b94 100644
--- a/lib/rubygems/command.rb
+++ b/lib/rubygems/command.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'optparse'
-require_relative 'requirement'
-require_relative 'user_interaction'
+require_relative "vendored_optparse"
+require_relative "requirement"
+require_relative "user_interaction"
##
# Base class for all Gem commands. When creating a new gem command, define
@@ -19,9 +20,7 @@ require_relative 'user_interaction'
class Gem::Command
include Gem::UserInteraction
- OptionParser.accept Symbol do |value|
- value.to_sym
- end
+ Gem::OptionParser.accept Symbol, &:to_sym
##
# The name of the command.
@@ -76,7 +75,7 @@ class Gem::Command
when Array
@extra_args = value
when String
- @extra_args = value.split(' ')
+ @extra_args = value.split(" ")
end
end
@@ -93,7 +92,7 @@ class Gem::Command
# array or a string to be split on white space.
def self.add_specific_extra_args(cmd,args)
- args = args.split(/\s+/) if args.kind_of? String
+ args = args.split(/\s+/) if args.is_a? String
specific_extra_args_hash[cmd] = args
end
@@ -159,11 +158,11 @@ class Gem::Command
gem = "'#{gem_name}' (#{version})"
msg = String.new "Could not find a valid gem #{gem}"
- if errors and !errors.empty?
+ if errors && !errors.empty?
msg << ", here is why:\n"
errors.each {|x| msg << " #{x.wordy}\n" }
else
- if required_by and gem != required_by
+ if required_by && gem != required_by
msg << " (required by #{required_by}) in any repository"
else
msg << " in any repository"
@@ -186,12 +185,12 @@ class Gem::Command
def get_all_gem_names
args = options[:args]
- if args.nil? or args.empty?
+ if args.nil? || args.empty?
raise Gem::CommandLineError,
"Please specify at least one gem name (e.g. gem build GEMNAME)"
end
- args.select {|arg| arg !~ /^-/ }
+ args.reject {|arg| arg.start_with?("-") }
end
##
@@ -201,11 +200,15 @@ class Gem::Command
# respectively.
def get_all_gem_names_and_versions
get_all_gem_names.map do |name|
- if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name
- [$1, $2]
- else
- [name]
- end
+ extract_gem_name_and_version(name)
+ end
+ end
+
+ def extract_gem_name_and_version(name) # :nodoc:
+ if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name
+ [$1, $2]
+ else
+ [name]
end
end
@@ -216,14 +219,14 @@ class Gem::Command
def get_one_gem_name
args = options[:args]
- if args.nil? or args.empty?
+ if args.nil? || args.empty?
raise Gem::CommandLineError,
"Please specify a gem name on the command line (e.g. gem build GEMNAME)"
end
if args.size > 1
raise Gem::CommandLineError,
- "Too many gem names (#{args.join(', ')}); please specify only one"
+ "Too many gem names (#{args.join(", ")}); please specify only one"
end
args.first
@@ -311,7 +314,7 @@ class Gem::Command
options[:build_args] = build_args
if options[:silent]
- old_ui = self.ui
+ old_ui = ui
self.ui = ui = Gem::SilentUI.new
end
@@ -344,7 +347,7 @@ class Gem::Command
##
# Add a command-line option and handler to the command.
#
- # See OptionParser#make_switch for an explanation of +opts+.
+ # See Gem::OptionParser#make_switch for an explanation of +opts+.
#
# +handler+ will be called with two values, the value of the argument and
# the options hash.
@@ -355,6 +358,8 @@ class Gem::Command
def add_option(*opts, &handler) # :yields: value, options
group_name = Symbol === opts.first ? opts.shift : :options
+ raise "Do not pass an empty string in opts" if opts.include?("")
+
@option_groups[group_name] << [opts, handler]
end
@@ -391,22 +396,21 @@ class Gem::Command
def check_deprecated_options(options)
options.each do |option|
- if option_is_deprecated?(option)
- deprecation = @deprecated_options[command][option]
- version_to_expire = deprecation["rg_version_to_expire"]
+ next unless option_is_deprecated?(option)
+ 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}."
- else
- "The \"#{option}\" option has been deprecated and will be removed in future versions of Rubygems."
- end
+ deprecate_option_msg = if version_to_expire
+ "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."
+ end
- extra_msg = deprecation["extra_msg"]
+ extra_msg = deprecation["extra_msg"]
- deprecate_option_msg += " #{extra_msg}" if extra_msg
+ deprecate_option_msg += " #{extra_msg}" if extra_msg
- alert_warning(deprecate_option_msg)
- end
+ alert_warning(deprecate_option_msg)
end
end
@@ -423,12 +427,10 @@ class Gem::Command
# True if the command handles the given argument list.
def handles?(args)
- begin
- parser.parse!(args.dup)
- return true
- rescue
- return false
- end
+ parser.parse!(args.dup)
+ true
+ rescue StandardError
+ false
end
##
@@ -455,7 +457,7 @@ class Gem::Command
until extra.empty? do
ex = []
ex << extra.shift
- ex << extra.shift if extra.first.to_s =~ /^[^-]/ # rubocop:disable Performance/StartWith
+ ex << extra.shift if /^[^-]/.match?(extra.first.to_s)
result << ex if handles?(ex)
end
@@ -471,7 +473,7 @@ class Gem::Command
private
def option_is_deprecated?(option)
- @deprecated_options[command].has_key?(option)
+ @deprecated_options[command].key?(option)
end
def add_parser_description # :nodoc:
@@ -483,7 +485,7 @@ class Gem::Command
@parser.separator nil
@parser.separator " Description:"
- formatted.split("\n").each do |line|
+ formatted.each_line do |line|
@parser.separator " #{line.rstrip}"
end
end
@@ -510,8 +512,8 @@ class Gem::Command
@parser.separator nil
@parser.separator " #{title}:"
- content.split(/\n/).each do |line|
- @parser.separator " #{line}"
+ content.each_line do |line|
+ @parser.separator " #{line.rstrip}"
end
end
@@ -520,7 +522,7 @@ class Gem::Command
@parser.separator nil
@parser.separator " Summary:"
- wrap(@summary, 80 - 4).split("\n").each do |line|
+ wrap(@summary, 80 - 4).each_line do |line|
@parser.separator " #{line.strip}"
end
end
@@ -538,7 +540,7 @@ class Gem::Command
# command.
def create_option_parser
- @parser = OptionParser.new
+ @parser = Gem::OptionParser.new
add_parser_options
@@ -552,9 +554,9 @@ class Gem::Command
end
def configure_options(header, option_list)
- return if option_list.nil? or option_list.empty?
+ return if option_list.nil? || option_list.empty?
- header = header.to_s.empty? ? '' : "#{header} "
+ header = header.to_s.empty? ? "" : "#{header} "
@parser.separator " #{header}Options:"
option_list.each do |args, handler|
@@ -563,7 +565,7 @@ class Gem::Command
end
end
- @parser.separator ''
+ @parser.separator ""
end
##
@@ -576,27 +578,27 @@ class Gem::Command
# ----------------------------------------------------------------
# Add the options common to all commands.
- add_common_option('-h', '--help',
- 'Get help on this command') do |value, options|
+ add_common_option("-h", "--help",
+ "Get help on this command") do |_value, options|
options[:help] = true
end
- add_common_option('-V', '--[no-]verbose',
- 'Set the verbose level of output') do |value, options|
+ add_common_option("-V", "--[no-]verbose",
+ "Set the verbose level of output") do |value, _options|
# Set us to "really verbose" so the progress meter works
- if Gem.configuration.verbose and value
+ if Gem.configuration.verbose && value
Gem.configuration.verbose = 1
else
Gem.configuration.verbose = value
end
end
- add_common_option('-q', '--quiet', 'Silence command progress meter') do |value, options|
+ add_common_option("-q", "--quiet", "Silence command progress meter") do |_value, _options|
Gem.configuration.verbose = false
end
add_common_option("--silent",
- "Silence RubyGems output") do |value, options|
+ "Silence RubyGems output") do |_value, options|
options[:silent] = true
end
@@ -604,31 +606,35 @@ class Gem::Command
# commands. Both options are actually handled before the other
# options get parsed.
- add_common_option('--config-file FILE',
- 'Use this config file instead of default') do
+ add_common_option("--config-file FILE",
+ "Use this config file instead of default") do
end
- add_common_option('--backtrace',
- 'Show stack backtrace on errors') do
+ add_common_option("--backtrace",
+ "Show stack backtrace on errors") do
end
- add_common_option('--debug',
- 'Turn on Ruby debugging') do
+ add_common_option("--debug",
+ "Turn on Ruby debugging") do
end
- add_common_option('--norc',
- 'Avoid loading any .gemrc file') do
+ add_common_option("--norc",
+ "Avoid loading any .gemrc file") do
end
# :stopdoc:
- HELP = <<-HELP.freeze
+ HELP = <<-HELP
RubyGems is a package manager for Ruby.
Usage:
gem -h/--help
gem -v/--version
- gem command [arguments...] [options...]
+ gem [global options...] command [arguments...] [options...]
+
+ Global options:
+ -C PATH run as if gem was started in <PATH>
+ instead of the current working directory
Examples:
gem install rake
diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb
index 2409550882..8e578dc196 100644
--- a/lib/rubygems/command_manager.rb
+++ b/lib/rubygems/command_manager.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/command'
-require 'rubygems/user_interaction'
-require 'rubygems/text'
+require_relative "command"
+require_relative "user_interaction"
+require_relative "text"
##
# The command manager registers and installs all the individual sub-commands
@@ -43,6 +44,7 @@ class Gem::CommandManager
:contents,
:dependency,
:environment,
+ :exec,
:fetch,
:generate_index,
:help,
@@ -58,6 +60,7 @@ class Gem::CommandManager
:push,
:query,
:rdoc,
+ :rebuild,
:search,
:server,
:signin,
@@ -73,14 +76,16 @@ class Gem::CommandManager
].freeze
ALIAS_COMMANDS = {
- 'i' => 'install',
+ "i" => "install",
+ "login" => "signin",
+ "logout" => "signout",
}.freeze
##
# Return the authoritative instance of the command manager.
def self.instance
- @command_manager ||= new
+ @instance ||= new
end
##
@@ -95,14 +100,14 @@ class Gem::CommandManager
# Reset the authoritative instance of the command manager.
def self.reset
- @command_manager = nil
+ @instance = nil
end
##
# Register all the subcommands supported by the gem command.
def initialize
- require 'timeout'
+ require_relative "vendored_timeout"
@commands = {}
BUILTIN_COMMANDS.each do |name|
@@ -137,7 +142,7 @@ class Gem::CommandManager
# Return a sorted list of all command names as strings.
def command_names
- @commands.keys.collect {|key| key.to_s }.sort
+ @commands.keys.collect(&:to_s).sort
end
##
@@ -145,8 +150,13 @@ class Gem::CommandManager
def run(args, build_args=nil)
process_args(args, build_args)
- rescue StandardError, Timeout::Error => ex
- alert_error clean_text("While executing gem ... (#{ex.class})\n #{ex}")
+ rescue StandardError, Gem::Timeout::Error => ex
+ if ex.respond_to?(:detailed_message)
+ msg = ex.detailed_message(highlight: false).sub(/\A(.*?)(?: \(.+?\))/) { $1 }
+ else
+ msg = ex.message
+ end
+ alert_error clean_text("While executing gem ... (#{ex.class})\n #{msg}")
ui.backtrace ex
terminate_interaction(1)
@@ -162,20 +172,26 @@ class Gem::CommandManager
end
case args.first
- when '-h', '--help' then
+ when "-h", "--help" then
say Gem::Command::HELP
terminate_interaction 0
- when '-v', '--version' then
+ when "-v", "--version" then
say Gem::VERSION
terminate_interaction 0
+ when "-C" then
+ args.shift
+ start_point = args.shift
+ if Dir.exist?(start_point)
+ Dir.chdir(start_point) { invoke_command(args, build_args) }
+ else
+ alert_error clean_text("#{start_point} isn't a directory.")
+ terminate_interaction 1
+ end
when /^-/ then
alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.")
terminate_interaction 1
else
- cmd_name = args.shift.downcase
- cmd = find_command cmd_name
- cmd.deprecation_warning if cmd.deprecated?
- cmd.invoke_with_build_args args, build_args
+ invoke_command(args, build_args)
end
end
@@ -186,7 +202,7 @@ class Gem::CommandManager
if possibilities.size > 1
raise Gem::CommandLineError,
- "Ambiguous command #{cmd_name} matches [#{possibilities.join(', ')}]"
+ "Ambiguous command #{cmd_name} matches [#{possibilities.join(", ")}]"
elsif possibilities.empty?
raise Gem::UnknownCommandError.new(cmd_name)
end
@@ -223,11 +239,19 @@ class Gem::CommandManager
load_error = e
end
Gem::Commands.const_get(const_name).new
- rescue Exception => e
+ rescue StandardError => e
e = load_error if load_error
alert_error clean_text("Loading command: #{command_name} (#{e.class})\n\t#{e}")
ui.backtrace e
end
end
+
+ def invoke_command(args, build_args)
+ cmd_name = args.shift.downcase
+ cmd = find_command cmd_name
+ terminate_interaction 1 unless cmd
+ cmd.deprecation_warning if cmd.deprecated?
+ cmd.invoke_with_build_args args, build_args
+ end
end
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb
index fff5f7c76f..2ec8324141 100644
--- a/lib/rubygems/commands/build_command.rb
+++ b/lib/rubygems/commands/build_command.rb
@@ -1,31 +1,37 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/package'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../gemspec_helpers"
+require_relative "../package"
+require_relative "../version_option"
class Gem::Commands::BuildCommand < Gem::Command
include Gem::VersionOption
+ include Gem::GemspecHelpers
def initialize
- super 'build', 'Build a gem from a gemspec'
+ super "build", "Build a gem from a gemspec"
add_platform_option
- add_option '--force', 'skip validation of the spec' do |value, options|
+ 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|
+ add_option "--strict", "consider warnings as errors when validating the spec" do |_value, options|
options[:strict] = true
end
- add_option '-o', '--output FILE', 'output gem with the given filename' do |value, options|
+ add_option "-o", "--output FILE", "output gem with the given filename" do |value, options|
options[:output] = value
end
- add_option '-C PATH', '', 'Run as if gem build was started in <PATH> instead of the current working directory.' do |value, options|
+ add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, options|
options[:build_path] = value
end
+ deprecate_option "-C",
+ version: "4.0",
+ extra_msg: "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead"
end
def arguments # :nodoc:
@@ -71,17 +77,6 @@ Gems can be saved to a specified filename with the output option:
private
- def find_gemspec(glob = "*.gemspec")
- gemspecs = Dir.glob(glob).sort
-
- if gemspecs.size > 1
- alert_error "Multiple gemspecs found: #{gemspecs}, please specify one"
- terminate_interaction(1)
- end
-
- gemspecs.first
- end
-
def build_gem
gemspec = resolve_gem_name
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index 998df0621b..72dcf1dd17 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -1,92 +1,70 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/security'
+
+require_relative "../command"
+require_relative "../security"
class Gem::Commands::CertCommand < Gem::Command
def initialize
- super 'cert', 'Manage RubyGems certificates and signing settings',
- :add => [], :remove => [], :list => [], :build => [], :sign => []
-
- OptionParser.accept OpenSSL::X509::Certificate do |certificate_file|
- begin
- certificate = OpenSSL::X509::Certificate.new File.read certificate_file
- rescue Errno::ENOENT
- raise OptionParser::InvalidArgument, "#{certificate_file}: does not exist"
- rescue OpenSSL::X509::CertificateError
- raise OptionParser::InvalidArgument,
- "#{certificate_file}: invalid X509 certificate"
- end
- [certificate, certificate_file]
- end
-
- OptionParser.accept OpenSSL::PKey::RSA do |key_file|
- begin
- passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase
- rescue Errno::ENOENT
- raise OptionParser::InvalidArgument, "#{key_file}: does not exist"
- rescue OpenSSL::PKey::RSAError
- raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key"
- end
-
- raise OptionParser::InvalidArgument,
- "#{key_file}: private key not found" unless key.private?
-
- key
- end
+ super "cert", "Manage RubyGems certificates and signing settings",
+ add: [], remove: [], list: [], build: [], sign: []
- add_option('-a', '--add CERT', OpenSSL::X509::Certificate,
- 'Add a trusted certificate.') do |(cert, _), options|
- options[:add] << cert
+ add_option("-a", "--add CERT",
+ "Add a trusted certificate.") do |cert_file, options|
+ options[:add] << open_cert(cert_file)
end
- add_option('-l', '--list [FILTER]',
- 'List trusted certificates where the',
- 'subject contains FILTER') do |filter, options|
- filter ||= ''
+ add_option("-l", "--list [FILTER]",
+ "List trusted certificates where the",
+ "subject contains FILTER") do |filter, options|
+ filter ||= ""
options[:list] << filter
end
- add_option('-r', '--remove FILTER',
- 'Remove trusted certificates where the',
- 'subject contains FILTER') do |filter, options|
+ add_option("-r", "--remove FILTER",
+ "Remove trusted certificates where the",
+ "subject contains FILTER") do |filter, options|
options[:remove] << filter
end
- add_option('-b', '--build EMAIL_ADDR',
- 'Build private key and self-signed',
- 'certificate for EMAIL_ADDR') do |email_address, options|
+ add_option("-b", "--build EMAIL_ADDR",
+ "Build private key and self-signed",
+ "certificate for EMAIL_ADDR") do |email_address, options|
options[:build] << email_address
end
- add_option('-C', '--certificate CERT', OpenSSL::X509::Certificate,
- 'Signing certificate for --sign') do |(cert, cert_file), options|
- options[:issuer_cert] = cert
+ add_option("-C", "--certificate CERT",
+ "Signing certificate for --sign") do |cert_file, options|
+ options[:issuer_cert] = open_cert(cert_file)
options[:issuer_cert_file] = cert_file
end
- add_option('-K', '--private-key KEY', OpenSSL::PKey::RSA,
- 'Key for --sign or --build') do |key, options|
- options[:key] = key
+ add_option("-K", "--private-key KEY",
+ "Key for --sign or --build") do |key_file, options|
+ options[:key] = open_private_key(key_file)
+ end
+
+ add_option("-A", "--key-algorithm ALGORITHM",
+ "Select which key algorithm to use for --build") do |algorithm, options|
+ options[:key_algorithm] = algorithm
end
- add_option('-s', '--sign CERT',
- 'Signs CERT with the key from -K',
- 'and the certificate from -C') do |cert_file, options|
- raise OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless
+ add_option("-s", "--sign CERT",
+ "Signs CERT with the key from -K",
+ "and the certificate from -C") do |cert_file, options|
+ raise Gem::OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless
File.file? cert_file
options[:sign] << cert_file
end
- add_option('-d', '--days NUMBER_OF_DAYS',
- 'Days before the certificate expires') do |days, options|
+ add_option("-d", "--days NUMBER_OF_DAYS",
+ "Days before the certificate expires") do |days, options|
options[:expiration_length_days] = days.to_i
end
- add_option('-R', '--re-sign',
- 'Re-signs the certificate from -C with the key from -K') do |resign, options|
+ add_option("-R", "--re-sign",
+ "Re-signs the certificate from -C with the key from -K") do |resign, options|
options[:resign] = resign
end
end
@@ -97,7 +75,39 @@ class Gem::Commands::CertCommand < Gem::Command
say "Added '#{certificate.subject}'"
end
+ def check_openssl
+ return if Gem::HAVE_OPENSSL
+
+ alert_error "OpenSSL library is required for the cert command"
+ terminate_interaction 1
+ end
+
+ def open_cert(certificate_file)
+ check_openssl
+ OpenSSL::X509::Certificate.new File.read certificate_file
+ rescue Errno::ENOENT
+ raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: does not exist"
+ rescue OpenSSL::X509::CertificateError
+ raise Gem::OptionParser::InvalidArgument,
+ "#{certificate_file}: invalid X509 certificate"
+ end
+
+ def open_private_key(key_file)
+ check_openssl
+ passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"]
+ key = OpenSSL::PKey.read File.read(key_file), passphrase
+ raise Gem::OptionParser::InvalidArgument,
+ "#{key_file}: private key not found" unless key.private?
+ key
+ rescue Errno::ENOENT
+ raise Gem::OptionParser::InvalidArgument, "#{key_file}: does not exist"
+ rescue OpenSSL::PKey::PKeyError, ArgumentError
+ raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key"
+ end
+
def execute
+ check_openssl
+
options[:add].each do |certificate|
add_certificate certificate
end
@@ -126,7 +136,7 @@ class Gem::Commands::CertCommand < Gem::Command
end
def build(email)
- if !valid_email?(email)
+ unless valid_email?(email)
raise Gem::CommandLineError, "Invalid email address #{email}"
end
@@ -143,7 +153,7 @@ class Gem::Commands::CertCommand < Gem::Command
def build_cert(email, key) # :nodoc:
expiration_length_days = options[:expiration_length_days] ||
- Gem.configuration.cert_expiration_length_days
+ Gem.configuration.cert_expiration_length_days
cert = Gem::Security.create_cert_email(
email,
@@ -157,19 +167,20 @@ class Gem::Commands::CertCommand < Gem::Command
def build_key # :nodoc:
return options[:key] if options[:key]
- passphrase = ask_for_password 'Passphrase for your Private Key:'
+ passphrase = ask_for_password "Passphrase for your Private Key:"
say "\n"
- passphrase_confirmation = ask_for_password 'Please repeat the passphrase for your Private Key:'
+ passphrase_confirmation = ask_for_password "Please repeat the passphrase for your Private Key:"
say "\n"
raise Gem::CommandLineError,
"Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation
- key = Gem::Security.create_key
- key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase
+ algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM
+ key = Gem::Security.create_key(algorithm)
+ key_path = Gem::Security.write key, "gem-private_key.pem", 0o600, passphrase
- return key, key_path
+ [key, key_path]
end
def certificates_matching(filter)
@@ -250,14 +261,14 @@ For further reading on signing gems see `ri Gem::Security`.
def load_default_key
key_file = File.join Gem.default_key_path
key = File.read key_file
- passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- options[:key] = OpenSSL::PKey::RSA.new key, passphrase
+ passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"]
+ options[:key] = OpenSSL::PKey.read key, passphrase
rescue Errno::ENOENT
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
terminate_interaction 1
- rescue OpenSSL::PKey::RSAError
+ rescue OpenSSL::PKey::PKeyError
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem is not valid"
@@ -280,7 +291,7 @@ For further reading on signing gems see `ri Gem::Security`.
cert = File.read cert_file
cert = OpenSSL::X509::Certificate.new cert
- permissions = File.stat(cert_file).mode & 0777
+ permissions = File.stat(cert_file).mode & 0o777
issuer_cert = options[:issuer_cert]
issuer_key = options[:key]
@@ -311,4 +322,4 @@ For further reading on signing gems see `ri Gem::Security`.
# It's simple, but is all we need
email =~ /\A.+@.+\z/
end
-end if Gem::HAVE_OPENSSL
+end
diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb
index 8b8eda53cf..fb23dd9cb4 100644
--- a/lib/rubygems/commands/check_command.rb
+++ b/lib/rubygems/commands/check_command.rb
@@ -1,63 +1,68 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/validator'
-require 'rubygems/doctor'
+
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../validator"
+require_relative "../doctor"
class Gem::Commands::CheckCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'check', 'Check a gem repository for added or missing files',
- :alien => true, :doctor => false, :dry_run => false, :gems => true
+ super "check", "Check a gem repository for added or missing files",
+ alien: true, doctor: false, dry_run: false, gems: true
- add_option('-a', '--[no-]alien',
+ add_option("-a", "--[no-]alien",
'Report "unmanaged" or rogue files in the',
- 'gem repository') do |value, options|
+ "gem repository") do |value, options|
options[:alien] = value
end
- add_option('--[no-]doctor',
- 'Clean up uninstalled gems and broken',
- 'specifications') do |value, options|
+ add_option("--[no-]doctor",
+ "Clean up uninstalled gems and broken",
+ "specifications") do |value, options|
options[:doctor] = value
end
- add_option('--[no-]dry-run',
- 'Do not remove files, only report what',
- 'would be removed') do |value, options|
+ add_option("--[no-]dry-run",
+ "Do not remove files, only report what",
+ "would be removed") do |value, options|
options[:dry_run] = value
end
- add_option('--[no-]gems',
- 'Check installed gems for problems') do |value, options|
+ add_option("--[no-]gems",
+ "Check installed gems for problems") do |value, options|
options[:gems] = value
end
- add_version_option 'check'
+ add_version_option "check"
end
def check_gems
- say 'Checking gems...'
+ say "Checking gems..."
say
- gems = get_all_gem_names rescue []
+ gems = begin
+ get_all_gem_names
+ rescue StandardError
+ []
+ end
Gem::Validator.new.alien(gems).sort.each do |key, val|
- unless val.empty?
+ if val.empty?
+ say "#{key} is error-free" if Gem.configuration.verbose
+ else
say "#{key} has #{val.size} problems"
val.each do |error_entry|
say " #{error_entry.path}:"
say " #{error_entry.problem}"
end
- else
- say "#{key} is error-free" if Gem.configuration.verbose
end
say
end
end
def doctor
- say 'Checking for files from uninstalled gems...'
+ say "Checking for files from uninstalled gems..."
say
Gem.path.each do |gem_repo|
@@ -72,11 +77,11 @@ class Gem::Commands::CheckCommand < Gem::Command
end
def arguments # :nodoc:
- 'GEMNAME name of gem to check'
+ "GEMNAME name of gem to check"
end
def defaults_str # :nodoc:
- '--gems --alien'
+ "--gems --alien"
end
def description # :nodoc:
diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb
index 662badce33..08fb598cea 100644
--- a/lib/rubygems/commands/cleanup_command.rb
+++ b/lib/rubygems/commands/cleanup_command.rb
@@ -1,35 +1,36 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/dependency_list'
-require 'rubygems/uninstaller'
+
+require_relative "../command"
+require_relative "../dependency_list"
+require_relative "../uninstaller"
class Gem::Commands::CleanupCommand < Gem::Command
def initialize
- super 'cleanup',
- 'Clean up old versions of installed gems',
- :force => false, :install_dir => Gem.dir,
- :check_dev => true
+ super "cleanup",
+ "Clean up old versions of installed gems",
+ force: false, install_dir: Gem.dir,
+ check_dev: true
- add_option('-n', '-d', '--dry-run',
- 'Do not uninstall gems') do |value, options|
+ add_option("-n", "-d", "--dry-run",
+ "Do not uninstall gems") do |_value, options|
options[:dryrun] = true
end
- add_option(:Deprecated, '--dryrun',
- 'Do not uninstall gems') do |value, options|
+ add_option(:Deprecated, "--dryrun",
+ "Do not uninstall gems") do |_value, options|
options[:dryrun] = true
end
- deprecate_option('--dryrun', extra_msg: 'Use --dry-run instead')
+ deprecate_option("--dryrun", extra_msg: "Use --dry-run instead")
- add_option('-D', '--[no-]check-development',
- 'Check development dependencies while uninstalling',
- '(default: true)') do |value, options|
+ add_option("-D", "--[no-]check-development",
+ "Check development dependencies while uninstalling",
+ "(default: true)") do |value, options|
options[:check_dev] = value
end
- add_option('--[no-]user-install',
- 'Cleanup in user\'s home directory instead',
- 'of GEM_HOME.') do |value, options|
+ add_option("--[no-]user-install",
+ "Cleanup in user's home directory instead",
+ "of GEM_HOME.") do |value, options|
options[:user_install] = value
end
@@ -74,7 +75,7 @@ If no gems are named all gems in GEM_HOME are cleaned.
until done do
clean_gems
- this_set = @gems_to_cleanup.map {|spec| spec.full_name }.sort
+ this_set = @gems_to_cleanup.map(&:full_name).sort
done = this_set.empty? || last_set == this_set
@@ -87,9 +88,9 @@ If no gems are named all gems in GEM_HOME are cleaned.
say "Clean up complete"
verbose do
- skipped = @default_gems.map {|spec| spec.full_name }
+ skipped = @default_gems.map(&:full_name)
- "Skipped default gems: #{skipped.join ', '}"
+ "Skipped default gems: #{skipped.join ", "}"
end
end
@@ -116,13 +117,13 @@ If no gems are named all gems in GEM_HOME are cleaned.
end
def get_candidate_gems
- @candidate_gems = unless options[:args].empty?
- options[:args].map do |gem_name|
- Gem::Specification.find_all_by_name gem_name
- end.flatten
- else
- Gem::Specification.to_a
- end
+ @candidate_gems = if options[:args].empty?
+ Gem::Specification.to_a
+ else
+ options[:args].map do |gem_name|
+ Gem::Specification.find_all_by_name gem_name
+ end.flatten
+ end
end
def get_gems_to_cleanup
@@ -130,9 +131,7 @@ If no gems are named all gems in GEM_HOME are cleaned.
@primary_gems[spec.name].version != spec.version
end
- default_gems, gems_to_cleanup = gems_to_cleanup.partition do |spec|
- spec.default_gem?
- end
+ default_gems, gems_to_cleanup = gems_to_cleanup.partition(&:default_gem?)
uninstall_from = options[:user_install] ? Gem.user_dir : @original_home
@@ -149,7 +148,7 @@ If no gems are named all gems in GEM_HOME are cleaned.
@primary_gems = {}
Gem::Specification.each do |spec|
- if @primary_gems[spec.name].nil? or
+ if @primary_gems[spec.name].nil? ||
@primary_gems[spec.name].version < spec.version
@primary_gems[spec.name] = spec
end
@@ -167,8 +166,8 @@ If no gems are named all gems in GEM_HOME are cleaned.
say "Attempting to uninstall #{spec.full_name}"
uninstall_options = {
- :executables => false,
- :version => "= #{spec.version}",
+ executables: false,
+ version: "= #{spec.version}",
}
uninstall_options[:user_install] = Gem.user_dir == spec.base_dir
diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb
index f17aed64db..807158d9c9 100644
--- a/lib/rubygems/commands/contents_command.rb
+++ b/lib/rubygems/commands/contents_command.rb
@@ -1,39 +1,40 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../version_option"
class Gem::Commands::ContentsCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'contents', 'Display the contents of the installed gems',
- :specdirs => [], :lib_only => false, :prefix => true,
- :show_install_dir => false
+ super "contents", "Display the contents of the installed gems",
+ specdirs: [], lib_only: false, prefix: true,
+ show_install_dir: false
add_version_option
- add_option('--all',
+ add_option("--all",
"Contents for all gems") do |all, options|
options[:all] = all
end
- add_option('-s', '--spec-dir a,b,c', Array,
+ add_option("-s", "--spec-dir a,b,c", Array,
"Search for gems under specific paths") do |spec_dirs, options|
options[:specdirs] = spec_dirs
end
- add_option('-l', '--[no-]lib-only',
+ add_option("-l", "--[no-]lib-only",
"Only return files in the Gem's lib_dirs") do |lib_only, options|
options[:lib_only] = lib_only
end
- add_option('--[no-]prefix',
+ add_option("--[no-]prefix",
"Don't include installed path prefix") do |prefix, options|
options[:prefix] = prefix
end
- add_option('--[no-]show-install-dir',
- 'Show only the gem install dir') do |show, options|
+ add_option("--[no-]show-install-dir",
+ "Show only the gem install dir") do |show, options|
options[:show_install_dir] = show
end
@@ -77,7 +78,7 @@ prefix or only the files that are requireable.
gem_contents name
end
- terminate_interaction 1 unless found or names.length > 1
+ terminate_interaction 1 unless found || names.length > 1
end
end
@@ -91,9 +92,9 @@ prefix or only the files that are requireable.
def files_in_gem(spec)
gem_path = spec.full_gem_path
- extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
+ extra = "/{#{spec.require_paths.join ","}}" if options[:lib_only]
glob = "#{gem_path}#{extra}/**/*"
- prefix_re = /#{Regexp.escape(gem_path)}\//
+ prefix_re = %r{#{Regexp.escape(gem_path)}/}
Dir[glob].map do |file|
[gem_path, file.sub(prefix_re, "")]
@@ -103,13 +104,13 @@ prefix or only the files that are requireable.
def files_in_default_gem(spec)
spec.files.map do |file|
case file
- when /\A#{spec.bindir}\//
+ when %r{\A#{spec.bindir}/}
# $' is POSTMATCH
- [RbConfig::CONFIG['bindir'], $']
+ [RbConfig::CONFIG["bindir"], $']
when /\.so\z/
- [RbConfig::CONFIG['archdir'], file]
+ [RbConfig::CONFIG["archdir"], file]
else
- [RbConfig::CONFIG['rubylibdir'], file]
+ [RbConfig::CONFIG["rubylibdir"], file]
end
end
end
@@ -177,7 +178,7 @@ prefix or only the files that are requireable.
@spec_dirs.sort.each {|dir| say dir }
end
- return nil
+ nil
end
def specification_directories # :nodoc:
diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb
index e472d8fa8d..9aaefae999 100644
--- a/lib/rubygems/commands/dependency_command.rb
+++ b/lib/rubygems/commands/dependency_command.rb
@@ -1,28 +1,28 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
class Gem::Commands::DependencyCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
- super 'dependency',
- 'Show the dependencies of an installed gem',
- :version => Gem::Requirement.default, :domain => :local
+ super "dependency",
+ "Show the dependencies of an installed gem",
+ version: Gem::Requirement.default, domain: :local
add_version_option
add_platform_option
add_prerelease_option
- add_option('-R', '--[no-]reverse-dependencies',
- 'Include reverse dependencies in the output') do
- |value, options|
+ add_option("-R", "--[no-]reverse-dependencies",
+ "Include reverse dependencies in the output") do |value, options|
options[:reverse_dependencies] = value
end
- add_option('-p', '--pipe',
+ add_option("-p", "--pipe",
"Pipe Format (name --version ver)") do |value, options|
options[:pipe_format] = value
end
@@ -53,47 +53,46 @@ use with other commands.
"#{program_name} REGEXP"
end
- def fetch_remote_specs(dependency) # :nodoc:
+ def fetch_remote_specs(name, requirement, prerelease) # :nodoc:
fetcher = Gem::SpecFetcher.fetcher
- ss, = fetcher.spec_for_dependency dependency
+ specs_type = prerelease ? :complete : :released
+
+ ss = if name.nil?
+ fetcher.detect(specs_type) { true }
+ else
+ fetcher.detect(specs_type) do |name_tuple|
+ name === name_tuple.name && requirement.satisfied_by?(name_tuple.version)
+ end
+ end
- ss.map {|spec, _| spec }
+ ss.map {|tuple, source| source.fetch_spec(tuple) }
end
- def fetch_specs(name_pattern, dependency) # :nodoc:
+ def fetch_specs(name_pattern, requirement, prerelease) # :nodoc:
specs = []
if local?
specs.concat Gem::Specification.stubs.find_all {|spec|
- name_pattern =~ spec.name and
- dependency.requirement.satisfied_by? spec.version
+ name_matches = name_pattern ? name_pattern =~ spec.name : true
+ version_matches = requirement.satisfied_by?(spec.version)
+
+ name_matches && version_matches
}.map(&:to_spec)
end
- specs.concat fetch_remote_specs dependency if remote?
+ specs.concat fetch_remote_specs name_pattern, requirement, prerelease if remote?
ensure_specs specs
specs.uniq.sort
end
- def gem_dependency(pattern, version, prerelease) # :nodoc:
- dependency = Gem::Deprecate.skip_during do
- Gem::Dependency.new pattern, version
- end
-
- dependency.prerelease = prerelease
-
- dependency
- end
-
def display_pipe(specs) # :nodoc:
specs.each do |spec|
- unless spec.dependencies.empty?
- spec.dependencies.sort_by {|dep| dep.name }.each do |dep|
- say "#{dep.name} --version '#{dep.requirement}'"
- end
+ next if spec.dependencies.empty?
+ spec.dependencies.sort_by(&:name).each do |dep|
+ say "#{dep.name} --version '#{dep.requirement}'"
end
end
end
@@ -119,11 +118,9 @@ use with other commands.
ensure_local_only_reverse_dependencies
pattern = name_pattern options[:args]
+ requirement = Gem::Requirement.new options[:version]
- dependency =
- gem_dependency pattern, options[:version], options[:prerelease]
-
- specs = fetch_specs pattern, dependency
+ specs = fetch_specs pattern, requirement, options[:prerelease]
reverse = reverse_dependencies specs
@@ -135,8 +132,8 @@ use with other commands.
end
def ensure_local_only_reverse_dependencies # :nodoc:
- if options[:reverse_dependencies] and remote? and not local?
- alert_error 'Only reverse dependencies for local gems are supported.'
+ if options[:reverse_dependencies] && remote? && !local?
+ alert_error "Only reverse dependencies for local gems are supported."
terminate_interaction 1
end
end
@@ -144,7 +141,7 @@ use with other commands.
def ensure_specs(specs) # :nodoc:
return unless specs.empty?
- patterns = options[:args].join ','
+ patterns = options[:args].join ","
say "No gems found matching #{patterns} (#{options[:version]})" if
Gem.configuration.verbose
@@ -153,23 +150,15 @@ use with other commands.
def print_dependencies(spec, level = 0) # :nodoc:
response = String.new
- response << ' ' * level + "Gem #{spec.full_name}\n"
+ response << " " * level + "Gem #{spec.full_name}\n"
unless spec.dependencies.empty?
- spec.dependencies.sort_by {|dep| dep.name }.each do |dep|
- response << ' ' * level + " #{dep}\n"
+ spec.dependencies.sort_by(&:name).each do |dep|
+ response << " " * level + " #{dep}\n"
end
end
response
end
- def remote_specs(dependency) # :nodoc:
- fetcher = Gem::SpecFetcher.fetcher
-
- ss, _ = fetcher.spec_for_dependency dependency
-
- ss.map {|s,o| s }
- end
-
def reverse_dependencies(specs) # :nodoc:
reverse = Hash.new {|h, k| h[k] = [] }
@@ -192,7 +181,7 @@ use with other commands.
sp.dependencies.each do |dep|
dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep
- if spec.name == dep.name and
+ if spec.name == dep.name &&
dep.requirement.satisfied_by?(spec.version)
result << [sp.full_name, dep]
end
@@ -205,9 +194,9 @@ use with other commands.
private
def name_pattern(args)
- args << '' if args.empty?
+ return if args.empty?
- if args.length == 1 and args.first =~ /\A(.*)(i)?\z/m
+ if args.length == 1 && args.first =~ /\A(.*)(i)?\z/m
flags = $2 ? Regexp::IGNORECASE : nil
Regexp.new $1, flags
else
diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb
index 37429fb836..8ed0996069 100644
--- a/lib/rubygems/commands/environment_command.rb
+++ b/lib/rubygems/commands/environment_command.rb
@@ -1,21 +1,23 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::EnvironmentCommand < Gem::Command
def initialize
- super 'environment', 'Display information about the RubyGems environment'
+ super "environment", "Display information about the RubyGems environment"
end
def arguments # :nodoc:
args = <<-EOF
- gemdir display the path where gems are installed
- gempath display path used to search for gems
+ home display the path where gems are installed. Aliases: gemhome, gemdir, GEM_HOME
+ path display path used to search for gems. Aliases: gempath, GEM_PATH
+ user_gemhome display the path where gems are installed when `--user-install` is given. Aliases: user_gemdir
version display the gem format version
remotesources display the remote gem servers
platform display the supported gem platforms
<omitted> display everything
EOF
- return args.gsub(/^\s+/, '')
+ args.gsub(/^\s+/, "")
end
def description # :nodoc:
@@ -80,6 +82,8 @@ lib/rubygems/defaults/operating_system.rb
Gem.dir
when /^gempath/, /^path/, /^GEM_PATH/ then
Gem.path.join(File::PATH_SEPARATOR)
+ when /^user_gemdir/, /^user_gemhome/ then
+ Gem.user_dir
when /^remotesources/ then
Gem.sources.to_a.join("\n")
when /^platform/ then
@@ -104,9 +108,7 @@ lib/rubygems/defaults/operating_system.rb
out << " - RUBYGEMS VERSION: #{Gem::VERSION}\n"
- out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
- out << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
- out << ") [#{RUBY_PLATFORM}]\n"
+ out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]\n"
out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n"
@@ -138,7 +140,7 @@ lib/rubygems/defaults/operating_system.rb
out << " - GEM CONFIGURATION:\n"
Gem.configuration.each do |name, value|
- value = value.gsub(/./, '*') if name == 'gemcutter_key'
+ value = value.gsub(/./, "*") if name == "gemcutter_key"
out << " - #{name.inspect} => #{value.inspect}\n"
end
@@ -149,7 +151,7 @@ lib/rubygems/defaults/operating_system.rb
out << " - SHELL PATH:\n"
- shell_path = ENV['PATH'].split(File::PATH_SEPARATOR)
+ shell_path = ENV["PATH"].split(File::PATH_SEPARATOR)
add_path out, shell_path
out
@@ -169,6 +171,6 @@ lib/rubygems/defaults/operating_system.rb
end
end
- return nil
+ nil
end
end
diff --git a/lib/rubygems/commands/exec_command.rb b/lib/rubygems/commands/exec_command.rb
new file mode 100644
index 0000000000..d588804290
--- /dev/null
+++ b/lib/rubygems/commands/exec_command.rb
@@ -0,0 +1,249 @@
+# frozen_string_literal: true
+
+require_relative "../command"
+require_relative "../dependency_installer"
+require_relative "../gem_runner"
+require_relative "../package"
+require_relative "../version_option"
+
+class Gem::Commands::ExecCommand < Gem::Command
+ include Gem::VersionOption
+
+ def initialize
+ super "exec", "Run a command from a gem", {
+ version: Gem::Requirement.default,
+ }
+
+ add_version_option
+ add_prerelease_option "to be installed"
+
+ add_option "-g", "--gem GEM", "run the executable from the given gem" do |value, options|
+ options[:gem_name] = value
+ end
+
+ add_option(:"Install/Update", "--conservative",
+ "Prefer the most recent installed version, ",
+ "rather than the latest version overall") do |_value, options|
+ options[:conservative] = true
+ end
+ end
+
+ def arguments # :nodoc:
+ "COMMAND the executable command to run"
+ end
+
+ def defaults_str # :nodoc:
+ "--version '#{Gem::Requirement.default}'"
+ end
+
+ def description # :nodoc:
+ <<-EOF
+The exec command handles installing (if necessary) and running an executable
+from a gem, regardless of whether that gem is currently installed.
+
+The exec command can be thought of as a shortcut to running `gem install` and
+then the executable from the installed gem.
+
+For example, `gem exec rails new .` will run `rails new .` in the current
+directory, without having to manually run `gem install rails`.
+Additionally, the exec command ensures the most recent version of the gem
+is used (unless run with `--conservative`), and that the gem is not installed
+to the same gem path as user-installed gems.
+ EOF
+ end
+
+ def usage # :nodoc:
+ "#{program_name} [options --] COMMAND [args]"
+ end
+
+ def execute
+ gem_paths = { "GEM_HOME" => Gem.paths.home, "GEM_PATH" => Gem.paths.path.join(File::PATH_SEPARATOR), "GEM_SPEC_CACHE" => Gem.paths.spec_cache_dir }.compact
+
+ check_executable
+
+ print_command
+ if options[:gem_name] == "gem" && options[:executable] == "gem"
+ set_gem_exec_install_paths
+ Gem::GemRunner.new.run options[:args]
+ return
+ elsif options[:conservative]
+ install_if_needed
+ else
+ install
+ activate!
+ end
+
+ load!
+ ensure
+ ENV.update(gem_paths) if gem_paths
+ Gem.clear_paths
+ end
+
+ private
+
+ def handle_options(args)
+ args = add_extra_args(args)
+ check_deprecated_options(args)
+ @options = Marshal.load Marshal.dump @defaults # deep copy
+ parser.order!(args) do |v|
+ # put the non-option back at the front of the list of arguments
+ args.unshift(v)
+
+ # stop parsing once we hit the first non-option,
+ # so you can call `gem exec rails --version` and it prints the rails
+ # version rather than rubygem's
+ break
+ end
+ @options[:args] = args
+
+ options[:executable], gem_version = extract_gem_name_and_version(options[:args].shift)
+ options[:gem_name] ||= options[:executable]
+
+ if gem_version
+ if options[:version].none?
+ options[:version] = Gem::Requirement.new(gem_version)
+ else
+ options[:version].concat [gem_version]
+ end
+ end
+
+ if options[:prerelease] && !options[:version].prerelease?
+ if options[:version].none?
+ options[:version] = Gem::Requirement.default_prerelease
+ else
+ options[:version].concat [Gem::Requirement.default_prerelease]
+ end
+ end
+ end
+
+ def check_executable
+ if options[:executable].nil?
+ raise Gem::CommandLineError,
+ "Please specify an executable to run (e.g. #{program_name} COMMAND)"
+ end
+ end
+
+ def print_command
+ verbose "running #{program_name} with:\n"
+ opts = options.reject {|_, v| v.nil? || Array(v).empty? }
+ max_length = opts.map {|k, _| k.size }.max
+ opts.each do |k, v|
+ next if v.nil?
+ verbose "\t#{k.to_s.rjust(max_length)}: #{v}"
+ end
+ verbose ""
+ end
+
+ def install_if_needed
+ activate!
+ rescue Gem::MissingSpecError
+ verbose "#{Gem::Dependency.new(options[:gem_name], options[:version])} not available locally, installing from remote"
+ install
+ activate!
+ end
+
+ def set_gem_exec_install_paths
+ home = File.join(Gem.dir, "gem_exec")
+
+ ENV["GEM_PATH"] = ([home] + Gem.path).join(File::PATH_SEPARATOR)
+ ENV["GEM_HOME"] = home
+ Gem.clear_paths
+ end
+
+ def install
+ set_gem_exec_install_paths
+
+ gem_name = options[:gem_name]
+ gem_version = options[:version]
+
+ install_options = options.merge(
+ minimal_deps: false,
+ wrappers: true
+ )
+
+ suppress_always_install do
+ dep_installer = Gem::DependencyInstaller.new install_options
+
+ request_set = dep_installer.resolve_dependencies gem_name, gem_version
+
+ verbose "Gems to install:"
+ request_set.sorted_requests.each do |activation_request|
+ verbose "\t#{activation_request.full_name}"
+ end
+
+ request_set.install install_options
+ end
+
+ Gem::Specification.reset
+ rescue Gem::InstallError => e
+ alert_error "Error installing #{gem_name}:\n\t#{e.message}"
+ terminate_interaction 1
+ rescue Gem::GemNotFoundException => e
+ show_lookup_failure e.name, e.version, e.errors, false
+
+ terminate_interaction 2
+ rescue Gem::UnsatisfiableDependencyError => e
+ show_lookup_failure e.name, e.version, e.errors, false,
+ "'#{gem_name}' (#{gem_version})"
+
+ terminate_interaction 2
+ end
+
+ def activate!
+ gem(options[:gem_name], options[:version])
+ Gem.finish_resolve
+
+ verbose "activated #{options[:gem_name]} (#{Gem.loaded_specs[options[:gem_name]].version})"
+ end
+
+ def load!
+ argv = ARGV.clone
+ ARGV.replace options[:args]
+
+ exe = executable = options[:executable]
+
+ contains_executable = Gem.loaded_specs.values.select do |spec|
+ spec.executables.include?(executable)
+ end
+
+ if contains_executable.any? {|s| s.name == executable }
+ contains_executable.select! {|s| s.name == executable }
+ end
+
+ if contains_executable.empty?
+ if (spec = Gem.loaded_specs[executable]) && (exe = spec.executable)
+ contains_executable << spec
+ else
+ alert_error "Failed to load executable `#{executable}`," \
+ " are you sure the gem `#{options[:gem_name]}` contains it?"
+ terminate_interaction 1
+ end
+ end
+
+ if contains_executable.size > 1
+ alert_error "Ambiguous which gem `#{executable}` should come from: " \
+ "the options are #{contains_executable.map(&:name)}, " \
+ "specify one via `-g`"
+ terminate_interaction 1
+ end
+
+ load Gem.activate_bin_path(contains_executable.first.name, exe, ">= 0.a")
+ ensure
+ ARGV.replace argv
+ end
+
+ def suppress_always_install
+ name = :always_install
+ cls = ::Gem::Resolver::InstallerSet
+ method = cls.instance_method(name)
+ cls.remove_method(name)
+ cls.define_method(name) { [] }
+
+ begin
+ yield
+ ensure
+ cls.remove_method(name)
+ cls.define_method(name, method)
+ end
+ end
+end
diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb
index 6a1b346dd3..f7f5b62306 100644
--- a/lib/rubygems/commands/fetch_command.rb
+++ b/lib/rubygems/commands/fetch_command.rb
@@ -1,14 +1,20 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
class Gem::Commands::FetchCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
- super 'fetch', 'Download a gem and place it in the current directory'
+ defaults = {
+ suggest_alternate: true,
+ version: Gem::Requirement.default,
+ }
+
+ super "fetch", "Download a gem and place it in the current directory", defaults
add_bulk_threshold_option
add_proxy_option
@@ -18,10 +24,14 @@ class Gem::Commands::FetchCommand < Gem::Command
add_version_option
add_platform_option
add_prerelease_option
+
+ add_option "--[no-]suggestions", "Suggest alternates when gems are not found" do |value, options|
+ options[:suggest_alternate] = value
+ end
end
def arguments # :nodoc:
- 'GEMNAME name of gem to download'
+ "GEMNAME name of gem to download"
end
def defaults_str # :nodoc:
@@ -42,15 +52,27 @@ then repackaging it.
"#{program_name} GEMNAME [GEMNAME ...]"
end
+ def check_version # :nodoc:
+ if options[:version] != Gem::Requirement.default &&
+ get_all_gem_names.size > 1
+ alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \
+ " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ terminate_interaction 1
+ end
+ end
+
def execute
- version = options[:version] || Gem::Requirement.default
+ check_version
+ version = options[:version]
platform = Gem.platforms.last
- gem_names = get_all_gem_names
+ gem_names = get_all_gem_names_and_versions
- gem_names.each do |gem_name|
- dep = Gem::Dependency.new gem_name, version
+ gem_names.each do |gem_name, gem_version|
+ gem_version ||= version
+ dep = Gem::Dependency.new gem_name, gem_version
dep.prerelease = options[:prerelease]
+ suppress_suggestions = !options[:suggest_alternate]
specs_and_sources, errors =
Gem::SpecFetcher.fetcher.spec_for_dependency dep
@@ -60,15 +82,13 @@ then repackaging it.
specs_and_sources = filtered unless filtered.empty?
end
- spec, source = specs_and_sources.max_by {|s,| s.version }
+ spec, source = specs_and_sources.max_by {|s,| s }
if spec.nil?
- show_lookup_failure gem_name, version, errors, options[:domain]
+ show_lookup_failure gem_name, gem_version, errors, suppress_suggestions, options[:domain]
next
end
-
source.download spec
-
say "Downloaded #{spec.full_name}"
end
end
diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb
index 93e25ef5e4..13be92593b 100644
--- a/lib/rubygems/commands/generate_index_command.rb
+++ b/lib/rubygems/commands/generate_index_command.rb
@@ -1,85 +1,51 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/indexer'
-##
-# Generates a index files for use as a gem server.
-#
-# See `gem help generate_index`
+require_relative "../command"
-class Gem::Commands::GenerateIndexCommand < Gem::Command
- def initialize
- super 'generate_index',
- 'Generates the index files for a gem server directory',
- :directory => '.', :build_modern => true
+unless defined? Gem::Commands::GenerateIndexCommand
+ class Gem::Commands::GenerateIndexCommand < Gem::Command
+ module RubygemsTrampoline
+ def description # :nodoc:
+ <<~EOF
+ The generate_index command has been moved to the rubygems-generate_index gem.
+ EOF
+ end
- add_option '-d', '--directory=DIRNAME',
- 'repository base dir containing gems subdir' do |dir, options|
- options[:directory] = File.expand_path dir
- end
+ def execute
+ alert_error "Install the rubygems-generate_index gem for the generate_index command"
+ end
- add_option '--[no-]modern',
- 'Generate indexes for RubyGems',
- '(always true)' do |value, options|
- options[:build_modern] = value
+ def invoke_with_build_args(args, build_args)
+ name = "rubygems-generate_index"
+ spec = begin
+ Gem::Specification.find_by_name(name)
+ rescue Gem::LoadError
+ require "rubygems/dependency_installer"
+ Gem.install(name, Gem::Requirement.default, Gem::DependencyInstaller::DEFAULT_OPTIONS).find {|s| s.name == name }
+ end
+
+ # remove the methods defined in this file so that the methods defined in the gem are used instead,
+ # and without a method redefinition warning
+ %w[description execute invoke_with_build_args].each do |method|
+ RubygemsTrampoline.remove_method(method)
+ end
+ self.class.singleton_class.remove_method(:new)
+
+ spec.activate
+ Gem.load_plugin_files spec.matches_for_glob("rubygems_plugin#{Gem.suffix_pattern}")
+
+ self.class.new.invoke_with_build_args(args, build_args)
+ end
end
+ private_constant :RubygemsTrampoline
- 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|
- options[:update] = value
+ # remove_method(:initialize) warns, but removing new does not warn
+ def self.new
+ command = allocate
+ command.send(:initialize, "generate_index", "Generates the index files for a gem server directory (requires rubygems-generate_index)")
+ command
end
- end
-
- def defaults_str # :nodoc:
- "--directory . --modern"
- end
-
- def description # :nodoc:
- <<-EOF
-The generate_index command creates a set of indexes for serving gems
-statically. The command expects a 'gems' directory under the path given to
-the --directory option. The given directory will be the directory you serve
-as the gem repository.
-
-For `gem generate_index --directory /path/to/repo`, expose /path/to/repo via
-your HTTP server configuration (not /path/to/repo/gems).
-When done, it will generate a set of files like this:
-
- gems/*.gem # .gem files you want to
- # index
-
- specs.<version>.gz # specs index
- latest_specs.<version>.gz # latest specs index
- prerelease_specs.<version>.gz # prerelease specs index
- quick/Marshal.<version>/<gemname>.gemspec.rz # Marshal quick index file
-
-The .rz extension files are compressed with the inflate algorithm.
-The Marshal version number comes from ruby's Marshal::MAJOR_VERSION and
-Marshal::MINOR_VERSION constants. It is used to ensure compatibility.
- EOF
- end
-
- def execute
- # This is always true because it's the only way now.
- options[:build_modern] = true
-
- if not File.exist?(options[:directory]) or
- not File.directory?(options[:directory])
- alert_error "unknown directory name #{options[:directory]}."
- terminate_interaction 1
- else
- indexer = Gem::Indexer.new options.delete(:directory), options
-
- if options[:update]
- indexer.update_index
- else
- indexer.generate_index
- end
- end
+ prepend(RubygemsTrampoline)
end
end
diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb
index 4e8d7600fb..1619b152e7 100644
--- a/lib/rubygems/commands/help_command.rb
+++ b/lib/rubygems/commands/help_command.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::HelpCommand < Gem::Command
# :stopdoc:
- EXAMPLES = <<-EOF.freeze
+ EXAMPLES = <<-EOF
Some examples of 'gem' usage.
* Install 'rake', either from local directory or remote server:
@@ -52,13 +53,13 @@ Some examples of 'gem' usage.
gem update --system
EOF
- GEM_DEPENDENCIES = <<-EOF.freeze
+ GEM_DEPENDENCIES = <<-EOF
A gem dependencies file allows installation of a consistent set of gems across
multiple environments. The RubyGems implementation is designed to be
compatible with Bundler's Gemfile format. You can see additional
documentation on the format at:
- http://bundler.io
+ https://bundler.io
RubyGems automatically looks for these gem dependencies files:
@@ -171,7 +172,7 @@ and #platforms methods:
See the bundler Gemfile manual page for a list of platforms supported in a gem
dependencies file.:
- http://bundler.io/v1.6/man/gemfile.5.html
+ https://bundler.io/v2.5/man/gemfile.5.html
Ruby Version and Engine Dependency
==================================
@@ -229,7 +230,7 @@ default. This may be overridden with the :development_group option:
EOF
- PLATFORMS = <<-'EOF'.freeze
+ PLATFORMS = <<-'EOF'
RubyGems platforms are composed of three parts, a CPU, an OS, and a
version. These values are taken from values in rbconfig.rb. You can view
your current platform by running `gem environment`.
@@ -268,7 +269,7 @@ Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's
platform.
EOF
- # NOTE when updating also update Gem::Command::HELP
+ # NOTE: when updating also update Gem::Command::HELP
SUBCOMMANDS = [
["commands", :show_commands],
@@ -280,7 +281,7 @@ platform.
# :startdoc:
def initialize
- super 'help', "Provide help on the 'gem' command"
+ super "help", "Provide help on the 'gem' command"
@command_manager = Gem::CommandManager.instance
end
@@ -323,16 +324,16 @@ platform.
margin_width = 4
- desc_width = @command_manager.command_names.map {|n| n.size }.max + 4
+ desc_width = @command_manager.command_names.map(&:size).max + 4
summary_width = 80 - margin_width - desc_width
- wrap_indent = ' ' * (margin_width + desc_width)
- format = "#{' ' * margin_width}%-#{desc_width}s%s"
+ wrap_indent = " " * (margin_width + desc_width)
+ format = "#{" " * margin_width}%-#{desc_width}s%s"
@command_manager.command_names.each do |cmd_name|
command = @command_manager[cmd_name]
- next if command.deprecated?
+ next if command&.deprecated?
summary =
if command
@@ -342,7 +343,7 @@ platform.
end
summary = wrap(summary, summary_width).split "\n"
- out << sprintf(format, cmd_name, summary.shift)
+ out << format(format, cmd_name, summary.shift)
until summary.empty? do
out << "#{wrap_indent}#{summary.shift}"
end
@@ -366,7 +367,7 @@ platform.
command = @command_manager[possibilities.first]
command.invoke("--help")
elsif possibilities.size > 1
- alert_warning "Ambiguous command #{command_name} (#{possibilities.join(', ')})"
+ alert_warning "Ambiguous command #{command_name} (#{possibilities.join(", ")})"
else
alert_warning "Unknown command #{command_name}. Try: gem help commands"
end
diff --git a/lib/rubygems/commands/info_command.rb b/lib/rubygems/commands/info_command.rb
index 9ca6ae364f..f65c639662 100644
--- a/lib/rubygems/commands/info_command.rb
+++ b/lib/rubygems/commands/info_command.rb
@@ -1,19 +1,19 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/query_utils'
+require_relative "../command"
+require_relative "../query_utils"
class Gem::Commands::InfoCommand < Gem::Command
include Gem::QueryUtils
def initialize
super "info", "Show information for the given gem",
- :name => //, :domain => :local, :details => false, :versions => true,
- :installed => nil, :version => Gem::Requirement.default
+ name: //, domain: :local, details: false, versions: true,
+ installed: nil, version: Gem::Requirement.default
add_query_options
- remove_option('-d')
+ remove_option("-d")
defaults[:details] = true
defaults[:exact] = true
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 4d36c69d51..2091634a29 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/install_update_options'
-require 'rubygems/dependency_installer'
-require 'rubygems/local_remote_options'
-require 'rubygems/validator'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../install_update_options"
+require_relative "../dependency_installer"
+require_relative "../local_remote_options"
+require_relative "../validator"
+require_relative "../version_option"
+require_relative "../update_suggestion"
##
# Gem installer command line tool
@@ -17,19 +19,20 @@ class Gem::Commands::InstallCommand < Gem::Command
include Gem::VersionOption
include Gem::LocalRemoteOptions
include Gem::InstallUpdateOptions
+ include Gem::UpdateSuggestion
def initialize
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
- :format_executable => false,
- :lock => true,
- :suggest_alternate => true,
- :version => Gem::Requirement.default,
- :without_groups => [],
+ format_executable: false,
+ lock: true,
+ suggest_alternate: true,
+ version: Gem::Requirement.default,
+ without_groups: [],
})
defaults.merge!(install_update_options)
- super 'install', 'Install a gem into the local repository', defaults
+ super "install", "Install a gem into the local repository", defaults
add_install_update_options
add_local_remote_options
@@ -45,9 +48,9 @@ class Gem::Commands::InstallCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--both --version '#{Gem::Requirement.default}' --no-force\n" +
- "--install-dir #{Gem.dir} --lock\n" +
- install_update_defaults_str
+ "--both --version '#{Gem::Requirement.default}' --no-force\n" \
+ "--install-dir #{Gem.dir} --lock\n" +
+ install_update_defaults_str
end
def description # :nodoc:
@@ -133,16 +136,9 @@ You can use `i` command instead of `install`.
"#{program_name} [options] GEMNAME [GEMNAME ...] -- --build-flags"
end
- def check_install_dir # :nodoc:
- if options[:install_dir] and options[:user_install]
- alert_error "Use --install-dir or --user-install but not both"
- terminate_interaction 1
- end
- end
-
def check_version # :nodoc:
- if options[:version] != Gem::Requirement.default and
- get_all_gem_names.size > 1
+ if options[:version] != Gem::Requirement.default &&
+ get_all_gem_names.size > 1
alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \
" version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
terminate_interaction 1
@@ -157,9 +153,8 @@ You can use `i` command instead of `install`.
@installed_specs = []
- ENV.delete 'GEM_PATH' if options[:install_dir].nil?
+ ENV.delete "GEM_PATH" if options[:install_dir].nil?
- check_install_dir
check_version
load_hooks
@@ -168,11 +163,13 @@ You can use `i` command instead of `install`.
show_installed
+ say update_suggestion if eligible_for_update?
+
terminate_interaction exit_code
end
def install_from_gemdeps # :nodoc:
- require 'rubygems/request_set'
+ require_relative "../request_set"
rs = Gem::RequestSet.new
specs = rs.install_from_gemdeps options do |req, inst|
@@ -191,8 +188,8 @@ You can use `i` command instead of `install`.
end
def install_gem(name, version) # :nodoc:
- return if options[:conservative] and
- not Gem::Dependency.new(name, version).matching_specs.empty?
+ return if options[:conservative] &&
+ !Gem::Dependency.new(name, version).matching_specs.empty?
req = Gem::Requirement.create(version)
@@ -247,20 +244,21 @@ You can use `i` command instead of `install`.
def load_hooks # :nodoc:
if options[:install_as_default]
- require 'rubygems/install_default_message'
+ require_relative "../install_default_message"
else
- require 'rubygems/install_message'
+ require_relative "../install_message"
end
- require 'rubygems/rdoc'
+ require_relative "../rdoc"
end
def show_install_errors(errors) # :nodoc:
return unless errors
errors.each do |x|
- return unless Gem::SourceFetchProblem === x
+ next unless Gem::SourceFetchProblem === x
- msg = "Unable to pull data from '#{x.source.uri}': #{x.error.message}"
+ require_relative "../uri"
+ msg = "Unable to pull data from '#{Gem::Uri.redact(x.source.uri)}': #{x.error.message}"
alert_warning msg
end
@@ -269,7 +267,7 @@ You can use `i` command instead of `install`.
def show_installed # :nodoc:
return if @installed_specs.empty?
- gems = @installed_specs.length == 1 ? 'gem' : 'gems'
+ gems = @installed_specs.length == 1 ? "gem" : "gems"
say "#{@installed_specs.length} #{gems} installed"
end
end
diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb
index 5c99d3d73d..fab4b73814 100644
--- a/lib/rubygems/commands/list_command.rb
+++ b/lib/rubygems/commands/list_command.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/query_utils'
+
+require_relative "../command"
+require_relative "../query_utils"
##
# Searches for gems starting with the supplied argument.
@@ -9,9 +10,9 @@ class Gem::Commands::ListCommand < Gem::Command
include Gem::QueryUtils
def initialize
- super 'list', 'Display local gems whose name matches REGEXP',
- :name => //, :domain => :local, :details => false, :versions => true,
- :installed => nil, :version => Gem::Requirement.default
+ super "list", "Display local gems whose name matches REGEXP",
+ domain: :local, details: false, versions: true,
+ installed: nil, version: Gem::Requirement.default
add_query_options
end
diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb
index f1dc1ac586..f7fd5ada16 100644
--- a/lib/rubygems/commands/lock_command.rb
+++ b/lib/rubygems/commands/lock_command.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::LockCommand < Gem::Command
def initialize
- super 'lock', 'Generate a lockdown list of gems',
- :strict => false
+ super "lock", "Generate a lockdown list of gems",
+ strict: false
- add_option '-s', '--[no-]strict',
- 'fail if unable to satisfy a dependency' do |strict, options|
+ add_option "-s", "--[no-]strict",
+ "fail if unable to satisfy a dependency" do |strict, options|
options[:strict] = strict
end
end
diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb
index 86671a93b2..b91a8db12d 100644
--- a/lib/rubygems/commands/mirror_command.rb
+++ b/lib/rubygems/commands/mirror_command.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
unless defined? Gem::Commands::MirrorCommand
class Gem::Commands::MirrorCommand < Gem::Command
def initialize
- super('mirror', 'Mirror all gem files (requires rubygems-mirror)')
+ super("mirror", "Mirror all gem files (requires rubygems-mirror)")
begin
- Gem::Specification.find_by_name('rubygems-mirror').activate
+ Gem::Specification.find_by_name("rubygems-mirror").activate
rescue Gem::LoadError
# no-op
end
diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb
index 8012a9a0e1..0fe90dc8b8 100644
--- a/lib/rubygems/commands/open_command.rb
+++ b/lib/rubygems/commands/open_command.rb
@@ -1,18 +1,19 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../version_option"
class Gem::Commands::OpenCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'open', 'Open gem sources in editor'
+ super "open", "Open gem sources in editor"
- add_option('-e', '--editor COMMAND', String,
+ add_option("-e", "--editor COMMAND", String,
"Prepends COMMAND to gem path. Could be used to specify editor.") do |command, options|
options[:editor] = command || get_env_editor
end
- add_option('-v', '--version VERSION', String,
+ add_option("-v", "--version VERSION", String,
"Opens specific gem version") do |version|
options[:version] = version
end
@@ -40,10 +41,10 @@ class Gem::Commands::OpenCommand < Gem::Command
end
def get_env_editor
- ENV['GEM_EDITOR'] ||
- ENV['VISUAL'] ||
- ENV['EDITOR'] ||
- 'vi'
+ ENV["GEM_EDITOR"] ||
+ ENV["VISUAL"] ||
+ ENV["EDITOR"] ||
+ "vi"
end
def execute
@@ -69,9 +70,7 @@ class Gem::Commands::OpenCommand < Gem::Command
end
def open_editor(path)
- Dir.chdir(path) do
- system(*@editor.split(/\s+/) + [path])
- end
+ system(*@editor.split(/\s+/) + [path], { chdir: path })
end
def spec_for(name)
diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb
index 3579bfc3ba..08a9221a26 100644
--- a/lib/rubygems/commands/outdated_command.rb
+++ b/lib/rubygems/commands/outdated_command.rb
@@ -1,15 +1,16 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/spec_fetcher'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../spec_fetcher"
+require_relative "../version_option"
class Gem::Commands::OutdatedCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
- super 'outdated', 'Display all gems that need updates'
+ super "outdated", "Display all gems that need updates"
add_local_remote_options
add_platform_option
diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb
index dd49027469..12bfe3a834 100644
--- a/lib/rubygems/commands/owner_command.rb
+++ b/lib/rubygems/commands/owner_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/gemcutter_utilities'
-require 'rubygems/text'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../gemcutter_utilities"
+require_relative "../text"
class Gem::Commands::OwnerCommand < Gem::Command
include Gem::Text
@@ -12,7 +13,12 @@ class Gem::Commands::OwnerCommand < Gem::Command
def description # :nodoc:
<<-EOF
The owner command lets you add and remove owners of a gem on a push
-server (the default is https://rubygems.org).
+server (the default is https://rubygems.org). Multiple owners can be
+added or removed at the same time, if the flag is given multiple times.
+
+The supported user identifiers are dependent on the push server.
+For rubygems.org, both e-mail and handle are supported, even though the
+user identifier field is called "email".
The owner of a gem has the permission to push new versions, yank existing
versions or edit the HTML page of the gem. Be careful of who you give push
@@ -29,23 +35,23 @@ permission to.
end
def initialize
- super 'owner', 'Manage gem owners of a gem on the push server'
+ super "owner", "Manage gem owners of a gem on the push server"
add_proxy_option
add_key_option
add_otp_option
- defaults.merge! :add => [], :remove => []
+ defaults.merge! add: [], remove: []
- add_option '-a', '--add EMAIL', 'Add an owner' do |value, options|
+ add_option "-a", "--add NEW_OWNER", "Add an owner by user identifier" do |value, options|
options[:add] << value
end
- add_option '-r', '--remove EMAIL', 'Remove an owner' do |value, options|
+ add_option "-r", "--remove OLD_OWNER", "Remove an owner by user identifier" do |value, options|
options[:remove] << value
end
- add_option '-h', '--host HOST',
- 'Use another gemcutter-compatible host',
- ' (e.g. https://rubygems.org)' do |value, options|
+ add_option "-h", "--host HOST",
+ "Use another gemcutter-compatible host",
+ " (e.g. https://rubygems.org)" do |value, options|
options[:host] = value
end
end
@@ -73,7 +79,7 @@ permission to.
say "Owners for gem: #{name}"
owners.each do |owner|
- say "- #{owner['email'] || owner['handle'] || owner['id']}"
+ say "- #{owner["email"] || owner["handle"] || owner["id"]}"
end
end
end
@@ -88,14 +94,14 @@ permission to.
def manage_owners(method, name, owners)
owners.each do |owner|
- begin
- response = send_owner_request(method, name, owner)
- action = method == :delete ? "Removing" : "Adding"
-
- with_response response, "#{action} #{owner}"
- rescue
- # ignore
- end
+ response = send_owner_request(method, name, owner)
+ action = method == :delete ? "Removing" : "Adding"
+
+ with_response response, "#{action} #{owner}"
+ rescue Gem::WebauthnVerificationError => e
+ raise e
+ rescue StandardError
+ # ignore early exits to allow for completing the iteration of all owners
end
end
@@ -103,7 +109,7 @@ permission to.
def send_owner_request(method, name, owner)
rubygems_api_request method, "api/v1/gems/#{name}/owners", scope: get_owner_scope(method: method) do |request|
- request.set_form_data 'email' => owner
+ request.set_form_data "email" => owner
request.add_field "Authorization", api_key
end
end
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index 143105981e..456d897df2 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -1,62 +1,73 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/package'
-require 'rubygems/installer'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../package"
+require_relative "../installer"
+require_relative "../version_option"
class Gem::Commands::PristineCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'pristine',
- 'Restores installed gems to pristine condition from files located in the gem cache',
- :version => Gem::Requirement.default,
- :extensions => true,
- :extensions_set => false,
- :all => false
-
- add_option('--all',
- 'Restore all installed gems to pristine',
- 'condition') do |value, options|
+ super "pristine",
+ "Restores installed gems to pristine condition from files located in the gem cache",
+ version: Gem::Requirement.default,
+ extensions: true,
+ extensions_set: false,
+ all: false
+
+ add_option("--all",
+ "Restore all installed gems to pristine",
+ "condition") do |value, options|
options[:all] = value
end
- add_option('--skip=gem_name',
- 'used on --all, skip if name == gem_name') do |value, options|
+ add_option("--skip=gem_name",
+ "used on --all, skip if name == gem_name") do |value, options|
options[:skip] ||= []
options[:skip] << value
end
- add_option('--[no-]extensions',
- 'Restore gems with extensions',
- 'in addition to regular gems') do |value, options|
+ add_option("--[no-]extensions",
+ "Restore gems with extensions",
+ "in addition to regular gems") do |value, options|
options[:extensions_set] = true
options[:extensions] = value
end
- add_option('--only-executables',
- 'Only restore executables') do |value, options|
+ add_option("--only-missing-extensions",
+ "Only restore gems with missing extensions") do |value, options|
+ options[:only_missing_extensions] = value
+ end
+
+ add_option("--only-executables",
+ "Only restore executables") do |value, options|
options[:only_executables] = value
end
- add_option('--only-plugins',
- 'Only restore plugins') do |value, options|
+ add_option("--only-plugins",
+ "Only restore plugins") do |value, options|
options[:only_plugins] = value
end
- add_option('-E', '--[no-]env-shebang',
- 'Rewrite executables with a shebang',
- 'of /usr/bin/env') do |value, options|
+ add_option("-E", "--[no-]env-shebang",
+ "Rewrite executables with a shebang",
+ "of /usr/bin/env") do |value, options|
options[:env_shebang] = value
end
- add_option('-n', '--bindir DIR',
- 'Directory where executables are',
- 'located') do |value, options|
+ add_option("-i", "--install-dir DIR",
+ "Gem repository to get binstubs and plugins installed") do |value, options|
+ options[:install_dir] = File.expand_path(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')
+ add_version_option("restore to", "pristine condition")
end
def arguments # :nodoc:
@@ -64,7 +75,7 @@ class Gem::Commands::PristineCommand < Gem::Command
end
def defaults_str # :nodoc:
- '--extensions'
+ "--extensions"
end
def description # :nodoc:
@@ -93,22 +104,24 @@ extensions will be restored.
def execute
specs = if options[:all]
- Gem::Specification.map
-
- # `--extensions` must be explicitly given to pristine only gems
- # with extensions.
- elsif options[:extensions_set] and
- options[:extensions] and options[:args].empty?
- Gem::Specification.select do |spec|
- spec.extensions and not spec.extensions.empty?
- end
- else
- get_all_gem_names.sort.map do |gem_name|
- Gem::Specification.find_all_by_name(gem_name, options[:version]).reverse
- end.flatten
- end
-
- specs = specs.select{|spec| RUBY_ENGINE == spec.platform || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY }
+ Gem::Specification.map
+
+ # `--extensions` must be explicitly given to pristine only gems
+ # with extensions.
+ elsif options[:extensions_set] &&
+ options[:extensions] && options[:args].empty?
+ Gem::Specification.select do |spec|
+ spec.extensions && !spec.extensions.empty?
+ end
+ elsif options[:only_missing_extensions]
+ Gem::Specification.select(&:missing_extensions?)
+ else
+ get_all_gem_names.sort.map do |gem_name|
+ Gem::Specification.find_all_by_name(gem_name, options[:version]).reverse
+ end.flatten
+ end
+
+ specs = specs.select {|spec| spec.platform == RUBY_ENGINE || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY }
if specs.to_a.empty?
raise Gem::Exception,
@@ -123,22 +136,22 @@ extensions will be restored.
next
end
- if options.has_key? :skip
+ if options.key? :skip
if options[:skip].include? spec.name
say "Skipped #{spec.full_name}, it was given through options"
next
end
end
- unless spec.extensions.empty? or options[:extensions] or options[:only_executables] or options[:only_plugins]
+ unless spec.extensions.empty? || options[:extensions] || options[:only_executables] || options[:only_plugins]
say "Skipped #{spec.full_name}, it needs to compile an extension"
next
end
gem = spec.cache_file
- unless File.exist? gem or options[:only_executables] or options[:only_plugins]
- require 'rubygems/remote_fetcher'
+ unless File.exist?(gem) || options[:only_executables] || options[:only_plugins]
+ require_relative "../remote_fetcher"
say "Cached gem for #{spec.full_name} not found, attempting to fetch..."
@@ -158,26 +171,27 @@ extensions will be restored.
if options.include? :env_shebang
options[:env_shebang]
else
- install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install']
- install_defaults.to_s['--env-shebang']
+ install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS["install"]
+ install_defaults.to_s["--env-shebang"]
end
bin_dir = options[:bin_dir] if options[:bin_dir]
+ install_dir = options[:install_dir] if options[:install_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,
+ wrappers: true,
+ force: true,
+ install_dir: install_dir || spec.base_dir,
+ env_shebang: env_shebang,
+ build_args: spec.build_args,
+ bin_dir: bin_dir,
}
if options[:only_executables]
installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_bin
elsif options[:only_plugins]
- installer = Gem::Installer.for_spec(spec)
+ installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_plugins
else
installer = Gem::Installer.at(gem, installer_options)
diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb
index 1a9a1932f8..591ddc3a80 100644
--- a/lib/rubygems/commands/push_command.rb
+++ b/lib/rubygems/commands/push_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/gemcutter_utilities'
-require 'rubygems/package'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../gemcutter_utilities"
+require_relative "../package"
class Gem::Commands::PushCommand < Gem::Command
include Gem::LocalRemoteOptions
@@ -29,7 +30,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo
end
def initialize
- super 'push', 'Push a gem up to the gem server', :host => self.host
+ super "push", "Push a gem up to the gem server", host: host
@user_defined_host = false
@@ -37,9 +38,9 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo
add_key_option
add_otp_option
- add_option('--host HOST',
- 'Push to another gemcutter-compatible host',
- ' (e.g. https://rubygems.org)') do |value, options|
+ add_option("--host HOST",
+ "Push to another gemcutter-compatible host",
+ " (e.g. https://rubygems.org)") do |value, options|
options[:host] = value
@user_defined_host = true
end
@@ -52,14 +53,14 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo
default_gem_server, push_host = get_hosts_for(gem_name)
@host = if @user_defined_host
- options[:host]
- elsif default_gem_server
- default_gem_server
- elsif push_host
- push_host
- else
- options[:host]
- end
+ options[:host]
+ elsif default_gem_server
+ default_gem_server
+ elsif push_host
+ push_host
+ else
+ options[:host]
+ end
sign_in @host, scope: get_push_scope
@@ -74,7 +75,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo
@host ||= push_host
# Always include @host, even if it's nil
- args += [ @host, push_host ]
+ args += [@host, push_host]
say "Pushing gem to #{@host || Gem.host}..."
diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
index 789afd6509..3b527974a3 100644
--- a/lib/rubygems/commands/query_command.rb
+++ b/lib/rubygems/commands/query_command.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/query_utils'
-require 'rubygems/deprecate'
+
+require_relative "../command"
+require_relative "../query_utils"
+require_relative "../deprecate"
class Gem::Commands::QueryCommand < Gem::Command
extend Gem::Deprecate
@@ -9,7 +10,7 @@ class Gem::Commands::QueryCommand < Gem::Command
include Gem::QueryUtils
- alias warning_without_suggested_alternatives deprecation_warning
+ alias_method :warning_without_suggested_alternatives, :deprecation_warning
def deprecation_warning
warning_without_suggested_alternatives
@@ -17,15 +18,14 @@ class Gem::Commands::QueryCommand < Gem::Command
alert_warning message unless Gem::Deprecate.skip
end
- def initialize(name = 'query',
- summary = 'Query gem information in local or remote repositories')
+ def initialize(name = "query", summary = "Query gem information in local or remote repositories")
super name, summary,
- :name => //, :domain => :local, :details => false, :versions => true,
- :installed => nil, :version => Gem::Requirement.default
+ domain: :local, details: false, versions: true,
+ installed: nil, version: Gem::Requirement.default
- add_option('-n', '--name-matches REGEXP',
- 'Name of gem(s) to query on matches the',
- 'provided REGEXP') do |value, options|
+ add_option("-n", "--name-matches REGEXP",
+ "Name of gem(s) to query on matches the",
+ "provided REGEXP") do |value, options|
options[:name] = /#{value}/i
end
diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb
index e8c9e84b29..977c90b8c4 100644
--- a/lib/rubygems/commands/rdoc_command.rb
+++ b/lib/rubygems/commands/rdoc_command.rb
@@ -1,35 +1,36 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/rdoc'
-require 'fileutils'
+
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../rdoc"
+require "fileutils"
class Gem::Commands::RdocCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'rdoc', 'Generates RDoc for pre-installed gems',
- :version => Gem::Requirement.default,
- :include_rdoc => false, :include_ri => true, :overwrite => false
+ super "rdoc", "Generates RDoc for pre-installed gems",
+ version: Gem::Requirement.default,
+ include_rdoc: false, include_ri: true, overwrite: false
- add_option('--all',
- 'Generate RDoc/RI documentation for all',
- 'installed gems') do |value, options|
+ add_option("--all",
+ "Generate RDoc/RI documentation for all",
+ "installed gems") do |value, options|
options[:all] = value
end
- add_option('--[no-]rdoc',
- 'Generate RDoc HTML') do |value, options|
+ add_option("--[no-]rdoc",
+ "Generate RDoc HTML") do |value, options|
options[:include_rdoc] = value
end
- add_option('--[no-]ri',
- 'Generate RI data') do |value, options|
+ add_option("--[no-]ri",
+ "Generate RI data") do |value, options|
options[:include_ri] = value
end
- add_option('--[no-]overwrite',
- 'Overwrite installed documents') do |value, options|
+ add_option("--[no-]overwrite",
+ "Overwrite installed documents") do |value, options|
options[:overwrite] = value
end
@@ -61,15 +62,15 @@ Use --overwrite to force rebuilding of documentation.
def execute
specs = if options[:all]
- Gem::Specification.to_a
- else
- get_all_gem_names.map do |name|
- Gem::Specification.find_by_name name, options[:version]
- end.flatten.uniq
- end
+ Gem::Specification.to_a
+ else
+ get_all_gem_names.map do |name|
+ Gem::Specification.find_by_name name, options[:version]
+ end.flatten.uniq
+ end
if specs.empty?
- alert_error 'No matching gems found'
+ alert_error "No matching gems found"
terminate_interaction 1
end
@@ -79,17 +80,11 @@ Use --overwrite to force rebuilding of documentation.
doc.force = options[:overwrite]
if options[:overwrite]
- FileUtils.rm_rf File.join(spec.doc_dir, 'ri')
- FileUtils.rm_rf File.join(spec.doc_dir, 'rdoc')
+ FileUtils.rm_rf File.join(spec.doc_dir, "ri")
+ FileUtils.rm_rf File.join(spec.doc_dir, "rdoc")
end
- begin
- doc.generate
- rescue Errno::ENOENT => e
- e.message =~ / - /
- alert_error "Unable to document #{spec.full_name}, #{$'} is missing, skipping"
- terminate_interaction 1 if specs.length == 1
- end
+ doc.generate
end
end
end
diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb
new file mode 100644
index 0000000000..77a474ef1d
--- /dev/null
+++ b/lib/rubygems/commands/rebuild_command.rb
@@ -0,0 +1,262 @@
+# frozen_string_literal: true
+
+require "date"
+require "digest"
+require "fileutils"
+require "tmpdir"
+require_relative "../gemspec_helpers"
+require_relative "../package"
+
+class Gem::Commands::RebuildCommand < Gem::Command
+ include Gem::GemspecHelpers
+
+ def initialize
+ super "rebuild", "Attempt to reproduce a build of a gem."
+
+ add_option "--diff", "If the files don't match, compare them using diffoscope." do |_value, options|
+ options[:diff] = true
+ end
+
+ 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
+
+ add_option "--source GEM_SOURCE", "Specify the source to download the gem from." do |value, options|
+ options[:source] = value
+ end
+
+ add_option "--original GEM_FILE", "Specify a local file to compare against (instead of downloading it)." do |value, options|
+ options[:original_gem_file] = value
+ end
+
+ add_option "--gemspec GEMSPEC_FILE", "Specify the name of the gemspec file." do |value, options|
+ options[:gemspec_file] = value
+ end
+
+ add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, options|
+ options[:build_path] = value
+ end
+ end
+
+ def arguments # :nodoc:
+ "GEM_NAME gem name on gem server\n" \
+ "GEM_VERSION gem version you are attempting to rebuild"
+ end
+
+ def description # :nodoc:
+ <<-EOF
+The rebuild command allows you to (attempt to) reproduce a build of a gem
+from a ruby gemspec.
+
+This command assumes the gemspec can be built with the `gem build` command.
+If you use any of `gem build`, `rake build`, or`rake release` in the
+build/release process for a gem, it is a potential candidate.
+
+You will need to match the RubyGems version used, since this is included in
+the Gem metadata.
+
+If the gem includes lockfiles (e.g. Gemfile.lock) and similar, it will
+require more effort to reproduce a build. For example, it might require
+more precisely matched versions of Ruby and/or Bundler to be used.
+ EOF
+ end
+
+ def usage # :nodoc:
+ "#{program_name} GEM_NAME GEM_VERSION"
+ end
+
+ def execute
+ gem_name, gem_version = get_gem_name_and_version
+
+ old_dir, new_dir = prep_dirs
+
+ gem_filename = "#{gem_name}-#{gem_version}.gem"
+ old_file = File.join(old_dir, gem_filename)
+ new_file = File.join(new_dir, gem_filename)
+
+ if options[:original_gem_file]
+ FileUtils.copy_file(options[:original_gem_file], old_file)
+ else
+ download_gem(gem_name, gem_version, old_file)
+ end
+
+ rg_version = rubygems_version(old_file)
+ unless rg_version == Gem::VERSION
+ alert_error <<-EOF
+You need to use the same RubyGems version #{gem_name} v#{gem_version} was built with.
+
+#{gem_name} v#{gem_version} was built using RubyGems v#{rg_version}.
+Gem files include the version of RubyGems used to build them.
+This means in order to reproduce #{gem_filename}, you must also use RubyGems v#{rg_version}.
+
+You're using RubyGems v#{Gem::VERSION}.
+
+Please install RubyGems v#{rg_version} and try again.
+ EOF
+ terminate_interaction 1
+ end
+
+ source_date_epoch = get_timestamp(old_file).to_s
+
+ if build_path = options[:build_path]
+ Dir.chdir(build_path) { build_gem(gem_name, source_date_epoch, new_file) }
+ else
+ build_gem(gem_name, source_date_epoch, new_file)
+ end
+
+ compare(source_date_epoch, old_file, new_file)
+ end
+
+ private
+
+ def sha256(file)
+ Digest::SHA256.hexdigest(Gem.read_binary(file))
+ end
+
+ def get_timestamp(file)
+ mtime = nil
+ File.open(file, Gem.binary_mode) do |f|
+ Gem::Package::TarReader.new(f) do |tar|
+ mtime = tar.seek("metadata.gz") {|tf| tf.header.mtime }
+ end
+ end
+
+ mtime
+ end
+
+ def compare(source_date_epoch, old_file, new_file)
+ date = Time.at(source_date_epoch.to_i).strftime("%F %T %Z")
+
+ old_hash = sha256(old_file)
+ new_hash = sha256(new_file)
+
+ say
+ say "Built at: #{date} (#{source_date_epoch})"
+ say "Original build saved to: #{old_file}"
+ say "Reproduced build saved to: #{new_file}"
+ say "Working directory: #{options[:build_path] || Dir.pwd}"
+ say
+ say "Hash comparison:"
+ say " #{old_hash}\t#{old_file}"
+ say " #{new_hash}\t#{new_file}"
+ say
+
+ if old_hash == new_hash
+ say "SUCCESS - original and rebuild hashes matched"
+ else
+ say "FAILURE - original and rebuild hashes did not match"
+ say
+
+ if options[:diff]
+ if system("diffoscope", old_file, new_file).nil?
+ alert_error "error: could not find `diffoscope` executable"
+ end
+ else
+ say "Pass --diff for more details (requires diffoscope to be installed)."
+ end
+
+ terminate_interaction 1
+ end
+ end
+
+ def prep_dirs
+ rebuild_dir = Dir.mktmpdir("gem_rebuild")
+ old_dir = File.join(rebuild_dir, "old")
+ new_dir = File.join(rebuild_dir, "new")
+
+ FileUtils.mkdir_p(old_dir)
+ FileUtils.mkdir_p(new_dir)
+
+ [old_dir, new_dir]
+ end
+
+ def get_gem_name_and_version
+ args = options[:args] || []
+ if args.length == 2
+ gem_name, gem_version = args
+ elsif args.length > 2
+ raise Gem::CommandLineError, "Too many arguments"
+ else
+ raise Gem::CommandLineError, "Expected GEM_NAME and GEM_VERSION arguments (gem rebuild GEM_NAME GEM_VERSION)"
+ end
+
+ [gem_name, gem_version]
+ end
+
+ def build_gem(gem_name, source_date_epoch, output_file)
+ gemspec = options[:gemspec_file] || find_gemspec("#{gem_name}.gemspec")
+
+ if gemspec
+ build_package(gemspec, source_date_epoch, output_file)
+ else
+ alert_error error_message(gem_name)
+ terminate_interaction(1)
+ end
+ end
+
+ def build_package(gemspec, source_date_epoch, output_file)
+ with_source_date_epoch(source_date_epoch) do
+ spec = Gem::Specification.load(gemspec)
+ if spec
+ Gem::Package.build(
+ spec,
+ options[:force],
+ options[:strict],
+ output_file
+ )
+ else
+ alert_error "Error loading gemspec. Aborting."
+ terminate_interaction 1
+ end
+ end
+ end
+
+ def with_source_date_epoch(source_date_epoch)
+ old_sde = ENV["SOURCE_DATE_EPOCH"]
+ ENV["SOURCE_DATE_EPOCH"] = source_date_epoch.to_s
+
+ yield
+ ensure
+ ENV["SOURCE_DATE_EPOCH"] = old_sde
+ end
+
+ def error_message(gem_name)
+ if gem_name
+ "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}"
+ else
+ "Couldn't find a gemspec file in #{Dir.pwd}"
+ end
+ end
+
+ def download_gem(gem_name, gem_version, old_file)
+ # This code was based loosely off the `gem fetch` command.
+ version = "= #{gem_version}"
+ dep = Gem::Dependency.new gem_name, version
+
+ specs_and_sources, errors =
+ Gem::SpecFetcher.fetcher.spec_for_dependency dep
+
+ # There should never be more than one item in specs_and_sources,
+ # since we search for an exact version.
+ spec, source = specs_and_sources[0]
+
+ if spec.nil?
+ show_lookup_failure gem_name, version, errors, options[:domain]
+ terminate_interaction 1
+ end
+
+ download_path = source.download spec
+
+ FileUtils.move(download_path, old_file)
+
+ say "Downloaded #{gem_name} version #{gem_version} as #{old_file}."
+ end
+
+ def rubygems_version(gem_file)
+ Gem::Package.new(gem_file).spec.rubygems_version
+ end
+end
diff --git a/lib/rubygems/commands/search_command.rb b/lib/rubygems/commands/search_command.rb
index aeb2119235..50e161ac9b 100644
--- a/lib/rubygems/commands/search_command.rb
+++ b/lib/rubygems/commands/search_command.rb
@@ -1,14 +1,15 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/query_utils'
+
+require_relative "../command"
+require_relative "../query_utils"
class Gem::Commands::SearchCommand < Gem::Command
include Gem::QueryUtils
def initialize
- super 'search', 'Display remote gems whose name matches REGEXP',
- :name => //, :domain => :remote, :details => false, :versions => true,
- :installed => nil, :version => Gem::Requirement.default
+ super "search", "Display remote gems whose name matches REGEXP",
+ domain: :remote, details: false, versions: true,
+ installed: nil, version: Gem::Requirement.default
add_query_options
end
diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb
index 594cf77f66..f1dde4aa02 100644
--- a/lib/rubygems/commands/server_command.rb
+++ b/lib/rubygems/commands/server_command.rb
@@ -1,88 +1,26 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/server'
-require 'rubygems/deprecate'
-class Gem::Commands::ServerCommand < Gem::Command
- extend Gem::Deprecate
- rubygems_deprecate_command
+require_relative "../command"
- def initialize
- super 'server', 'Documentation and gem repository HTTP server',
- :port => 8808, :gemdir => [], :daemon => false
-
- OptionParser.accept :Port do |port|
- if port =~ /\A\d+\z/
- port = Integer port
- raise OptionParser::InvalidArgument, "#{port}: not a port number" if
- port > 65535
-
- port
- else
- begin
- Socket.getservbyname port
- rescue SocketError
- raise OptionParser::InvalidArgument, "#{port}: no such named service"
- end
+unless defined? Gem::Commands::ServerCommand
+ class Gem::Commands::ServerCommand < Gem::Command
+ def initialize
+ super("server", "Starts up a web server that hosts the RDoc (requires rubygems-server)")
+ begin
+ Gem::Specification.find_by_name("rubygems-server").activate
+ rescue Gem::LoadError
+ # no-op
end
end
- add_option '-p', '--port=PORT', :Port,
- 'port to listen on' do |port, options|
- options[:port] = port
- end
-
- add_option '-d', '--dir=GEMDIR',
- 'directories from which to serve gems',
- 'multiple directories may be provided' do |gemdir, options|
- options[:gemdir] << File.expand_path(gemdir)
- end
-
- add_option '--[no-]daemon', 'run as a daemon' do |daemon, options|
- options[:daemon] = daemon
- end
-
- add_option '-b', '--bind=HOST,HOST',
- 'addresses to bind', Array do |address, options|
- options[:addresses] ||= []
- options[:addresses].push(*address)
+ def description # :nodoc:
+ <<-EOF
+The server command has been moved to the rubygems-server gem.
+ EOF
end
- add_option '-l', '--launch[=COMMAND]',
- 'launches a browser window',
- "COMMAND defaults to 'start' on Windows",
- "and 'open' on all other platforms" do |launch, options|
- launch ||= Gem.win_platform? ? 'start' : 'open'
- options[:launch] = launch
+ def execute
+ alert_error "Install the rubygems-server gem for the server command"
end
end
-
- def defaults_str # :nodoc:
- "--port 8808 --dir #{Gem.dir} --no-daemon"
- end
-
- def description # :nodoc:
- <<-EOF
-The server command starts up a web server that hosts the RDoc for your
-installed gems and can operate as a server for installation of gems on other
-machines.
-
-The cache files for installed gems must exist to use the server as a source
-for gem installation.
-
-To install gems from a running server, use `gem install GEMNAME --source
-http://gem_server_host:8808`
-
-You can set up a shortcut to gem server documentation using the URL:
-
- http://localhost:8808/rdoc?q=%s - Firefox
- http://localhost:8808/rdoc?q=* - LaunchBar
-
- EOF
- end
-
- def execute
- options[:gemdir] = Gem.path if options[:gemdir].empty?
- Gem::Server.run options
- end
end
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 47e215c149..3f38074280 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -1,107 +1,106 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
##
# Installs RubyGems itself. This command is ordinarily only available from a
# RubyGems checkout or tarball.
class Gem::Commands::SetupCommand < Gem::Command
- HISTORY_HEADER = /^#\s*[\d.a-zA-Z]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze
- VERSION_MATCHER = /^#\s*([\d.a-zA-Z]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze
+ HISTORY_HEADER = %r{^#\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
+ VERSION_MATCHER = %r{^#\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
ENV_PATHS = %w[/usr/bin/env /bin/env].freeze
def initialize
- require 'tmpdir'
-
- super 'setup', 'Install RubyGems',
- :format_executable => false, :document => %w[ri],
- :force => true,
- :site_or_vendor => 'sitelibdir',
- :destdir => '', :prefix => '', :previous_version => '',
- :regenerate_binstubs => true,
- :regenerate_plugins => true
-
- add_option '--previous-version=VERSION',
- 'Previous version of RubyGems',
- 'Used for changelog processing' do |version, options|
+ super "setup", "Install RubyGems",
+ format_executable: false, document: %w[ri],
+ force: true,
+ site_or_vendor: "sitelibdir",
+ destdir: "", prefix: "", previous_version: "",
+ regenerate_binstubs: true,
+ regenerate_plugins: true
+
+ add_option "--previous-version=VERSION",
+ "Previous version of RubyGems",
+ "Used for changelog processing" do |version, options|
options[:previous_version] = version
end
- add_option '--prefix=PREFIX',
- 'Prefix path for installing RubyGems',
- 'Will not affect gem repository location' do |prefix, options|
+ add_option "--prefix=PREFIX",
+ "Prefix path for installing RubyGems",
+ "Will not affect gem repository location" do |prefix, options|
options[:prefix] = File.expand_path prefix
end
- add_option '--destdir=DESTDIR',
- 'Root directory to install RubyGems into',
- 'Mainly used for packaging RubyGems' do |destdir, options|
+ add_option "--destdir=DESTDIR",
+ "Root directory to install RubyGems into",
+ "Mainly used for packaging RubyGems" do |destdir, options|
options[:destdir] = File.expand_path destdir
end
- add_option '--[no-]vendor',
- 'Install into vendorlibdir not sitelibdir' do |vendor, options|
- options[:site_or_vendor] = vendor ? 'vendorlibdir' : 'sitelibdir'
+ add_option "--[no-]vendor",
+ "Install into vendorlibdir not sitelibdir" do |vendor, options|
+ options[:site_or_vendor] = vendor ? "vendorlibdir" : "sitelibdir"
end
- add_option '--[no-]format-executable',
- 'Makes `gem` match ruby',
- 'If Ruby is ruby18, gem will be gem18' do |value, options|
+ add_option "--[no-]format-executable",
+ "Makes `gem` match ruby",
+ "If Ruby is ruby18, gem will be gem18" do |value, options|
options[:format_executable] = value
end
- add_option '--[no-]document [TYPES]', Array,
- 'Generate documentation for RubyGems',
- 'List the documentation types you wish to',
- 'generate. For example: rdoc,ri' do |value, options|
+ add_option "--[no-]document [TYPES]", Array,
+ "Generate documentation for RubyGems",
+ "List the documentation types you wish to",
+ "generate. For example: rdoc,ri" do |value, options|
options[:document] = case value
when nil then %w[rdoc ri]
when false then []
- else value
- end
+ else value
+ end
end
- add_option '--[no-]rdoc',
- 'Generate RDoc documentation for RubyGems' do |value, options|
+ add_option "--[no-]rdoc",
+ "Generate RDoc documentation for RubyGems" do |value, options|
if value
- options[:document] << 'rdoc'
+ options[:document] << "rdoc"
else
- options[:document].delete 'rdoc'
+ options[:document].delete "rdoc"
end
options[:document].uniq!
end
- add_option '--[no-]ri',
- 'Generate RI documentation for RubyGems' do |value, options|
+ add_option "--[no-]ri",
+ "Generate RI documentation for RubyGems" do |value, options|
if value
- options[:document] << 'ri'
+ options[:document] << "ri"
else
- options[:document].delete 'ri'
+ options[:document].delete "ri"
end
options[:document].uniq!
end
- add_option '--[no-]regenerate-binstubs',
- 'Regenerate gem binstubs' do |value, options|
+ add_option "--[no-]regenerate-binstubs",
+ "Regenerate gem binstubs" do |value, options|
options[:regenerate_binstubs] = value
end
- add_option '--[no-]regenerate-plugins',
- 'Regenerate gem plugins' do |value, options|
+ add_option "--[no-]regenerate-plugins",
+ "Regenerate gem plugins" do |value, options|
options[:regenerate_plugins] = value
end
- add_option '-f', '--[no-]force',
- 'Forcefully overwrite binstubs' do |value, options|
+ add_option "-f", "--[no-]force",
+ "Forcefully overwrite binstubs" do |value, options|
options[:force] = value
end
- add_option('-E', '--[no-]env-shebang',
- 'Rewrite executables with a shebang',
- 'of /usr/bin/env') do |value, options|
+ add_option("-E", "--[no-]env-shebang",
+ "Rewrite executables with a shebang",
+ "of /usr/bin/env") do |value, options|
options[:env_shebang] = value
end
@@ -109,7 +108,7 @@ class Gem::Commands::SetupCommand < Gem::Command
end
def check_ruby_version
- required_version = Gem::Requirement.new '>= 2.3.0'
+ required_version = Gem::Requirement.new ">= 2.6.0"
unless required_version.satisfied_by? Gem.ruby_version
alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}"
@@ -135,7 +134,7 @@ prefix and suffix. If ruby was installed as `ruby18`, gem will be
installed as `gem18`.
By default, this RubyGems will install gem as:
- #{Gem.default_exec_format % 'gem'}
+ #{Gem.default_exec_format % "gem"}
EOF
end
@@ -149,16 +148,9 @@ By default, this RubyGems will install gem as:
def execute
@verbose = Gem.configuration.really_verbose
- install_destdir = options[:destdir]
-
- unless install_destdir.empty?
- ENV['GEM_HOME'] ||= File.join(install_destdir,
- Gem.default_dir.gsub(/^[a-zA-Z]:/, ''))
- end
-
check_ruby_version
- require 'fileutils'
+ require "fileutils"
if Gem.configuration.really_verbose
extend FileUtils::Verbose
else
@@ -166,8 +158,8 @@ By default, this RubyGems will install gem as:
end
extend MakeDirs
- lib_dir, bin_dir = make_destination_dirs install_destdir
- man_dir = generate_default_man_dir install_destdir
+ lib_dir, bin_dir = make_destination_dirs
+ man_dir = generate_default_man_dir
install_lib lib_dir
@@ -189,8 +181,8 @@ By default, this RubyGems will install gem as:
say "RubyGems #{Gem::VERSION} installed"
- regenerate_binstubs if options[:regenerate_binstubs]
- regenerate_plugins if options[:regenerate_plugins]
+ regenerate_binstubs(bin_dir) if options[:regenerate_binstubs]
+ regenerate_plugins(bin_dir) if options[:regenerate_plugins]
uninstall_old_gemcutter
@@ -203,7 +195,7 @@ By default, this RubyGems will install gem as:
end
if options[:previous_version].empty?
- options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, '0')
+ options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, "0")
end
options[:previous_version] = Gem::Version.new(options[:previous_version])
@@ -225,7 +217,7 @@ By default, this RubyGems will install gem as:
end
if documentation_success
- if options[:document].include? 'rdoc'
+ if options[:document].include? "rdoc"
say "Rdoc documentation was installed. You may now invoke:"
say " gem server"
say "and then peruse beautifully formatted documentation for your gems"
@@ -236,7 +228,7 @@ By default, this RubyGems will install gem as:
say
end
- if options[:document].include? 'ri'
+ if options[:document].include? "ri"
say "Ruby Interactive (ri) documentation was installed. ri is kind of like man "
say "pages for Ruby libraries. You may access it like this:"
say " ri Classname"
@@ -251,42 +243,41 @@ By default, this RubyGems will install gem as:
end
def install_executables(bin_dir)
- prog_mode = options[:prog_mode] || 0755
+ prog_mode = options[:prog_mode] || 0o755
- executables = { 'gem' => 'bin' }
+ executables = { "gem" => "exe" }
executables.each do |tool, path|
say "Installing #{tool} executable" if @verbose
Dir.chdir path do
- bin_files = Dir['*']
+ bin_file = "gem"
- bin_files -= %w[update_rubygems]
+ require "tmpdir"
- bin_files.each do |bin_file|
- dest_file = target_bin_path(bin_dir, bin_file)
- bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
+ dest_file = target_bin_path(bin_dir, bin_file)
+ bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
- begin
- bin = File.readlines bin_file
- bin[0] = shebang
+ begin
+ bin = File.readlines bin_file
+ bin[0] = shebang
- File.open bin_tmp_file, 'w' do |fp|
- fp.puts bin.join
- end
-
- install bin_tmp_file, dest_file, :mode => prog_mode
- bin_file_names << dest_file
- ensure
- rm bin_tmp_file
+ File.open bin_tmp_file, "w" do |fp|
+ fp.puts bin.join
end
- next unless Gem.win_platform?
+ install bin_tmp_file, dest_file, mode: prog_mode
+ bin_file_names << dest_file
+ ensure
+ rm bin_tmp_file
+ end
+
+ next unless Gem.win_platform?
- begin
- bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
+ begin
+ bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
- File.open bin_cmd_file, 'w' do |file|
- file.puts <<-TEXT
+ File.open bin_cmd_file, "w" do |file|
+ file.puts <<-TEXT
@ECHO OFF
IF NOT "%~f0" == "~f0" GOTO :WinNT
@"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9
@@ -294,12 +285,11 @@ By default, this RubyGems will install gem as:
:WinNT
@"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %*
TEXT
- end
-
- install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode
- ensure
- rm bin_cmd_file
end
+
+ install bin_cmd_file, "#{dest_file}.bat", mode: prog_mode
+ ensure
+ rm bin_cmd_file
end
end
end
@@ -307,7 +297,7 @@ By default, this RubyGems will install gem as:
def shebang
if options[:env_shebang]
- ruby_name = RbConfig::CONFIG['ruby_install_name']
+ ruby_name = RbConfig::CONFIG["ruby_install_name"]
@env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
"#!#{@env_path} #{ruby_name}\n"
else
@@ -316,8 +306,8 @@ By default, this RubyGems will install gem as:
end
def install_lib(lib_dir)
- libs = { 'RubyGems' => 'lib' }
- libs['Bundler'] = 'bundler/lib'
+ libs = { "RubyGems" => "lib" }
+ libs["Bundler"] = "bundler/lib"
libs.each do |tool, path|
say "Installing #{tool}" if @verbose
@@ -330,7 +320,7 @@ By default, this RubyGems will install gem as:
end
def install_rdoc
- gem_doc_dir = File.join Gem.dir, 'doc'
+ gem_doc_dir = File.join Gem.dir, "doc"
rubygems_name = "rubygems-#{Gem::VERSION}"
rubygems_doc_dir = File.join gem_doc_dir, rubygems_name
@@ -340,23 +330,23 @@ By default, this RubyGems will install gem as:
# ignore
end
- if File.writable? gem_doc_dir and
- (not File.exist? rubygems_doc_dir or
- File.writable? rubygems_doc_dir)
+ if File.writable?(gem_doc_dir) &&
+ (!File.exist?(rubygems_doc_dir) ||
+ File.writable?(rubygems_doc_dir))
say "Removing old RubyGems RDoc and ri" if @verbose
- Dir[File.join(Gem.dir, 'doc', 'rubygems-[0-9]*')].each do |dir|
+ Dir[File.join(Gem.dir, "doc", "rubygems-[0-9]*")].each do |dir|
rm_rf dir
end
- require 'rubygems/rdoc'
+ require_relative "../rdoc"
- fake_spec = Gem::Specification.new 'rubygems', Gem::VERSION
+ fake_spec = Gem::Specification.new "rubygems", Gem::VERSION
def fake_spec.full_gem_path
- File.expand_path '../../../..', __FILE__
+ File.expand_path "../../..", __dir__
end
- generate_ri = options[:document].include? 'ri'
- generate_rdoc = options[:document].include? 'rdoc'
+ generate_ri = options[:document].include? "ri"
+ generate_rdoc = options[:document].include? "rdoc"
rdoc = Gem::RDoc.new fake_spec, generate_rdoc, generate_ri
rdoc.generate
@@ -367,28 +357,33 @@ By default, this RubyGems will install gem as:
say "Set the GEM_HOME environment variable if you want RDoc generated"
end
- return false
+ false
end
def install_default_bundler_gem(bin_dir)
- specs_dir = Gem.default_specifications_dir
- specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform?
- mkdir_p specs_dir, :mode => 0755
-
- bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") }
+ current_default_spec = Gem::Specification.default_stubs.find {|s| s.name == "bundler" }
+ specs_dir = if current_default_spec && default_dir == Gem.default_dir
+ Gem::Specification.remove_spec current_default_spec
+ loaded_from = current_default_spec.loaded_from
+ File.delete(loaded_from)
+ File.dirname(loaded_from)
+ else
+ target_specs_dir = File.join(default_dir, "specifications", "default")
+ mkdir_p target_specs_dir, mode: 0o755
+ target_specs_dir
+ end
- # Remove bundler-*.gemspec in default specification directory.
- Dir.entries(specs_dir).
- select {|gs| gs.start_with?("bundler-") }.
- each {|gs| File.delete(File.join(specs_dir, gs)) }
+ new_bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") }
+ full_name = new_bundler_spec.full_name
+ gemspec_path = "#{full_name}.gemspec"
- default_spec_path = File.join(specs_dir, "#{bundler_spec.full_name}.gemspec")
- Gem.write_binary(default_spec_path, bundler_spec.to_ruby)
+ default_spec_path = File.join(specs_dir, gemspec_path)
+ Gem.write_binary(default_spec_path, new_bundler_spec.to_ruby)
bundler_spec = Gem::Specification.load(default_spec_path)
# Remove gemspec that was same version of vendored bundler.
- normal_gemspec = File.join(Gem.default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec")
+ normal_gemspec = File.join(default_dir, "specifications", gemspec_path)
if File.file? normal_gemspec
File.delete normal_gemspec
end
@@ -396,111 +391,91 @@ By default, this RubyGems will install gem as:
# Remove gem files that were same version of vendored bundler.
if File.directory? bundler_spec.gems_dir
Dir.entries(bundler_spec.gems_dir).
- select {|default_gem| File.basename(default_gem) == "bundler-#{bundler_spec.version}" }.
+ select {|default_gem| File.basename(default_gem) == full_name }.
each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) }
end
- bundler_bin_dir = bundler_spec.bin_dir
- bundler_bin_dir = File.join(options[:destdir], bundler_bin_dir) unless Gem.win_platform?
- mkdir_p bundler_bin_dir, :mode => 0755
- bundler_spec.executables.each do |e|
- cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_bin_dir, e)
- end
-
- require 'rubygems/installer'
+ require_relative "../installer"
Dir.chdir("bundler") do
- built_gem = Gem::Package.build(bundler_spec)
+ built_gem = Gem::Package.build(new_bundler_spec)
begin
- installer = Gem::Installer.at(built_gem, env_shebang: options[:env_shebang], format_executable: options[:format_executable], force: options[:force], install_as_default: true, bin_dir: bin_dir, wrappers: true)
- installer.install
+ Gem::Installer.at(
+ built_gem,
+ env_shebang: options[:env_shebang],
+ format_executable: options[:format_executable],
+ force: options[:force],
+ install_as_default: true,
+ bin_dir: bin_dir,
+ install_dir: default_dir,
+ wrappers: true
+ ).install
ensure
FileUtils.rm_f built_gem
end
end
- bundler_spec.executables.each {|executable| bin_file_names << target_bin_path(bin_dir, executable) }
+ new_bundler_spec.executables.each {|executable| bin_file_names << target_bin_path(bin_dir, executable) }
- say "Bundler #{bundler_spec.version} installed"
+ say "Bundler #{new_bundler_spec.version} installed"
end
- def make_destination_dirs(install_destdir)
+ def make_destination_dirs
lib_dir, bin_dir = Gem.default_rubygems_dirs
unless lib_dir
- lib_dir, bin_dir = generate_default_dirs(install_destdir)
+ lib_dir, bin_dir = generate_default_dirs
end
- mkdir_p lib_dir, :mode => 0755
- mkdir_p bin_dir, :mode => 0755
+ mkdir_p lib_dir, mode: 0o755
+ mkdir_p bin_dir, mode: 0o755
- return lib_dir, bin_dir
+ [lib_dir, bin_dir]
end
- def generate_default_man_dir(install_destdir)
+ def generate_default_man_dir
prefix = options[:prefix]
if prefix.empty?
- man_dir = RbConfig::CONFIG['mandir']
+ man_dir = RbConfig::CONFIG["mandir"]
return unless man_dir
else
- man_dir = File.join prefix, 'man'
- end
-
- unless install_destdir.empty?
- man_dir = File.join install_destdir, man_dir.gsub(/^[a-zA-Z]:/, '')
+ man_dir = File.join prefix, "man"
end
- man_dir
+ prepend_destdir_if_present(man_dir)
end
- def generate_default_dirs(install_destdir)
+ def generate_default_dirs
prefix = options[:prefix]
site_or_vendor = options[:site_or_vendor]
if prefix.empty?
lib_dir = RbConfig::CONFIG[site_or_vendor]
- bin_dir = RbConfig::CONFIG['bindir']
+ bin_dir = RbConfig::CONFIG["bindir"]
else
- # Apple installed RubyGems into libdir, and RubyGems <= 1.1.0 gets
- # confused about installation location, so switch back to
- # sitelibdir/vendorlibdir.
- if defined?(APPLE_GEM_HOME) and
- # just in case Apple and RubyGems don't get this patched up proper.
- (prefix == RbConfig::CONFIG['libdir'] or
- # this one is important
- prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby'))
- 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'
- end
+ lib_dir = File.join prefix, "lib"
+ bin_dir = File.join prefix, "bin"
end
- unless install_destdir.empty?
- lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '')
- bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '')
- end
-
- [lib_dir, bin_dir]
+ [prepend_destdir_if_present(lib_dir), prepend_destdir_if_present(bin_dir)]
end
def files_in(dir)
Dir.chdir dir do
- Dir.glob(File.join('**', '*'), File::FNM_DOTMATCH).
- select{|f| !File.directory?(f) }
+ Dir.glob(File.join("**", "*"), File::FNM_DOTMATCH).
+ select {|f| !File.directory?(f) }
end
end
def remove_old_bin_files(bin_dir)
old_bin_files = {
- 'gem_mirror' => 'gem mirror',
- 'gem_server' => 'gem server',
- 'gemlock' => 'gem lock',
- 'gemri' => 'ri',
- 'gemwhich' => 'gem which',
- 'index_gem_repository.rb' => 'gem generate_index',
+ "gem_mirror" => "gem mirror",
+ "gem_server" => "gem server",
+ "gemlock" => "gem lock",
+ "gemri" => "ri",
+ "gemwhich" => "gem which",
+ "index_gem_repository.rb" => "gem generate_index",
}
old_bin_files.each do |old_bin_file, new_name|
@@ -509,7 +484,7 @@ By default, this RubyGems will install gem as:
deprecation_message = "`#{old_bin_file}` has been deprecated. Use `#{new_name}` instead."
- File.open old_bin_path, 'w' do |fp|
+ File.open old_bin_path, "w" do |fp|
fp.write <<-EOF
#!#{Gem.ruby}
@@ -519,15 +494,15 @@ abort "#{deprecation_message}"
next unless Gem.win_platform?
- File.open "#{old_bin_path}.bat", 'w' do |fp|
+ File.open "#{old_bin_path}.bat", "w" do |fp|
fp.puts %(@ECHO.#{deprecation_message})
end
end
end
def remove_old_lib_files(lib_dir)
- lib_dirs = { File.join(lib_dir, 'rubygems') => 'lib/rubygems' }
- lib_dirs[File.join(lib_dir, 'bundler')] = 'bundler/lib/bundler'
+ lib_dirs = { File.join(lib_dir, "rubygems") => "lib/rubygems" }
+ lib_dirs[File.join(lib_dir, "bundler")] = "bundler/lib/bundler"
lib_dirs.each do |old_lib_dir, new_lib_dir|
lib_files = files_in(new_lib_dir)
@@ -535,11 +510,11 @@ abort "#{deprecation_message}"
to_remove = old_lib_files - lib_files
- gauntlet_rubygems = File.join(lib_dir, 'gauntlet_rubygems.rb')
+ gauntlet_rubygems = File.join(lib_dir, "gauntlet_rubygems.rb")
to_remove << gauntlet_rubygems if File.exist? gauntlet_rubygems
to_remove.delete_if do |file|
- file.start_with? 'defaults'
+ file.start_with? "defaults"
end
remove_file_list(to_remove, old_lib_dir)
@@ -565,7 +540,7 @@ abort "#{deprecation_message}"
end
def show_release_notes
- release_notes = File.join Dir.pwd, 'CHANGELOG.md'
+ release_notes = File.join Dir.pwd, "CHANGELOG.md"
release_notes =
if File.exist? release_notes
@@ -582,7 +557,7 @@ abort "#{deprecation_message}"
history_string = ""
- until versions.length == 0 or
+ until versions.length == 0 ||
versions.shift <= options[:previous_version] do
history_string += version_lines.shift + text.shift
end
@@ -596,19 +571,20 @@ abort "#{deprecation_message}"
end
def uninstall_old_gemcutter
- require 'rubygems/uninstaller'
+ require_relative "../uninstaller"
- ui = Gem::Uninstaller.new('gemcutter', :all => true, :ignore => true,
- :version => '< 0.4')
+ ui = Gem::Uninstaller.new("gemcutter", all: true, ignore: true,
+ version: "< 0.4")
ui.uninstall
rescue Gem::InstallError
end
- def regenerate_binstubs
- require "rubygems/commands/pristine_command"
+ def regenerate_binstubs(bindir)
+ require_relative "pristine_command"
say "Regenerating binstubs"
args = %w[--all --only-executables --silent]
+ args << "--bindir=#{bindir}"
if options[:env_shebang]
args << "--env-shebang"
end
@@ -617,11 +593,13 @@ abort "#{deprecation_message}"
command.invoke(*args)
end
- def regenerate_plugins
- require "rubygems/commands/pristine_command"
+ def regenerate_plugins(bindir)
+ require_relative "pristine_command"
say "Regenerating plugins"
args = %w[--all --only-plugins --silent]
+ args << "--bindir=#{bindir}"
+ args << "--install-dir=#{default_dir}"
command = Gem::Commands::PristineCommand.new
command.invoke(*args)
@@ -629,6 +607,25 @@ abort "#{deprecation_message}"
private
+ def default_dir
+ prefix = options[:prefix]
+
+ if prefix.empty?
+ dir = Gem.default_dir
+ else
+ dir = prefix
+ end
+
+ prepend_destdir_if_present(dir)
+ end
+
+ def prepend_destdir_if_present(path)
+ destdir = options[:destdir]
+ return path if destdir.empty?
+
+ File.join(options[:destdir], path.gsub(/^[a-zA-Z]:/, ""))
+ end
+
def install_file_list(files, dest_dir)
files.each do |file|
install_file file, dest_dir
@@ -639,10 +636,10 @@ abort "#{deprecation_message}"
dest_file = File.join dest_dir, file
dest_dir = File.dirname dest_file
unless File.directory? dest_dir
- mkdir_p dest_dir, :mode => 0755
+ mkdir_p dest_dir, mode: 0o755
end
- install file, dest_file, :mode => options[:data_mode] || 0644
+ install file, dest_file, mode: options[:data_mode] || 0o644
end
def remove_file_list(files, dir)
@@ -658,10 +655,10 @@ abort "#{deprecation_message}"
def target_bin_path(bin_dir, bin_file)
bin_file_formatted = if options[:format_executable]
- Gem.default_exec_format % bin_file
- else
- bin_file
- end
+ Gem.default_exec_format % bin_file
+ else
+ bin_file
+ end
File.join bin_dir, bin_file_formatted
end
diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb
index 2e19c8333c..0f77908c5b 100644
--- a/lib/rubygems/commands/signin_command.rb
+++ b/lib/rubygems/commands/signin_command.rb
@@ -1,15 +1,16 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/gemcutter_utilities'
+
+require_relative "../command"
+require_relative "../gemcutter_utilities"
class Gem::Commands::SigninCommand < Gem::Command
include Gem::GemcutterUtilities
def initialize
- super 'signin', 'Sign in to any gemcutter-compatible host. '\
- 'It defaults to https://rubygems.org'
+ super "signin", "Sign in to any gemcutter-compatible host. "\
+ "It defaults to https://rubygems.org"
- add_option('--host HOST', 'Push to another gemcutter-compatible host') do |value, options|
+ add_option("--host HOST", "Push to another gemcutter-compatible host") do |value, options|
options[:host] = value
end
@@ -17,10 +18,10 @@ class Gem::Commands::SigninCommand < Gem::Command
end
def description # :nodoc:
- 'The signin command executes host sign in for a push server (the default is'\
- ' https://rubygems.org). The host can be provided with the host flag or can'\
- ' be inferred from the provided gem. Host resolution matches the resolution'\
- ' strategy for the push command.'
+ "The signin command executes host sign in for a push server (the default is"\
+ " https://rubygems.org). The host can be provided with the host flag or can"\
+ " be inferred from the provided gem. Host resolution matches the resolution"\
+ " strategy for the push command."
end
def usage # :nodoc:
diff --git a/lib/rubygems/commands/signout_command.rb b/lib/rubygems/commands/signout_command.rb
index ebbe746cb4..bdd01e4393 100644
--- a/lib/rubygems/commands/signout_command.rb
+++ b/lib/rubygems/commands/signout_command.rb
@@ -1,14 +1,15 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::SignoutCommand < Gem::Command
def initialize
- super 'signout', 'Sign out from all the current sessions.'
+ super "signout", "Sign out from all the current sessions."
end
def description # :nodoc:
- 'The `signout` command is used to sign out from all current sessions,'\
- ' allowing you to sign in using a different set of credentials.'
+ "The `signout` command is used to sign out from all current sessions,"\
+ " allowing you to sign in using a different set of credentials."
end
def usage # :nodoc:
@@ -19,13 +20,13 @@ class Gem::Commands::SignoutCommand < Gem::Command
credentials_path = Gem.configuration.credentials_path
if !File.exist?(credentials_path)
- alert_error 'You are not currently signed in.'
+ alert_error "You are not currently signed in."
elsif !File.writable?(credentials_path)
alert_error "File '#{Gem.configuration.credentials_path}' is read-only."\
- ' Please make sure it is writable.'
+ " Please make sure it is writable."
else
Gem.configuration.unset_api_key!
- say 'You have successfully signed out from all sessions.'
+ say "You have successfully signed out from all sessions."
end
end
end
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index f74fb12e42..976f4a4ea2 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -1,40 +1,41 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/remote_fetcher'
-require 'rubygems/spec_fetcher'
-require 'rubygems/local_remote_options'
+
+require_relative "../command"
+require_relative "../remote_fetcher"
+require_relative "../spec_fetcher"
+require_relative "../local_remote_options"
class Gem::Commands::SourcesCommand < Gem::Command
include Gem::LocalRemoteOptions
def initialize
- require 'fileutils'
+ require "fileutils"
- super 'sources',
- 'Manage the sources and cache file RubyGems uses to search for gems'
+ super "sources",
+ "Manage the sources and cache file RubyGems uses to search for gems"
- add_option '-a', '--add SOURCE_URI', 'Add source' do |value, options|
+ add_option "-a", "--add SOURCE_URI", "Add source" do |value, options|
options[:add] = value
end
- add_option '-l', '--list', 'List sources' do |value, options|
+ add_option "-l", "--list", "List sources" do |value, options|
options[:list] = value
end
- add_option '-r', '--remove SOURCE_URI', 'Remove source' do |value, options|
+ add_option "-r", "--remove SOURCE_URI", "Remove source" do |value, options|
options[:remove] = value
end
- add_option '-c', '--clear-all',
- 'Remove all sources (clear the cache)' do |value, options|
+ add_option "-c", "--clear-all",
+ "Remove all sources (clear the cache)" do |value, options|
options[:clear_all] = value
end
- add_option '-u', '--update', 'Update source cache' do |value, options|
+ add_option "-u", "--update", "Update source cache" do |value, options|
options[:update] = value
end
- add_option '-f', '--[no-]force', "Do not show any confirmation prompts and behave as if 'yes' was always answered" do |value, options|
+ add_option "-f", "--[no-]force", "Do not show any confirmation prompts and behave as if 'yes' was always answered" do |value, options|
options[:force] = value
end
@@ -58,11 +59,11 @@ class Gem::Commands::SourcesCommand < Gem::Command
say "#{source_uri} added to sources"
end
- rescue URI::Error, ArgumentError
+ rescue Gem::URI::Error, ArgumentError
say "#{source_uri} is not a URI"
terminate_interaction 1
rescue Gem::RemoteFetcher::FetchError => e
- say "Error fetching #{source_uri}:\n\t#{e.message}"
+ say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}"
terminate_interaction 1
end
end
@@ -70,7 +71,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
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
+#{source.uri} is too similar to https://rubygems.org
Do you want to add this source?
QUESTION
@@ -80,10 +81,10 @@ Do you want to add this source?
end
def check_rubygems_https(source_uri) # :nodoc:
- uri = URI source_uri
+ uri = Gem::URI source_uri
- if uri.scheme and uri.scheme.downcase == 'http' and
- uri.host.downcase == 'rubygems.org'
+ if uri.scheme && uri.scheme.casecmp("http").zero? &&
+ uri.host.casecmp("rubygems.org").zero?
question = <<-QUESTION.chomp
https://rubygems.org is recommended for security over #{uri}
@@ -98,21 +99,21 @@ Do you want to add this insecure source?
path = Gem.spec_cache_dir
FileUtils.rm_rf path
- unless File.exist? path
- say "*** Removed specs cache ***"
- else
- unless File.writable? path
- say "*** Unable to remove source cache (write protected) ***"
- else
+ if File.exist? path
+ if File.writable? path
say "*** Unable to remove source cache ***"
+ else
+ say "*** Unable to remove source cache (write protected) ***"
end
terminate_interaction 1
+ else
+ say "*** Removed specs cache ***"
end
end
def defaults_str # :nodoc:
- '--list'
+ "--list"
end
def description # :nodoc:
@@ -138,8 +139,8 @@ do not recognize you should remove them.
RubyGems has been configured to serve gems via the following URLs through
its history:
-* http://gems.rubyforge.org (RubyGems 1.3.6 and earlier)
-* https://rubygems.org/ (RubyGems 1.3.7 through 1.8.25)
+* http://gems.rubyforge.org (RubyGems 1.3.5 and earlier)
+* http://rubygems.org (RubyGems 1.3.6 through 1.8.30, and 2.0.0)
* https://rubygems.org (RubyGems 2.0.1 and newer)
Since all of these sources point to the same set of gems you only need one
@@ -193,13 +194,13 @@ To remove a source use the --remove argument:
end
def remove_source(source_uri) # :nodoc:
- unless Gem.sources.include? source_uri
- say "source #{source_uri} not present in cache"
- else
+ if Gem.sources.include? source_uri
Gem.sources.delete source_uri
Gem.configuration.write
say "#{source_uri} removed from sources"
+ else
+ say "source #{source_uri} not present in cache"
end
end
@@ -215,9 +216,9 @@ To remove a source use the --remove argument:
def remove_cache_file(desc, path) # :nodoc:
FileUtils.rm_rf path
- if not File.exist?(path)
+ if !File.exist?(path)
say "*** Removed #{desc} source cache ***"
- elsif not File.writable?(path)
+ elsif !File.writable?(path)
say "*** Unable to remove #{desc} source cache (write protected) ***"
else
say "*** Unable to remove #{desc} source cache ***"
diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb
index 3fddaaaf30..a21ed35be3 100644
--- a/lib/rubygems/commands/specification_command.rb
+++ b/lib/rubygems/commands/specification_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
-require 'rubygems/package'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
+require_relative "../package"
class Gem::Commands::SpecificationCommand < Gem::Command
include Gem::LocalRemoteOptions
@@ -11,28 +12,28 @@ class Gem::Commands::SpecificationCommand < Gem::Command
def initialize
Gem.load_yaml
- super 'specification', 'Display gem specification (in yaml)',
- :domain => :local, :version => Gem::Requirement.default,
- :format => :yaml
+ super "specification", "Display gem specification (in yaml)",
+ domain: :local, version: Gem::Requirement.default,
+ format: :yaml
- add_version_option('examine')
+ add_version_option("examine")
add_platform_option
add_prerelease_option
- add_option('--all', 'Output specifications for all versions of',
- 'the gem') do |value, options|
+ add_option("--all", "Output specifications for all versions of",
+ "the gem") do |_value, options|
options[:all] = true
end
- add_option('--ruby', 'Output ruby format') do |value, options|
+ add_option("--ruby", "Output ruby format") do |_value, options|
options[:format] = :ruby
end
- add_option('--yaml', 'Output YAML format') do |value, options|
+ add_option("--yaml", "Output YAML format") do |_value, options|
options[:format] = :yaml
end
- add_option('--marshal', 'Output Marshal format') do |value, options|
+ add_option("--marshal", "Output Marshal format") do |_value, options|
options[:format] = :marshal
end
@@ -88,7 +89,7 @@ Specific fields in the specification can be extracted in YAML format:
raise Gem::CommandLineError, "Unsupported version type: '#{v}'"
end
- if !req.none? and options[:all]
+ if !req.none? && options[:all]
alert_error "Specify --all or -v, not both"
terminate_interaction 1
end
@@ -102,11 +103,15 @@ Specific fields in the specification can be extracted in YAML format:
field = get_one_optional_argument
raise Gem::CommandLineError, "--ruby and FIELD are mutually exclusive" if
- field and options[:format] == :ruby
+ field && options[:format] == :ruby
if local?
if File.exist? gem
- specs << Gem::Package.new(gem).spec rescue nil
+ begin
+ specs << Gem::Package.new(gem).spec
+ rescue StandardError
+ nil
+ end
end
if specs.empty?
@@ -129,11 +134,11 @@ Specific fields in the specification can be extracted in YAML format:
platform = get_platform_from_requirements(options)
if platform
- specs = specs.select{|s| s.platform.to_s == platform }
+ specs = specs.select {|s| s.platform.to_s == platform }
end
unless options[:all]
- specs = [specs.max_by {|s| s.version }]
+ specs = [specs.max_by(&:version)]
end
specs.each do |s|
@@ -143,7 +148,7 @@ Specific fields in the specification can be extracted in YAML format:
when :ruby then s.to_ruby
when :marshal then Marshal.dump s
else s.to_yaml
- end
+ end
say "\n"
end
diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb
index badc9905c1..0be2b85159 100644
--- a/lib/rubygems/commands/stale_command.rb
+++ b/lib/rubygems/commands/stale_command.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::StaleCommand < Gem::Command
def initialize
- super('stale', 'List gems along with access times')
+ super("stale", "List gems along with access times")
end
def description # :nodoc:
@@ -17,7 +18,7 @@ longer using.
end
def usage # :nodoc:
- "#{program_name}"
+ program_name.to_s
end
def execute
@@ -33,7 +34,7 @@ longer using.
end
gem_to_atime.sort_by {|_, atime| atime }.each do |name, atime|
- say "#{name} at #{atime.strftime '%c'}"
+ say "#{name} at #{atime.strftime "%c"}"
end
end
end
diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb
index 1540b2f0fb..2a77ec72cf 100644
--- a/lib/rubygems/commands/uninstall_command.rb
+++ b/lib/rubygems/commands/uninstall_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/uninstaller'
-require 'fileutils'
+
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../uninstaller"
+require "fileutils"
##
# Gem uninstaller command line tool
@@ -13,78 +14,77 @@ class Gem::Commands::UninstallCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'uninstall', 'Uninstall gems from the local repository',
- :version => Gem::Requirement.default, :user_install => true,
- :check_dev => false, :vendor => false
+ super "uninstall", "Uninstall gems from the local repository",
+ version: Gem::Requirement.default, user_install: true,
+ check_dev: false, vendor: false
- add_option('-a', '--[no-]all',
- 'Uninstall all matching versions'
- ) do |value, options|
+ add_option("-a", "--[no-]all",
+ "Uninstall all matching versions") do |value, options|
options[:all] = value
end
- add_option('-I', '--[no-]ignore-dependencies',
- 'Ignore dependency requirements while',
- 'uninstalling') do |value, options|
+ add_option("-I", "--[no-]ignore-dependencies",
+ "Ignore dependency requirements while",
+ "uninstalling") do |value, options|
options[:ignore] = value
end
- add_option('-D', '--[no-]check-development',
- 'Check development dependencies while uninstalling',
- '(default: false)') do |value, options|
+ add_option("-D", "--[no-]check-development",
+ "Check development dependencies while uninstalling",
+ "(default: false)") do |value, options|
options[:check_dev] = value
end
- add_option('-x', '--[no-]executables',
- 'Uninstall applicable executables without',
- 'confirmation') do |value, options|
+ add_option("-x", "--[no-]executables",
+ "Uninstall applicable executables without",
+ "confirmation") do |value, options|
options[:executables] = value
end
- add_option('-i', '--install-dir DIR',
- 'Directory to uninstall gem from') do |value, options|
+ add_option("-i", "--install-dir DIR",
+ "Directory to uninstall gem from") do |value, options|
options[:install_dir] = File.expand_path(value)
end
- add_option('-n', '--bindir DIR',
- 'Directory to remove executables from') do |value, options|
+ add_option("-n", "--bindir DIR",
+ "Directory to remove executables from") do |value, options|
options[:bin_dir] = File.expand_path(value)
end
- add_option('--[no-]user-install',
- 'Uninstall from user\'s home directory',
- 'in addition to GEM_HOME.') do |value, options|
+ add_option("--[no-]user-install",
+ "Uninstall from user's home directory",
+ "in addition to GEM_HOME.") do |value, options|
options[:user_install] = value
end
- add_option('--[no-]format-executable',
- 'Assume executable names match Ruby\'s prefix and suffix.') do |value, options|
+ add_option("--[no-]format-executable",
+ "Assume executable names match Ruby's prefix and suffix.") do |value, options|
options[:format_executable] = value
end
- add_option('--[no-]force',
- 'Uninstall all versions of the named gems',
- 'ignoring dependencies') do |value, options|
+ add_option("--[no-]force",
+ "Uninstall all versions of the named gems",
+ "ignoring dependencies") do |value, options|
options[:force] = value
end
- add_option('--[no-]abort-on-dependent',
- 'Prevent uninstalling gems that are',
- 'depended on by other gems.') do |value, options|
+ add_option("--[no-]abort-on-dependent",
+ "Prevent uninstalling gems that are",
+ "depended on by other gems.") do |value, options|
options[:abort_on_dependent] = value
end
add_version_option
add_platform_option
- add_option('--vendor',
- 'Uninstall gem from the vendor directory.',
- 'Only for use by gem repackagers.') do |value, options|
+ add_option("--vendor",
+ "Uninstall gem from the vendor directory.",
+ "Only for use by gem repackagers.") do |_value, options|
unless Gem.vendor_dir
- raise OptionParser::InvalidOption.new 'your platform is not supported'
+ raise Gem::OptionParser::InvalidOption.new "your platform is not supported"
end
- alert_warning 'Use your OS package manager to uninstall vendor gems'
+ alert_warning "Use your OS package manager to uninstall vendor gems"
options[:vendor] = true
options[:install_dir] = Gem.vendor_dir
end
@@ -95,8 +95,8 @@ class Gem::Commands::UninstallCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--version '#{Gem::Requirement.default}' --no-force " +
- "--user-install"
+ "--version '#{Gem::Requirement.default}' --no-force " \
+ "--user-install"
end
def description # :nodoc:
@@ -114,8 +114,8 @@ that is a dependency of an existing gem. You can use the
end
def check_version # :nodoc:
- if options[:version] != Gem::Requirement.default and
- get_all_gem_names.size > 1
+ if options[:version] != Gem::Requirement.default &&
+ get_all_gem_names.size > 1
alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \
" version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
terminate_interaction 1
@@ -125,7 +125,10 @@ that is a dependency of an existing gem. You can use the
def execute
check_version
- if options[:all] and not options[:args].empty?
+ # Consider only gem specifications installed at `--install-dir`
+ Gem::Specification.dirs = options[:install_dir] if options[:install_dir]
+
+ if options[:all] && !options[:args].empty?
uninstall_specific
elsif options[:all]
uninstall_all
@@ -135,7 +138,7 @@ that is a dependency of an existing gem. You can use the
end
def uninstall_all
- specs = Gem::Specification.reject {|spec| spec.default_gem? }
+ specs = Gem::Specification.reject(&:default_gem?)
specs.each do |spec|
options[:version] = spec.version
@@ -165,15 +168,14 @@ that is a dependency of an existing gem. You can use the
gems_to_uninstall = {}
deps.each do |dep|
- unless gems_to_uninstall[dep.name]
+ if original_gem_version[dep.name] == Gem::Requirement.default
+ next if gems_to_uninstall[dep.name]
gems_to_uninstall[dep.name] = true
-
- unless original_gem_version[dep.name] == Gem::Requirement.default
- options[:version] = dep.version
- end
-
- uninstall_gem(dep.name)
+ else
+ options[:version] = dep.version
end
+
+ uninstall_gem(dep.name)
end
end
@@ -181,12 +183,12 @@ that is a dependency of an existing gem. You can use the
uninstall(gem_name)
rescue Gem::GemNotInHomeException => e
spec = e.spec
- alert("In order to remove #{spec.name}, please execute:\n" +
+ alert("In order to remove #{spec.name}, please execute:\n" \
"\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}")
rescue Gem::UninstallError => e
spec = e.spec
- alert_error("Error: unable to successfully uninstall '#{spec.name}' which is " +
- "located at '#{spec.full_gem_path}'. This is most likely because" +
+ alert_error("Error: unable to successfully uninstall '#{spec.name}' which is " \
+ "located at '#{spec.full_gem_path}'. This is most likely because" \
"the current user does not have the appropriate permissions")
terminate_interaction 1
end
diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb
index 8d90d08eb4..c2fc720297 100644
--- a/lib/rubygems/commands/unpack_command.rb
+++ b/lib/rubygems/commands/unpack_command.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/security_option'
-require 'rubygems/remote_fetcher'
-require 'rubygems/package'
+
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../security_option"
+require_relative "../remote_fetcher"
+require_relative "../package"
# forward-declare
@@ -17,18 +18,18 @@ class Gem::Commands::UnpackCommand < Gem::Command
include Gem::SecurityOption
def initialize
- require 'fileutils'
+ require "fileutils"
- super 'unpack', 'Unpack an installed gem to the current directory',
- :version => Gem::Requirement.default,
- :target => Dir.pwd
+ super "unpack", "Unpack an installed gem to the current directory",
+ version: Gem::Requirement.default,
+ target: Dir.pwd
- add_option('--target=DIR',
- 'target directory for unpacking') do |value, options|
+ add_option("--target=DIR",
+ "target directory for unpacking") do |value, options|
options[:target] = value
end
- add_option('--spec', 'unpack the gem specification') do |value, options|
+ add_option("--spec", "unpack the gem specification") do |_value, options|
options[:spec] = true
end
@@ -95,19 +96,17 @@ command help for an example.
FileUtils.mkdir_p @options[:target] if @options[:target]
- destination = begin
- if @options[:target]
- File.join @options[:target], spec_file
- else
- spec_file
- end
+ destination = if @options[:target]
+ File.join @options[:target], spec_file
+ else
+ spec_file
end
- File.open destination, 'w' do |io|
+ File.open destination, "w" do |io|
io.write metadata
end
else
- basename = File.basename path, '.gem'
+ basename = File.basename path, ".gem"
target_dir = File.expand_path basename, options[:target]
package = Gem::Package.new path, security_policy
@@ -131,7 +130,7 @@ command help for an example.
return this_path if File.exist? this_path
end
- return nil
+ nil
end
##
@@ -144,24 +143,18 @@ command help for an example.
# get_path 'rake', '< 0.1' # nil
# get_path 'rak' # nil (exact name required)
#--
- # TODO: This should be refactored so that it's a general service. I don't
- # think any of our existing classes are the right place though. Just maybe
- # 'Cache'?
- #
- # TODO: It just uses Gem.dir for now. What's an easy way to get the list of
- # source directories?
def get_path(dependency)
- return dependency.name if dependency.name =~ /\.gem$/i
+ return dependency.name if /\.gem$/i.match?(dependency.name)
specs = dependency.matching_specs
- selected = specs.max_by {|s| s.version }
+ selected = specs.max_by(&:version)
return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless
selected
- return unless dependency.name =~ /^#{selected.name}$/i
+ return unless /^#{selected.name}$/i.match?(dependency.name)
# We expect to find (basename).gem in the 'cache' directory. Furthermore,
# the name match must be exact (ignoring case).
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index 91d93e398c..8e80d46856 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/command_manager'
-require 'rubygems/dependency_installer'
-require 'rubygems/install_update_options'
-require 'rubygems/local_remote_options'
-require 'rubygems/spec_fetcher'
-require 'rubygems/version_option'
-require 'rubygems/install_message' # must come before rdoc for messaging
-require 'rubygems/rdoc'
+
+require_relative "../command"
+require_relative "../command_manager"
+require_relative "../dependency_installer"
+require_relative "../install_update_options"
+require_relative "../local_remote_options"
+require_relative "../spec_fetcher"
+require_relative "../version_option"
+require_relative "../install_message" # must come before rdoc for messaging
+require_relative "../rdoc"
class Gem::Commands::UpdateCommand < Gem::Command
include Gem::InstallUpdateOptions
@@ -20,26 +21,26 @@ class Gem::Commands::UpdateCommand < Gem::Command
def initialize
options = {
- :force => false,
+ force: false,
}
options.merge!(install_update_options)
- super 'update', 'Update installed gems to the latest version', options
+ super "update", "Update installed gems to the latest version", options
add_install_update_options
- OptionParser.accept Gem::Version do |value|
+ Gem::OptionParser.accept Gem::Version do |value|
Gem::Version.new value
value
end
- add_option('--system [VERSION]', Gem::Version,
- 'Update the RubyGems system software') do |value, options|
- value = true unless value
+ add_option("--system [VERSION]", Gem::Version,
+ "Update the RubyGems system software") do |value, opts|
+ value ||= true
- options[:system] = value
+ opts[:system] = value
end
add_local_remote_options
@@ -56,7 +57,7 @@ class Gem::Commands::UpdateCommand < Gem::Command
def defaults_str # :nodoc:
"--no-force --install-dir #{Gem.dir}\n" +
- install_update_defaults_str
+ install_update_defaults_str
end
def description # :nodoc:
@@ -118,15 +119,19 @@ command to remove old versions.
updated = update_gems gems_to_update
- updated_names = updated.map {|spec| spec.name }
+ installed_names = highest_installed_gems.keys
+ updated_names = updated.map(&:name)
not_updated_names = options[:args].uniq - updated_names
+ not_installed_names = not_updated_names - installed_names
+ up_to_date_names = not_updated_names - not_installed_names
if updated.empty?
say "Nothing to update"
else
- say "Gems updated: #{updated_names.join(' ')}"
- say "Gems already up-to-date: #{not_updated_names.join(' ')}" unless not_updated_names.empty?
+ say "Gems updated: #{updated_names.join(" ")}"
end
+ say "Gems already up-to-date: #{up_to_date_names.join(" ")}" unless up_to_date_names.empty?
+ say "Gems not currently installed: #{not_installed_names.join(" ")}" unless not_installed_names.empty?
end
def fetch_remote_gems(spec) # :nodoc:
@@ -151,7 +156,7 @@ command to remove old versions.
Gem::Specification.dirs = Gem.user_dir if options[:user_install]
Gem::Specification.each do |spec|
- if hig[spec.name].nil? or hig[spec.name].version < spec.version
+ if hig[spec.name].nil? || hig[spec.name].version < spec.version
hig[spec.name] = spec
end
end
@@ -162,30 +167,28 @@ command to remove old versions.
def highest_remote_name_tuple(spec) # :nodoc:
spec_tuples = fetch_remote_gems spec
- matching_gems = spec_tuples.select do |g,_|
- g.name == spec.name and g.match_platform?
- end
-
- highest_remote_gem = matching_gems.max
-
- highest_remote_gem ||= [Gem::NameTuple.null]
+ highest_remote_gem = spec_tuples.max
+ return unless highest_remote_gem
highest_remote_gem.first
end
- def install_rubygems(version) # :nodoc:
+ def install_rubygems(spec) # :nodoc:
args = update_rubygems_arguments
+ version = spec.version
- update_dir = File.join Gem.dir, 'gems', "rubygems-update-#{version}"
+ update_dir = File.join spec.base_dir, "gems", "rubygems-update-#{version}"
Dir.chdir update_dir do
say "Installing RubyGems #{version}" unless options[:silent]
installed = preparing_gem_layout_for(version) do
- system Gem.ruby, '--disable-gems', 'setup.rb', *args
+ system Gem.ruby, "--disable-gems", "setup.rb", *args
end
- say "RubyGems system software updated" if installed unless options[:silent]
+ unless options[:silent]
+ say "RubyGems system software updated" if installed
+ end
end
end
@@ -194,18 +197,17 @@ command to remove old versions.
yield
else
require "tmpdir"
- tmpdir = Dir.mktmpdir
- FileUtils.mv Gem.plugindir, tmpdir
+ Dir.mktmpdir("gem_update") do |tmpdir|
+ FileUtils.mv Gem.plugindir, tmpdir
- status = yield
+ status = yield
- if status
- FileUtils.rm_rf tmpdir
- else
- FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir
- end
+ unless status
+ FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir
+ end
- status
+ status
+ end
end
end
@@ -213,32 +215,24 @@ command to remove old versions.
version = options[:system]
update_latest = version == true
- if update_latest
- version = Gem::Version.new Gem::VERSION
- requirement = Gem::Requirement.new ">= #{Gem::VERSION}"
- else
+ unless update_latest
version = Gem::Version.new version
requirement = Gem::Requirement.new version
+
+ return version, requirement
end
+ version = Gem::Version.new Gem::VERSION
+ requirement = Gem::Requirement.new ">= #{Gem::VERSION}"
+
rubygems_update = Gem::Specification.new
- rubygems_update.name = 'rubygems-update'
+ rubygems_update.name = "rubygems-update"
rubygems_update.version = version
- hig = {
- 'rubygems-update' => rubygems_update,
- }
-
- gems_to_update = which_to_update hig, options[:args], :system
- up_ver = gems_to_update.first.version
-
- target = if update_latest
- up_ver
- else
- version
- end
+ highest_remote_tup = highest_remote_name_tuple(rubygems_update)
+ target = highest_remote_tup ? highest_remote_tup.version : version
- return target, requirement
+ [target, requirement]
end
def update_gem(name, version = Gem::Requirement.default)
@@ -249,7 +243,7 @@ command to remove old versions.
@installer = Gem::DependencyInstaller.new update_options
- say "Updating #{name}" unless options[:system] && options[:silent]
+ say "Updating #{name}" unless options[:system]
begin
@installer.install name, Gem::Requirement.new(version)
rescue Gem::InstallError, Gem::DependencyError => e
@@ -286,40 +280,34 @@ command to remove old versions.
check_oldest_rubygems version
- update_gem 'rubygems-update', version
+ installed_gems = Gem::Specification.find_all_by_name "rubygems-update", requirement
+ installed_gems = update_gem("rubygems-update", requirement) if installed_gems.empty? || installed_gems.first.version != version
+ return if installed_gems.empty?
- installed_gems = Gem::Specification.find_all_by_name 'rubygems-update', requirement
- version = installed_gems.first.version
-
- install_rubygems version
+ install_rubygems installed_gems.first
end
def update_rubygems_arguments # :nodoc:
args = []
- args << '--silent' if options[:silent]
- args << '--prefix' << Gem.prefix if Gem.prefix
- args << '--no-document' unless options[:document].include?('rdoc') || options[:document].include?('ri')
- args << '--no-format-executable' if options[:no_format_executable]
- args << '--previous-version' << Gem::VERSION if
- options[:system] == true or
- Gem::Version.new(options[:system]) >= Gem::Version.new(2)
+ args << "--silent" if options[:silent]
+ args << "--prefix" << Gem.prefix if Gem.prefix
+ args << "--no-document" unless options[:document].include?("rdoc") || options[:document].include?("ri")
+ args << "--no-format-executable" if options[:no_format_executable]
+ args << "--previous-version" << Gem::VERSION
args
end
- def which_to_update(highest_installed_gems, gem_names, system = false)
+ def which_to_update(highest_installed_gems, gem_names)
result = []
- highest_installed_gems.each do |l_name, l_spec|
- next if not gem_names.empty? and
+ highest_installed_gems.each do |_l_name, l_spec|
+ next if !gem_names.empty? &&
gem_names.none? {|name| name == l_spec.name }
highest_remote_tup = highest_remote_name_tuple l_spec
- highest_remote_ver = highest_remote_tup.version
- highest_installed_ver = l_spec.version
+ next unless highest_remote_tup
- if system or (highest_installed_ver < highest_remote_ver)
- result << Gem::NameTuple.new(l_spec.name, [highest_installed_ver, highest_remote_ver].max, highest_remote_tup.platform)
- end
+ result << highest_remote_tup
end
result
@@ -335,18 +323,10 @@ command to remove old versions.
#
def oldest_supported_version
@oldest_supported_version ||=
- if Gem.ruby_version > Gem::Version.new("3.0.a")
- Gem::Version.new("3.2.3")
- elsif Gem.ruby_version > Gem::Version.new("2.7.a")
- Gem::Version.new("3.1.2")
- elsif Gem.ruby_version > Gem::Version.new("2.6.a")
- Gem::Version.new("3.0.1")
- elsif Gem.ruby_version > Gem::Version.new("2.5.a")
- Gem::Version.new("2.7.3")
- elsif Gem.ruby_version > Gem::Version.new("2.4.a")
- Gem::Version.new("2.6.8")
+ if Gem.ruby_version > Gem::Version.new("3.1.a")
+ Gem::Version.new("3.3.3")
else
- Gem::Version.new("2.5.2")
+ Gem::Version.new("3.2.3")
end
end
end
diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb
index d42ab18395..5ed4d9d142 100644
--- a/lib/rubygems/commands/which_command.rb
+++ b/lib/rubygems/commands/which_command.rb
@@ -1,17 +1,18 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::WhichCommand < Gem::Command
def initialize
- super 'which', 'Find the location of a library file you can require',
- :search_gems_first => false, :show_all => false
+ super "which", "Find the location of a library file you can require",
+ search_gems_first: false, show_all: false
- add_option '-a', '--[no-]all', 'show all matching files' do |show_all, options|
+ add_option "-a", "--[no-]all", "show all matching files" do |show_all, options|
options[:show_all] = show_all
end
- add_option '-g', '--[no-]gems-first',
- 'search gems before non-gems' do |gems_first, options|
+ add_option "-g", "--[no-]gems-first",
+ "search gems before non-gems" do |gems_first, options|
options[:search_gems_first] = gems_first
end
end
@@ -39,7 +40,7 @@ requiring to see why it does not behave as you expect.
found = true
options[:args].each do |arg|
- arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, '')
+ arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, "")
dirs = $LOAD_PATH
spec = Gem::Specification.find_by_path arg
@@ -71,7 +72,7 @@ requiring to see why it does not behave as you expect.
dirs.each do |dir|
Gem.suffixes.each do |ext|
full_path = File.join dir, "#{package_name}#{ext}"
- if File.exist? full_path and not File.directory? full_path
+ if File.exist?(full_path) && !File.directory?(full_path)
result << full_path
return result unless options[:show_all]
end
diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb
index a7930253d6..fbdc262549 100644
--- a/lib/rubygems/commands/yank_command.rb
+++ b/lib/rubygems/commands/yank_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
-require 'rubygems/gemcutter_utilities'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
+require_relative "../gemcutter_utilities"
class Gem::Commands::YankCommand < Gem::Command
include Gem::LocalRemoteOptions
@@ -28,15 +29,15 @@ data you will need to change them immediately and yank your gem.
end
def initialize
- super 'yank', 'Remove a pushed gem from the index'
+ super "yank", "Remove a pushed gem from the index"
add_version_option("remove")
add_platform_option("remove")
add_otp_option
- add_option('--host HOST',
- 'Yank from another gemcutter-compatible host',
- ' (e.g. https://rubygems.org)') do |value, options|
+ add_option("--host HOST",
+ "Yank from another gemcutter-compatible host",
+ " (e.g. https://rubygems.org)") do |value, options|
options[:host] = value
end
@@ -61,7 +62,7 @@ data you will need to change them immediately and yank your gem.
end
def yank_gem(version, platform)
- say "Yanking gem from #{self.host}..."
+ say "Yanking gem from #{host}..."
args = [:delete, version, platform, "api/v1/gems/yank"]
response = yank_api_request(*args)
@@ -76,10 +77,10 @@ data you will need to change them immediately and yank your gem.
request.add_field("Authorization", api_key)
data = {
- 'gem_name' => name,
- 'version' => version,
+ "gem_name" => name,
+ "version" => version,
}
- data['platform'] = platform if platform
+ data["platform"] = platform if platform
request.set_form_data data
end
@@ -88,7 +89,7 @@ data you will need to change them immediately and yank your gem.
def get_version_from_requirements(requirements)
requirements.requirements.first[1].version
- rescue
+ rescue StandardError
nil
end
diff --git a/lib/rubygems/compatibility.rb b/lib/rubygems/compatibility.rb
index f1d452ea04..0d9df56f8a 100644
--- a/lib/rubygems/compatibility.rb
+++ b/lib/rubygems/compatibility.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-# :stopdoc:
#--
# This file contains all sorts of little compatibility hacks that we've
@@ -8,10 +7,13 @@
#
# Ruby 1.9.x has introduced some things that are awkward, and we need to
# support them, so we define some constants to use later.
+#
+# TODO remove at RubyGems 4
#++
-# TODO remove at RubyGems 4
module Gem
+ # :stopdoc:
+
RubyGemsVersion = VERSION
deprecate_constant(:RubyGemsVersion)
@@ -24,17 +26,16 @@ module Gem
rubylibdir
].freeze
- unless defined?(ConfigMap)
+ if defined?(ConfigMap)
+ RbConfigPriorities.each do |key|
+ ConfigMap[key.to_sym] = RbConfig::CONFIG[key]
+ end
+ else
##
# Configuration settings from ::RbConfig
ConfigMap = Hash.new do |cm, key|
cm[key] = RbConfig::CONFIG[key.to_s]
end
deprecate_constant(:ConfigMap)
- else
- RbConfigPriorities.each do |key|
- ConfigMap[key.to_sym] = RbConfig::CONFIG[key]
- end
end
-
end
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index 9dc41a2995..7874ad0dc9 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/user_interaction'
-require 'rbconfig'
+require_relative "user_interaction"
+require "rbconfig"
##
# Gem::ConfigFile RubyGems options and gem command options from gemrc.
@@ -39,13 +40,15 @@ require 'rbconfig'
class Gem::ConfigFile
include Gem::UserInteraction
- DEFAULT_BACKTRACE = false
+ DEFAULT_BACKTRACE = true
DEFAULT_BULK_THRESHOLD = 1000
DEFAULT_VERBOSITY = true
DEFAULT_UPDATE_SOURCES = true
DEFAULT_CONCURRENT_DOWNLOADS = 8
DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365
DEFAULT_IPV4_FALLBACK_ENABLED = false
+ # TODO: Use false as default value for this option in RubyGems 4.0
+ DEFAULT_INSTALL_EXTENSION_IN_LIB = true
##
# For Ruby packagers to set configuration defaults. Set in
@@ -71,7 +74,7 @@ class Gem::ConfigFile
# :startdoc:
- SYSTEM_WIDE_CONFIG_FILE = File.join SYSTEM_CONFIG_PATH, 'gemrc'
+ SYSTEM_WIDE_CONFIG_FILE = File.join SYSTEM_CONFIG_PATH, "gemrc"
##
# List of arguments supplied to the config file object.
@@ -142,6 +145,11 @@ class Gem::ConfigFile
attr_accessor :cert_expiration_length_days
##
+ # Install extensions into lib as well as into the extension directory.
+
+ attr_accessor :install_extension_in_lib
+
+ ##
# == Experimental ==
# Fallback to IPv4 when IPv6 is not reachable or slow (default: false)
@@ -182,40 +190,54 @@ class Gem::ConfigFile
@update_sources = DEFAULT_UPDATE_SOURCES
@concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS
@cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS
- @ipv4_fallback_enabled = ENV['IPV4_FALLBACK_ENABLED'] == 'true' || DEFAULT_IPV4_FALLBACK_ENABLED
+ @install_extension_in_lib = DEFAULT_INSTALL_EXTENSION_IN_LIB
+ @ipv4_fallback_enabled = ENV["IPV4_FALLBACK_ENABLED"] == "true" || DEFAULT_IPV4_FALLBACK_ENABLED
operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS)
platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS)
system_config = load_file SYSTEM_WIDE_CONFIG_FILE
- user_config = load_file config_file_name.dup.tap(&Gem::UNTAINT)
+ user_config = load_file config_file_name
- environment_config = (ENV['GEMRC'] || '')
- .split(File::PATH_SEPARATOR).inject({}) do |result, file|
+ environment_config = (ENV["GEMRC"] || "").
+ split(File::PATH_SEPARATOR).inject({}) do |result, file|
result.merge load_file file
end
@hash = operating_system_config.merge platform_config
- unless args.index '--norc'
+ unless args.index "--norc"
@hash = @hash.merge system_config
@hash = @hash.merge user_config
@hash = @hash.merge environment_config
end
- # HACK these override command-line args, which is bad
+ @hash.transform_keys! do |k|
+ # gemhome and gempath are not working with symbol keys
+ if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days
+ install_extension_in_lib ipv4_fallback_enabled sources disable_default_gem_server
+ ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k)
+ k.to_sym
+ else
+ k
+ end
+ end
+
+ # HACK: these override command-line args, which is bad
@backtrace = @hash[:backtrace] if @hash.key? :backtrace
@bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold
- @home = @hash[:gemhome] if @hash.key? :gemhome
- @path = @hash[:gempath] if @hash.key? :gempath
- @update_sources = @hash[:update_sources] if @hash.key? :update_sources
@verbose = @hash[:verbose] if @hash.key? :verbose
- @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server
- @sources = @hash[:sources] if @hash.key? :sources
+ @update_sources = @hash[:update_sources] if @hash.key? :update_sources
+ # TODO: We should handle concurrent_downloads same as other options
@cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days
+ @install_extension_in_lib = @hash[:install_extension_in_lib] if @hash.key? :install_extension_in_lib
@ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled
- @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
- @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert
- @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert
+ @home = @hash[:gemhome] if @hash.key? :gemhome
+ @path = @hash[:gempath] if @hash.key? :gempath
+ @sources = @hash[:sources] if @hash.key? :sources
+ @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server
+ @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
+ @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert
+ @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert
@api_keys = nil
@rubygems_api_key = nil
@@ -240,9 +262,9 @@ class Gem::ConfigFile
return if Gem.win_platform? # windows doesn't write 0600 as 0600
return unless File.exist? credentials_path
- existing_permissions = File.stat(credentials_path).mode & 0777
+ existing_permissions = File.stat(credentials_path).mode & 0o777
- return if existing_permissions == 0600
+ return if existing_permissions == 0o600
alert_error <<-ERROR
Your gem push credentials file located at:
@@ -269,7 +291,7 @@ if you believe they were disclosed to a third party.
# Location of RubyGems.org credentials
def credentials_path
- credentials = File.join Gem.user_home, '.gem', 'credentials'
+ credentials = File.join Gem.user_home, ".gem", "credentials"
if File.exist? credentials
credentials
else
@@ -281,10 +303,10 @@ if you believe they were disclosed to a third party.
check_credentials_permissions
@api_keys = if File.exist? credentials_path
- load_file(credentials_path)
- else
- @hash
- end
+ load_file(credentials_path)
+ else
+ @hash
+ end
if @api_keys.key? :rubygems_api_key
@rubygems_api_key = @api_keys[:rubygems_api_key]
@@ -320,13 +342,12 @@ if you believe they were disclosed to a third party.
config = load_file(credentials_path).merge(host => api_key)
dirname = File.dirname credentials_path
- Dir.mkdir(dirname) unless File.exist? dirname
-
- Gem.load_yaml
+ require "fileutils"
+ FileUtils.mkdir_p(dirname)
- permissions = 0600 & (~File.umask)
- File.open(credentials_path, 'w', permissions) do |f|
- f.write config.to_yaml
+ permissions = 0o600 & (~File.umask)
+ File.open(credentials_path, "w", permissions) do |f|
+ f.write self.class.dump_with_rubygems_yaml(config)
end
load_api_keys # reload
@@ -342,20 +363,18 @@ if you believe they were disclosed to a third party.
end
def load_file(filename)
- Gem.load_yaml
-
yaml_errors = [ArgumentError]
- yaml_errors << Psych::SyntaxError if defined?(Psych::SyntaxError)
return {} unless filename && !filename.empty? && File.exist?(filename)
begin
- content = Gem::SafeYAML.load(File.read(filename))
- unless content.kind_of? Hash
+ config = self.class.load_with_rubygems_config_hash(File.read(filename))
+ if config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") }
warn "Failed to load #{filename} because it doesn't contain valid YAML hash"
return {}
+ else
+ return config
end
- return content
rescue *yaml_errors => e
warn "Failed to load #{filename}, #{e}"
rescue Errno::EACCES
@@ -367,7 +386,21 @@ if you believe they were disclosed to a third party.
# True if the backtrace option has been specified, or debug is on.
def backtrace
- @backtrace or $DEBUG
+ @backtrace || $DEBUG
+ end
+
+ # Check state file is writable. Creates empty file if not present to ensure we can write to it.
+ def state_file_writable?
+ if File.exist?(state_file_name)
+ File.writable?(state_file_name)
+ else
+ require "fileutils"
+ FileUtils.mkdir_p File.dirname(state_file_name)
+ File.open(state_file_name, "w") {}
+ true
+ end
+ rescue Errno::EACCES
+ false
end
# The name of the configuration file.
@@ -375,6 +408,25 @@ if you believe they were disclosed to a third party.
@config_file_name || Gem.config_file
end
+ # The name of the state file.
+ def state_file_name
+ Gem.state_file
+ end
+
+ # Reads time of last update check from state file
+ def last_update_check
+ if File.readable?(state_file_name)
+ File.read(state_file_name).to_i
+ else
+ 0
+ end
+ end
+
+ # Writes time of last update check to state file
+ def last_update_check=(timestamp)
+ File.write(state_file_name, timestamp.to_s) if state_file_writable?
+ end
+
# Delegates to @hash
def each(&block)
hash = @hash.dup
@@ -388,7 +440,7 @@ if you believe they were disclosed to a third party.
yield :backtrace, @backtrace
yield :bulk_threshold, @bulk_threshold
- yield 'config_file_name', @config_file_name if @config_file_name
+ yield "config_file_name", @config_file_name if @config_file_name
hash.each(&block)
end
@@ -404,7 +456,7 @@ if you believe they were disclosed to a third party.
when /^--debug$/ then
$DEBUG = true
- warn 'NOTE: Debugging mode prints all exceptions even when rescued'
+ warn "NOTE: Debugging mode prints all exceptions even when rescued"
else
@args << arg
end
@@ -433,6 +485,9 @@ if you believe they were disclosed to a third party.
yaml_hash[:concurrent_downloads] =
@hash.fetch(:concurrent_downloads, DEFAULT_CONCURRENT_DOWNLOADS)
+ yaml_hash[:install_extension_in_lib] =
+ @hash.fetch(:install_extension_in_lib, DEFAULT_INSTALL_EXTENSION_IN_LIB)
+
yaml_hash[:ssl_verify_mode] =
@hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
@@ -442,26 +497,25 @@ if you believe they were disclosed to a third party.
yaml_hash[:ssl_client_cert] =
@hash[:ssl_client_cert] if @hash.key? :ssl_client_cert
- keys = yaml_hash.keys.map {|key| key.to_s }
- keys << 'debug'
+ keys = yaml_hash.keys.map(&:to_s)
+ keys << "debug"
re = Regexp.union(*keys)
@hash.each do |key, value|
key = key.to_s
- next if key =~ re
+ next if key&.match?(re)
yaml_hash[key.to_s] = value
end
- yaml_hash.to_yaml
+ self.class.dump_with_rubygems_yaml(yaml_hash)
end
# Writes out this config file, replacing its source.
def write
- unless File.exist?(File.dirname(config_file_name))
- FileUtils.mkdir_p File.dirname(config_file_name)
- end
+ require "fileutils"
+ FileUtils.mkdir_p File.dirname(config_file_name)
- File.open config_file_name, 'w' do |io|
+ File.open config_file_name, "w" do |io|
io.write to_yaml
end
end
@@ -477,17 +531,68 @@ if you believe they were disclosed to a third party.
end
def ==(other) # :nodoc:
- self.class === other and
- @backtrace == other.backtrace and
- @bulk_threshold == other.bulk_threshold and
- @verbose == other.verbose and
- @update_sources == other.update_sources and
+ self.class === other &&
+ @backtrace == other.backtrace &&
+ @bulk_threshold == other.bulk_threshold &&
+ @verbose == other.verbose &&
+ @update_sources == other.update_sources &&
@hash == other.hash
end
attr_reader :hash
protected :hash
+ def self.dump_with_rubygems_yaml(content)
+ content.transform_keys! do |k|
+ k.is_a?(Symbol) ? ":#{k}" : k
+ end
+
+ require_relative "yaml_serializer"
+ Gem::YAMLSerializer.dump(content)
+ end
+
+ def self.load_with_rubygems_config_hash(yaml)
+ require_relative "yaml_serializer"
+
+ content = Gem::YAMLSerializer.load(yaml)
+
+ content.transform_keys! do |k|
+ if k.match?(/\A:(.*)\Z/)
+ k[1..-1].to_sym
+ elsif k.include?("__") || k.match?(%r{/\Z})
+ if k.is_a?(Symbol)
+ k.to_s.gsub(/__/,".").gsub(%r{/\Z}, "").to_sym
+ else
+ k.dup.gsub(/__/,".").gsub(%r{/\Z}, "")
+ end
+ else
+ k
+ end
+ end
+
+ content.transform_values! do |v|
+ if v.is_a?(String)
+ if v.match?(/\A:(.*)\Z/)
+ v[1..-1].to_sym
+ elsif v.match?(/\A[+-]?\d+\Z/)
+ v.to_i
+ elsif v.match?(/\Atrue|false\Z/)
+ v == "true"
+ elsif v.empty?
+ nil
+ else
+ v
+ end
+ elsif v.is_a?(Hash) && v.empty?
+ nil
+ else
+ v
+ end
+ end
+
+ content
+ end
+
private
def set_config_file_name(args)
@@ -500,7 +605,7 @@ if you believe they were disclosed to a third party.
need_config_file_name = false
elsif arg =~ /^--config-file=(.*)/
@config_file_name = $1
- elsif arg =~ /^--config-file$/
+ elsif /^--config-file$/.match?(arg)
need_config_file_name = true
end
end
diff --git a/lib/rubygems/core_ext/kernel_gem.rb b/lib/rubygems/core_ext/kernel_gem.rb
index e722225739..4e09b95c44 100644
--- a/lib/rubygems/core_ext/kernel_gem.rb
+++ b/lib/rubygems/core_ext/kernel_gem.rb
@@ -1,12 +1,6 @@
# frozen_string_literal: true
-##
-# RubyGems adds the #gem method to allow activation of specific gem versions
-# and overrides the #require method on Kernel to make gems appear as if they
-# live on the <code>$LOAD_PATH</code>. See the documentation of these methods
-# for further detail.
module Kernel
-
##
# Use Kernel#gem to activate a specific version of +gem_name+.
#
@@ -39,12 +33,12 @@ module Kernel
# GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb
def gem(gem_name, *requirements) # :doc:
- skip_list = (ENV['GEM_SKIP'] || "").split(/:/)
+ skip_list = (ENV["GEM_SKIP"] || "").split(/:/)
raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name
- if gem_name.kind_of? Gem::Dependency
+ if gem_name.is_a? Gem::Dependency
unless Gem::Deprecate.skip
- warn "#{Gem.location_of_caller.join ':'}:Warning: Kernel.gem no longer "\
+ warn "#{Gem.location_of_caller.join ":"}:Warning: Kernel.gem no longer "\
"accepts a Gem::Dependency object, please pass the name "\
"and requirements directly"
end
@@ -71,5 +65,4 @@ module Kernel
end
private :gem
-
end
diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb
index 4b867c55e9..073966b696 100644
--- a/lib/rubygems/core_ext/kernel_require.rb
+++ b/lib/rubygems/core_ext/kernel_require.rb
@@ -1,24 +1,24 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'monitor'
+require "monitor"
module Kernel
-
RUBYGEMS_ACTIVATION_MONITOR = Monitor.new # :nodoc:
# Make sure we have a reference to Ruby's original Kernel#require
unless defined?(gem_original_require)
- alias gem_original_require require
+ # :stopdoc:
+ alias_method :gem_original_require, :require
private :gem_original_require
+ # :startdoc:
end
- file = Gem::KERNEL_WARN_IGNORES_INTERNAL_ENTRIES ? "<internal:#{__FILE__}>" : __FILE__
- module_eval <<'RUBY', file, __LINE__ + 1
##
# When RubyGems is required, Kernel#require is replaced with our own which
# is capable of loading gems on demand.
@@ -33,143 +33,117 @@ module Kernel
# The normal <tt>require</tt> functionality of returning false if
# that file has already been loaded is preserved.
- def require(path)
- if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?)
- monitor_owned = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?
- end
- RUBYGEMS_ACTIVATION_MONITOR.enter
-
- path = path.to_path if path.respond_to? :to_path
-
- if spec = Gem.find_unresolved_default_spec(path)
- # Ensure -I beats a default gem
- resolved_path = begin
- rp = nil
- load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths
- Gem.suffixes.each do |s|
- $LOAD_PATH[0...load_path_check_index].each do |lp|
- safe_lp = lp.dup.tap(&Gem::UNTAINT)
- begin
- if File.symlink? safe_lp # for backward compatibility
+ def require(path) # :doc:
+ return gem_original_require(path) unless Gem.discover_gems_on_require
+
+ RUBYGEMS_ACTIVATION_MONITOR.synchronize do
+ path = File.path(path)
+
+ # If +path+ belongs to a default gem, we activate it and then go straight
+ # to normal require
+
+ if spec = Gem.find_default_spec(path)
+ name = spec.name
+
+ next if Gem.loaded_specs[name]
+
+ # Ensure -I beats a default gem
+ resolved_path = begin
+ rp = nil
+ load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths
+ Gem.suffixes.find do |s|
+ $LOAD_PATH[0...load_path_check_index].find do |lp|
+ if File.symlink? lp # for backward compatibility
next
end
- rescue SecurityError
- RUBYGEMS_ACTIVATION_MONITOR.exit
- raise
- end
- full_path = File.expand_path(File.join(safe_lp, "#{path}#{s}"))
- if File.file?(full_path)
- rp = full_path
- break
+ full_path = File.expand_path(File.join(lp, "#{path}#{s}"))
+ rp = full_path if File.file?(full_path)
end
end
- break if rp
+ rp
end
- rp
- end
- begin
- Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease)
- rescue Exception
- RUBYGEMS_ACTIVATION_MONITOR.exit
- raise
- end unless resolved_path
- end
+ Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless
+ resolved_path
- # If there are no unresolved deps, then we can use just try
- # normal require handle loading a gem from the rescue below.
+ next
+ end
- if Gem::Specification.unresolved_deps.empty?
- RUBYGEMS_ACTIVATION_MONITOR.exit
- return gem_original_require(path)
- end
+ # If there are no unresolved deps, then we can use just try
+ # normal require handle loading a gem from the rescue below.
- # If +path+ is for a gem that has already been loaded, don't
- # bother trying to find it in an unresolved gem, just go straight
- # to normal require.
- #--
- # TODO request access to the C implementation of this to speed up RubyGems
+ if Gem::Specification.unresolved_deps.empty?
+ next
+ end
- if Gem::Specification.find_active_stub_by_path(path)
- RUBYGEMS_ACTIVATION_MONITOR.exit
- return gem_original_require(path)
- end
+ # If +path+ is for a gem that has already been loaded, don't
+ # bother trying to find it in an unresolved gem, just go straight
+ # to normal require.
+ #--
+ # TODO request access to the C implementation of this to speed up RubyGems
- # Attempt to find +path+ in any unresolved gems...
-
- found_specs = Gem::Specification.find_in_unresolved path
-
- # If there are no directly unresolved gems, then try and find +path+
- # in any gems that are available via the currently unresolved gems.
- # For example, given:
- #
- # a => b => c => d
- #
- # If a and b are currently active with c being unresolved and d.rb is
- # requested, then find_in_unresolved_tree will find d.rb in d because
- # it's a dependency of c.
- #
- if found_specs.empty?
- found_specs = Gem::Specification.find_in_unresolved_tree path
-
- found_specs.each do |found_spec|
- found_spec.activate
+ if Gem::Specification.find_active_stub_by_path(path)
+ next
end
- # We found +path+ directly in an unresolved gem. Now we figure out, of
- # the possible found specs, which one we should activate.
- else
+ # Attempt to find +path+ in any unresolved gems...
- # Check that all the found specs are just different
- # versions of the same gem
- names = found_specs.map(&:name).uniq
+ found_specs = Gem::Specification.find_in_unresolved path
- if names.size > 1
- RUBYGEMS_ACTIVATION_MONITOR.exit
- raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}"
- end
+ # If there are no directly unresolved gems, then try and find +path+
+ # in any gems that are available via the currently unresolved gems.
+ # For example, given:
+ #
+ # a => b => c => d
+ #
+ # If a and b are currently active with c being unresolved and d.rb is
+ # requested, then find_in_unresolved_tree will find d.rb in d because
+ # it's a dependency of c.
+ #
+ if found_specs.empty?
+ found_specs = Gem::Specification.find_in_unresolved_tree path
- # Ok, now find a gem that has no conflicts, starting
- # at the highest version.
- valid = found_specs.find {|s| !s.has_conflicts? }
+ found_specs.each(&:activate)
- unless valid
- le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
- le.name = names.first
- RUBYGEMS_ACTIVATION_MONITOR.exit
- raise le
- end
+ # We found +path+ directly in an unresolved gem. Now we figure out, of
+ # the possible found specs, which one we should activate.
+ else
- valid.activate
- end
+ # Check that all the found specs are just different
+ # versions of the same gem
+ names = found_specs.map(&:name).uniq
- RUBYGEMS_ACTIVATION_MONITOR.exit
- return gem_original_require(path)
- rescue LoadError => load_error
- RUBYGEMS_ACTIVATION_MONITOR.enter
+ if names.size > 1
+ raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ", "}"
+ end
- begin
- if load_error.path == path and Gem.try_activate(path)
- require_again = true
+ # Ok, now find a gem that has no conflicts, starting
+ # at the highest version.
+ valid = found_specs.find {|s| !s.has_conflicts? }
+
+ unless valid
+ le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
+ le.name = names.first
+ raise le
+ end
+
+ valid.activate
end
- ensure
- RUBYGEMS_ACTIVATION_MONITOR.exit
end
- return gem_original_require(path) if require_again
+ begin
+ gem_original_require(path)
+ rescue LoadError => load_error
+ if load_error.path == path &&
+ RUBYGEMS_ACTIVATION_MONITOR.synchronize { Gem.try_activate(path) }
- raise load_error
- ensure
- if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?)
- if monitor_owned != (ow = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?)
- STDERR.puts [$$, Thread.current, $!, $!.backtrace].inspect if $!
- raise "CRITICAL: RUBYGEMS_ACTIVATION_MONITOR.owned?: before #{monitor_owned} -> after #{ow}"
+ return gem_original_require(path)
end
+
+ raise load_error
end
end
-RUBY
private :require
-
end
diff --git a/lib/rubygems/core_ext/kernel_warn.rb b/lib/rubygems/core_ext/kernel_warn.rb
index 3373cfdd3b..9dc9f2218c 100644
--- a/lib/rubygems/core_ext/kernel_warn.rb
+++ b/lib/rubygems/core_ext/kernel_warn.rb
@@ -1,54 +1,49 @@
# frozen_string_literal: true
-# `uplevel` keyword argument of Kernel#warn is available since ruby 2.5.
-if RUBY_VERSION >= "2.5" && !Gem::KERNEL_WARN_IGNORES_INTERNAL_ENTRIES
+module Kernel
+ rubygems_path = "#{__dir__}/" # Frames to be skipped start with this path.
- module Kernel
- rubygems_path = "#{__dir__}/" # Frames to be skipped start with this path.
+ original_warn = instance_method(:warn)
- original_warn = instance_method(:warn)
+ remove_method :warn
+ class << self
remove_method :warn
+ end
- class << self
- remove_method :warn
+ module_function define_method(:warn) {|*messages, **kw|
+ unless uplevel = kw[:uplevel]
+ if Gem.java_platform? && RUBY_VERSION < "3.1"
+ return original_warn.bind(self).call(*messages)
+ else
+ return original_warn.bind(self).call(*messages, **kw)
+ end
end
- module_function define_method(:warn) {|*messages, **kw|
- unless uplevel = kw[:uplevel]
- if Gem.java_platform?
- return original_warn.bind(self).call(*messages)
- else
- return original_warn.bind(self).call(*messages, **kw)
+ # Ensure `uplevel` fits a `long`
+ uplevel, = [uplevel].pack("l!").unpack("l!")
+
+ if uplevel >= 0
+ start = 0
+ while uplevel >= 0
+ loc, = caller_locations(start, 1)
+ unless loc
+ # No more backtrace
+ start += uplevel
+ break
end
- end
- # Ensure `uplevel` fits a `long`
- uplevel, = [uplevel].pack("l!").unpack("l!")
-
- if uplevel >= 0
- start = 0
- while uplevel >= 0
- loc, = caller_locations(start, 1)
- unless loc
- # No more backtrace
- start += uplevel
- break
- end
-
- start += 1
-
- if path = loc.path
- unless path.start_with?(rubygems_path) or path.start_with?('<internal:')
- # Non-rubygems frames
- uplevel -= 1
- end
- end
+ start += 1
+
+ next unless path = loc.path
+ unless path.start_with?(rubygems_path, "<internal:")
+ # Non-rubygems frames
+ uplevel -= 1
end
- kw[:uplevel] = start
end
+ kw[:uplevel] = start
+ end
- original_warn.bind(self).call(*messages, **kw)
- }
- end
+ original_warn.bind(self).call(*messages, **kw)
+ }
end
diff --git a/lib/rubygems/core_ext/tcpsocket_init.rb b/lib/rubygems/core_ext/tcpsocket_init.rb
index 3d9740c579..018c49dbeb 100644
--- a/lib/rubygems/core_ext/tcpsocket_init.rb
+++ b/lib/rubygems/core_ext/tcpsocket_init.rb
@@ -1,4 +1,6 @@
-require 'socket'
+# frozen_string_literal: true
+
+require "socket"
module CoreExtensions
module TCPSocketExt
@@ -11,13 +13,13 @@ module CoreExtensions
IPV4_DELAY_SECONDS = 0.1
def initialize(host, serv, *rest)
- mutex = Mutex.new
+ mutex = Thread::Mutex.new
addrs = []
threads = []
- cond_var = ConditionVariable.new
+ cond_var = Thread::ConditionVariable.new
Addrinfo.foreach(host, serv, nil, :STREAM) do |addr|
- Thread.report_on_exception = false if defined? Thread.report_on_exception = ()
+ Thread.report_on_exception = false
threads << Thread.new(addr) do
# give head start to ipv6 addresses
diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb
index e95bc06792..1bd208feb9 100644
--- a/lib/rubygems/defaults.rb
+++ b/lib/rubygems/defaults.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
+
module Gem
- DEFAULT_HOST = "https://rubygems.org".freeze
+ DEFAULT_HOST = "https://rubygems.org"
@post_install_hooks ||= []
@done_installing_hooks ||= []
@@ -20,10 +21,10 @@ module Gem
# specified in the environment
def self.default_spec_cache_dir
- default_spec_cache_dir = File.join Gem.user_home, '.gem', 'specs'
+ default_spec_cache_dir = File.join Gem.user_home, ".gem", "specs"
unless File.exist?(default_spec_cache_dir)
- default_spec_cache_dir = File.join Gem.data_home, 'gem', 'specs'
+ default_spec_cache_dir = File.join Gem.cache_home, "gem", "specs"
end
default_spec_cache_dir
@@ -34,21 +35,7 @@ module Gem
# specified in the environment
def self.default_dir
- path = if defined? RUBY_FRAMEWORK_VERSION
- [
- File.dirname(RbConfig::CONFIG['sitedir']),
- 'Gems',
- RbConfig::CONFIG['ruby_version'],
- ]
- else
- [
- RbConfig::CONFIG['rubylibprefix'],
- 'gems',
- RbConfig::CONFIG['ruby_version'],
- ]
- end
-
- @default_dir ||= File.join(*path)
+ @default_dir ||= File.join(RbConfig::CONFIG["rubylibprefix"], "gems", RbConfig::CONFIG["ruby_version"])
end
##
@@ -73,7 +60,7 @@ module Gem
# Path to specification files of default gems.
def self.default_specifications_dir
- File.join(Gem.default_dir, "specifications", "default")
+ @default_specifications_dir ||= File.join(Gem.default_dir, "specifications", "default")
end
##
@@ -93,9 +80,9 @@ module Gem
def self.find_home
Dir.home.dup
- rescue
+ rescue StandardError
if Gem.win_platform?
- File.expand_path File.join(ENV['HOMEDRIVE'] || ENV['SystemDrive'], '/')
+ File.expand_path File.join(ENV["HOMEDRIVE"] || ENV["SystemDrive"], "/")
else
File.expand_path "/"
end
@@ -107,7 +94,7 @@ module Gem
# The home directory for the user.
def self.user_home
- @user_home ||= find_home.tap(&Gem::UNTAINT)
+ @user_home ||= find_home
end
##
@@ -117,7 +104,7 @@ module Gem
gem_dir = File.join(Gem.user_home, ".gem")
gem_dir = File.join(Gem.data_home, "gem") unless File.exist?(gem_dir)
parts = [gem_dir, ruby_engine]
- parts << RbConfig::CONFIG['ruby_version'] unless RbConfig::CONFIG['ruby_version'].empty?
+ parts << RbConfig::CONFIG["ruby_version"] unless RbConfig::CONFIG["ruby_version"].empty?
File.join parts
end
@@ -125,14 +112,14 @@ module Gem
# The path to standard location of the user's configuration directory.
def self.config_home
- @config_home ||= (ENV["XDG_CONFIG_HOME"] || File.join(Gem.user_home, '.config'))
+ @config_home ||= ENV["XDG_CONFIG_HOME"] || File.join(Gem.user_home, ".config")
end
##
# Finds the user's config file
def self.find_config_file
- gemrc = File.join Gem.user_home, '.gemrc'
+ gemrc = File.join Gem.user_home, ".gemrc"
if File.exist? gemrc
gemrc
else
@@ -144,21 +131,35 @@ module Gem
# The path to standard location of the user's .gemrc file.
def self.config_file
- @config_file ||= find_config_file.tap(&Gem::UNTAINT)
+ @config_file ||= find_config_file
+ end
+
+ ##
+ # The path to standard location of the user's state file.
+
+ def self.state_file
+ @state_file ||= File.join(Gem.state_home, "gem", "last_update_check")
end
##
# The path to standard location of the user's cache directory.
def self.cache_home
- @cache_home ||= (ENV["XDG_CACHE_HOME"] || File.join(Gem.user_home, '.cache'))
+ @cache_home ||= ENV["XDG_CACHE_HOME"] || File.join(Gem.user_home, ".cache")
end
##
# The path to standard location of the user's data directory.
def self.data_home
- @data_home ||= (ENV["XDG_DATA_HOME"] || File.join(Gem.user_home, '.local', 'share'))
+ @data_home ||= ENV["XDG_DATA_HOME"] || File.join(Gem.user_home, ".local", "share")
+ end
+
+ ##
+ # The path to standard location of the user's state directory.
+
+ def self.state_home
+ @state_home ||= ENV["XDG_STATE_HOME"] || File.join(Gem.user_home, ".local", "state")
end
##
@@ -175,7 +176,7 @@ module Gem
path = []
path << user_dir if user_home && File.exist?(user_home)
path << default_dir
- path << vendor_dir if vendor_dir and File.directory? vendor_dir
+ path << vendor_dir if vendor_dir && File.directory?(vendor_dir)
path
end
@@ -183,9 +184,13 @@ module Gem
# Deduce Ruby's --program-prefix and --program-suffix from its install name
def self.default_exec_format
- exec_format = RbConfig::CONFIG['ruby_install_name'].sub('ruby', '%s') rescue '%s'
+ exec_format = begin
+ RbConfig::CONFIG["ruby_install_name"].sub("ruby", "%s")
+ rescue StandardError
+ "%s"
+ end
- unless exec_format =~ /%s/
+ unless exec_format.include?("%s")
raise Gem::Exception,
"[BUG] invalid exec_format #{exec_format.inspect}, no %s"
end
@@ -197,11 +202,7 @@ module Gem
# The default directory for binaries
def self.default_bindir
- if defined? RUBY_FRAMEWORK_VERSION # mac framework support
- '/usr/local/bin'
- else # generic install
- RbConfig::CONFIG['bindir']
- end
+ RbConfig::CONFIG["bindir"]
end
def self.ruby_engine
@@ -235,24 +236,36 @@ module Gem
end
##
+ # Enables automatic installation into user directory
+
+ def self.default_user_install # :nodoc:
+ if !ENV.key?("GEM_HOME") && (File.exist?(Gem.dir) && !File.writable?(Gem.dir))
+ Gem.ui.say "Defaulting to user installation because default installation directory (#{Gem.dir}) is not writable."
+ return true
+ end
+
+ false
+ end
+
+ ##
# Install extensions into lib as well as into the extension directory.
def self.install_extension_in_lib # :nodoc:
- true
+ Gem.configuration.install_extension_in_lib
end
##
# Directory where vendor gems are installed.
def self.vendor_dir # :nodoc:
- if vendor_dir = ENV['GEM_VENDOR']
+ if vendor_dir = ENV["GEM_VENDOR"]
return vendor_dir.dup
end
- return nil unless RbConfig::CONFIG.key? 'vendordir'
+ return nil unless RbConfig::CONFIG.key? "vendordir"
- File.join RbConfig::CONFIG['vendordir'], 'gems',
- RbConfig::CONFIG['ruby_version']
+ File.join RbConfig::CONFIG["vendordir"], "gems",
+ RbConfig::CONFIG["ruby_version"]
end
##
diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb
index 3721204ab2..d1bf074441 100644
--- a/lib/rubygems/dependency.rb
+++ b/lib/rubygems/dependency.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The Dependency class holds a Gem name and a Gem::Requirement.
@@ -45,10 +46,10 @@ class Gem::Dependency
end
type = Symbol === requirements.last ? requirements.pop : :runtime
- requirements = requirements.first if 1 == requirements.length # unpack
+ requirements = requirements.first if requirements.length == 1 # unpack
unless TYPES.include? type
- raise ArgumentError, "Valid types are #{TYPES.inspect}, " +
+ raise ArgumentError, "Valid types are #{TYPES.inspect}, " \
"not #{type.inspect}"
end
@@ -73,11 +74,9 @@ class Gem::Dependency
def inspect # :nodoc:
if prerelease?
- "<%s type=%p name=%p requirements=%p prerelease=ok>" %
- [self.class, self.type, self.name, requirement.to_s]
+ format("<%s type=%p name=%p requirements=%p prerelease=ok>", self.class, type, name, requirement.to_s)
else
- "<%s type=%p name=%p requirements=%p>" %
- [self.class, self.type, self.name, requirement.to_s]
+ format("<%s type=%p name=%p requirements=%p>", self.class, type, name, requirement.to_s)
end
end
@@ -97,14 +96,14 @@ class Gem::Dependency
end
def pretty_print(q) # :nodoc:
- q.group 1, 'Gem::Dependency.new(', ')' do
+ q.group 1, "Gem::Dependency.new(", ")" do
q.pp name
- q.text ','
+ q.text ","
q.breakable
q.pp requirement
- q.text ','
+ q.text ","
q.breakable
q.pp type
@@ -115,7 +114,7 @@ class Gem::Dependency
# What does this dependency require?
def requirement
- return @requirement if defined?(@requirement) and @requirement
+ return @requirement if defined?(@requirement) && @requirement
# @version_requirements and @version_requirement are legacy ivar
# names, and supported here because older gems need to keep
@@ -168,16 +167,16 @@ class Gem::Dependency
def ==(other) # :nodoc:
Gem::Dependency === other &&
- self.name == other.name &&
- self.type == other.type &&
- self.requirement == other.requirement
+ name == other.name &&
+ type == other.type &&
+ requirement == other.requirement
end
##
# Dependencies are ordered by name.
def <=>(other)
- self.name <=> other.name
+ name <=> other.name
end
##
@@ -197,14 +196,14 @@ class Gem::Dependency
reqs = other.requirement.requirements
return false unless reqs.length == 1
- return false unless reqs.first.first == '='
+ return false unless reqs.first.first == "="
version = reqs.first.last
requirement.satisfied_by? version
end
- alias === =~
+ alias_method :===, :=~
##
# :call-seq:
@@ -230,10 +229,10 @@ class Gem::Dependency
version = Gem::Version.new version
- return true if requirement.none? and not version.prerelease?
- return false if version.prerelease? and
- not allow_prerelease and
- not prerelease?
+ return true if requirement.none? && !version.prerelease?
+ return false if version.prerelease? &&
+ !allow_prerelease &&
+ !prerelease?
requirement.satisfied_by? version
end
@@ -262,7 +261,7 @@ class Gem::Dependency
end
default = Gem::Requirement.default
- self_req = self.requirement
+ self_req = requirement
other_req = other.requirement
return self.class.new name, self_req if other_req == default
@@ -277,7 +276,10 @@ class Gem::Dependency
requirement.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version)
end.map(&:to_spec)
- Gem::BundlerVersionFinder.filter!(matches) if filters_bundler?
+ if prioritizes_bundler?
+ require_relative "bundler_version_finder"
+ Gem::BundlerVersionFinder.prioritize!(matches)
+ end
if platform_only
matches.reject! do |spec|
@@ -295,8 +297,8 @@ class Gem::Dependency
@requirement.specific?
end
- def filters_bundler?
- name == "bundler".freeze && !specific?
+ def prioritizes_bundler?
+ name == "bundler" && !specific?
end
def to_specs
@@ -320,16 +322,16 @@ class Gem::Dependency
end
def to_spec
- matches = self.to_specs.compact
+ matches = to_specs.compact
- active = matches.find {|spec| spec.activated? }
+ active = matches.find(&:activated?)
return active if active
- return matches.first if prerelease?
-
- # Move prereleases to the end of the list for >= 0 requirements
- pre, matches = matches.partition {|spec| spec.version.prerelease? }
- matches += pre if requirement == Gem::Requirement.default
+ unless prerelease?
+ # Consider prereleases only as a fallback
+ pre, matches = matches.partition {|spec| spec.version.prerelease? }
+ matches = pre if matches.empty?
+ end
matches.first
end
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index 400a5de5cf..b119dca1cf 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
-require 'rubygems'
-require 'rubygems/dependency_list'
-require 'rubygems/package'
-require 'rubygems/installer'
-require 'rubygems/spec_fetcher'
-require 'rubygems/user_interaction'
-require 'rubygems/available_set'
-require 'rubygems/deprecate'
+
+require_relative "../rubygems"
+require_relative "dependency_list"
+require_relative "package"
+require_relative "installer"
+require_relative "spec_fetcher"
+require_relative "user_interaction"
+require_relative "available_set"
+require_relative "deprecate"
##
# Installs a gem along with all its dependencies from local and remote gems.
@@ -16,18 +17,18 @@ class Gem::DependencyInstaller
extend Gem::Deprecate
DEFAULT_OPTIONS = { # :nodoc:
- :env_shebang => false,
- :document => %w[ri],
- :domain => :both, # HACK dup
- :force => false,
- :format_executable => false, # HACK dup
- :ignore_dependencies => false,
- :prerelease => false,
- :security_policy => nil, # HACK NoSecurity requires OpenSSL. AlmostNo? Low?
- :wrappers => true,
- :build_args => nil,
- :build_docs_in_background => false,
- :install_as_default => false,
+ env_shebang: false,
+ document: %w[ri],
+ domain: :both, # HACK: dup
+ force: false,
+ format_executable: false, # HACK: dup
+ ignore_dependencies: false,
+ prerelease: false,
+ security_policy: nil, # HACK: NoSecurity requires OpenSSL. AlmostNo? Low?
+ wrappers: true,
+ build_args: nil,
+ build_docs_in_background: false,
+ install_as_default: false,
}.freeze
##
@@ -65,7 +66,7 @@ class Gem::DependencyInstaller
# :build_args:: See Gem::Installer::new
def initialize(options = {})
- @only_install_dir = !!options[:install_dir]
+ @only_install_dir = !options[:install_dir].nil?
@install_dir = options[:install_dir] || Gem.dir
@build_root = options[:build_root]
@@ -109,7 +110,7 @@ class Gem::DependencyInstaller
# gems should be considered.
def consider_local?
- @domain == :both or @domain == :local
+ @domain == :both || @domain == :local
end
##
@@ -117,7 +118,7 @@ class Gem::DependencyInstaller
# gems should be considered.
def consider_remote?
- @domain == :both or @domain == :remote
+ @domain == :both || @domain == :remote
end
##
@@ -162,13 +163,11 @@ class Gem::DependencyInstaller
specs = []
tuples.each do |tup, source|
- begin
- spec = source.fetch_spec(tup)
- rescue Gem::RemoteFetcher::FetchError => e
- errors << Gem::SourceFetchProblem.new(source, e)
- else
- specs << [spec, source]
- end
+ spec = source.fetch_spec(tup)
+ rescue Gem::RemoteFetcher::FetchError => e
+ errors << Gem::SourceFetchProblem.new(source, e)
+ else
+ specs << [spec, source]
end
if @errors
@@ -178,7 +177,6 @@ class Gem::DependencyInstaller
end
set << specs
-
rescue Gem::RemoteFetcher::FetchError => e
# FIX if there is a problem talking to the network, we either need to always tell
# the user (no really_verbose) or fail hard, not silently tell them that we just
@@ -197,7 +195,7 @@ class Gem::DependencyInstaller
def in_background(what) # :nodoc:
fork_happened = false
- if @build_docs_in_background and Process.respond_to?(:fork)
+ if @build_docs_in_background && Process.respond_to?(:fork)
begin
Process.fork do
yield
@@ -230,22 +228,22 @@ class Gem::DependencyInstaller
@installed_gems = []
options = {
- :bin_dir => @bin_dir,
- :build_args => @build_args,
- :document => @document,
- :env_shebang => @env_shebang,
- :force => @force,
- :format_executable => @format_executable,
- :ignore_dependencies => @ignore_dependencies,
- :prerelease => @prerelease,
- :security_policy => @security_policy,
- :user_install => @user_install,
- :wrappers => @wrappers,
- :build_root => @build_root,
- :install_as_default => @install_as_default,
- :dir_mode => @dir_mode,
- :data_mode => @data_mode,
- :prog_mode => @prog_mode,
+ bin_dir: @bin_dir,
+ build_args: @build_args,
+ document: @document,
+ env_shebang: @env_shebang,
+ force: @force,
+ format_executable: @format_executable,
+ ignore_dependencies: @ignore_dependencies,
+ prerelease: @prerelease,
+ security_policy: @security_policy,
+ user_install: @user_install,
+ wrappers: @wrappers,
+ build_root: @build_root,
+ install_as_default: @install_as_default,
+ dir_mode: @dir_mode,
+ data_mode: @data_mode,
+ prog_mode: @prog_mode,
}
options[:install_dir] = @install_dir if @only_install_dir
@@ -268,7 +266,7 @@ class Gem::DependencyInstaller
end
def install_development_deps # :nodoc:
- if @development and @dev_shallow
+ if @development && @dev_shallow
:shallow
elsif @development
:all
@@ -289,17 +287,15 @@ class Gem::DependencyInstaller
installer_set.force = @force
if consider_local?
- if dep_or_name =~ /\.gem$/ and File.file? dep_or_name
+ if dep_or_name =~ /\.gem$/ && File.file?(dep_or_name)
src = Gem::Source::SpecificFile.new dep_or_name
installer_set.add_local dep_or_name, src.spec, src
version = src.spec.version if version == Gem::Requirement.default
- elsif dep_or_name =~ /\.gem$/
+ elsif dep_or_name =~ /\.gem$/ # rubocop:disable Performance/RegexpMatch
Dir[dep_or_name].each do |name|
- begin
- src = Gem::Source::SpecificFile.new name
- installer_set.add_local dep_or_name, src.spec, src
- rescue Gem::Package::FormatError
- end
+ src = Gem::Source::SpecificFile.new name
+ installer_set.add_local dep_or_name, src.spec, src
+ rescue Gem::Package::FormatError
end
# else This is a dependency. InstallerSet handles this case
end
diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb
index bcf436cd03..ad5e59e8c1 100644
--- a/lib/rubygems/dependency_list.rb
+++ b/lib/rubygems/dependency_list.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'tsort'
-require 'rubygems/deprecate'
+require_relative "vendored_tsort"
+require_relative "deprecate"
##
# Gem::DependencyList is used for installing and uninstalling gems in the
@@ -20,7 +21,7 @@ class Gem::DependencyList
attr_reader :specs
include Enumerable
- include TSort
+ include Gem::TSort
##
# Allows enabling/disabling use of development dependencies
@@ -104,7 +105,7 @@ class Gem::DependencyList
end
def inspect # :nodoc:
- "%s %p>" % [super[0..-2], map {|s| s.full_name }]
+ format("%s %p>", super[0..-2], map(&:full_name))
end
##
@@ -119,11 +120,11 @@ class Gem::DependencyList
each do |spec|
spec.runtime_dependencies.each do |dep|
inst = Gem::Specification.any? do |installed_spec|
- dep.name == installed_spec.name and
- dep.requirement.satisfied_by? installed_spec.version
+ dep.name == installed_spec.name &&
+ dep.requirement.satisfied_by?(installed_spec.version)
end
- unless inst or @specs.find {|s| s.satisfies_requirement? dep }
+ unless inst || @specs.find {|s| s.satisfies_requirement? dep }
unsatisfied[spec.name] << dep
return unsatisfied if quick
end
@@ -175,7 +176,7 @@ class Gem::DependencyList
def remove_specs_unsatisfied_by(dependencies)
specs.reject! do |spec|
dep = dependencies[spec.name]
- dep and not dep.requirement.satisfied_by? spec.version
+ dep && !dep.requirement.satisfied_by?(spec.version)
end
end
diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb
index 8c822cda95..58a6c5b7dc 100644
--- a/lib/rubygems/deprecate.rb
+++ b/lib/rubygems/deprecate.rb
@@ -1,28 +1,75 @@
# frozen_string_literal: true
+
##
-# Provides a single method +deprecate+ to be used to declare when
-# something is going away.
+# Provides 3 methods for declaring when something is going away.
+#
+# +deprecate(name, repl, year, month)+:
+# Indicate something may be removed on/after a certain date.
+#
+# +rubygems_deprecate(name, replacement=:none)+:
+# Indicate something will be removed in the next major RubyGems version,
+# and (optionally) a replacement for it.
+#
+# +rubygems_deprecate_command+:
+# Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be
+# removed in the next RubyGems version.
+#
+# Also provides +skip_during+ for temporarily turning off deprecation warnings.
+# This is intended to be used in the test suite, so deprecation warnings
+# don't cause test failures if you need to make sure stderr is otherwise empty.
+#
+#
+# Example usage of +deprecate+ and +rubygems_deprecate+:
#
# class Legacy
-# def self.klass_method
+# def self.some_class_method
+# # ...
+# end
+#
+# def some_instance_method
# # ...
# end
#
-# def instance_method
+# def some_old_method
# # ...
# end
#
# extend Gem::Deprecate
-# deprecate :instance_method, "X.z", 2011, 4
+# deprecate :some_instance_method, "X.z", 2011, 4
+# rubygems_deprecate :some_old_method, "Modern#some_new_method"
#
# class << self
# extend Gem::Deprecate
-# deprecate :klass_method, :none, 2011, 4
+# deprecate :some_class_method, :none, 2011, 4
+# end
+# end
+#
+#
+# Example usage of +rubygems_deprecate_command+:
+#
+# class Gem::Commands::QueryCommand < Gem::Command
+# extend Gem::Deprecate
+# rubygems_deprecate_command
+#
+# # ...
+# end
+#
+#
+# Example usage of +skip_during+:
+#
+# class TestSomething < Gem::Testcase
+# def test_some_thing_with_deprecations
+# Gem::Deprecate.skip_during do
+# actual_stdout, actual_stderr = capture_output do
+# Gem.something_deprecated
+# end
+# assert_empty actual_stdout
+# assert_equal(expected, actual_stderr)
+# end
# end
# end
module Gem::Deprecate
-
def self.skip # :nodoc:
@skip ||= false
end
@@ -35,7 +82,8 @@ module Gem::Deprecate
# Temporarily turn off warnings. Intended for tests only.
def skip_during
- Gem::Deprecate.skip, original = true, Gem::Deprecate.skip
+ original = Gem::Deprecate.skip
+ Gem::Deprecate.skip = true
yield
ensure
Gem::Deprecate.skip = original
@@ -56,12 +104,13 @@ module Gem::Deprecate
old = "_deprecated_#{name}"
alias_method old, name
define_method name do |*args, &block|
- klass = self.kind_of? Module
+ klass = is_a? Module
target = klass ? "#{self}." : "#{self.class}#"
- msg = [ "NOTE: #{target}#{name} is deprecated",
- repl == :none ? " with no replacement" : "; use #{repl} instead",
- ". It will be removed on or after %4d-%02d." % [year, month],
- "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}",
+ msg = [
+ "NOTE: #{target}#{name} is deprecated",
+ repl == :none ? " with no replacement" : "; use #{repl} instead",
+ format(". It will be removed on or after %4d-%02d.", year, month),
+ "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}",
]
warn "#{msg.join}." unless Gem::Deprecate.skip
send old, *args, &block
@@ -81,12 +130,13 @@ module Gem::Deprecate
old = "_deprecated_#{name}"
alias_method old, name
define_method name do |*args, &block|
- klass = self.kind_of? Module
+ klass = is_a? Module
target = klass ? "#{self}." : "#{self.class}#"
- msg = [ "NOTE: #{target}#{name} is deprecated",
- replacement == :none ? " with no replacement" : "; use #{replacement} instead",
- ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}",
- "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}",
+ msg = [
+ "NOTE: #{target}#{name} is deprecated",
+ replacement == :none ? " with no replacement" : "; use #{replacement} instead",
+ ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}",
+ "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}",
]
warn "#{msg.join}." unless Gem::Deprecate.skip
send old, *args, &block
@@ -96,22 +146,22 @@ module Gem::Deprecate
end
# Deprecation method to deprecate Rubygems commands
- def rubygems_deprecate_command
+ def rubygems_deprecate_command(version = Gem::Deprecate.next_rubygems_major_version)
class_eval do
define_method "deprecated?" do
true
end
define_method "deprecation_warning" do
- msg = [ "#{self.command} command is deprecated",
- ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}.\n",
+ msg = [
+ "#{command} command is deprecated",
+ ". It will be removed in Rubygems #{version}.\n",
]
- alert_warning "#{msg.join}" unless Gem::Deprecate.skip
+ alert_warning msg.join.to_s unless Gem::Deprecate.skip
end
end
end
module_function :rubygems_deprecate, :rubygems_deprecate_command, :skip_during
-
end
diff --git a/lib/rubygems/doctor.rb b/lib/rubygems/doctor.rb
index ef31aeddd0..56b7c081eb 100644
--- a/lib/rubygems/doctor.rb
+++ b/lib/rubygems/doctor.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
-require 'rubygems'
-require 'rubygems/user_interaction'
+
+require_relative "../rubygems"
+require_relative "user_interaction"
##
# Cleans up after a partially-failed uninstall or for an invalid
@@ -19,20 +20,20 @@ class Gem::Doctor
# subdirectory.
REPOSITORY_EXTENSION_MAP = [ # :nodoc:
- ['specifications', '.gemspec'],
- ['build_info', '.info'],
- ['cache', '.gem'],
- ['doc', ''],
- ['extensions', ''],
- ['gems', ''],
- ['plugins', ''],
+ ["specifications", ".gemspec"],
+ ["build_info", ".info"],
+ ["cache", ".gem"],
+ ["doc", ""],
+ ["extensions", ""],
+ ["gems", ""],
+ ["plugins", ""],
].freeze
missing =
Gem::REPOSITORY_SUBDIRECTORIES.sort -
- REPOSITORY_EXTENSION_MAP.map {|(k,_)| k }.sort
+ REPOSITORY_EXTENSION_MAP.map {|(k,_)| k }.sort
- raise "Update REPOSITORY_EXTENSION_MAP, missing: #{missing.join ', '}" unless
+ raise "Update REPOSITORY_EXTENSION_MAP, missing: #{missing.join ", "}" unless
missing.empty?
##
@@ -52,14 +53,14 @@ class Gem::Doctor
# Specs installed in this gem repository
def installed_specs # :nodoc:
- @installed_specs ||= Gem::Specification.map {|s| s.full_name }
+ @installed_specs ||= Gem::Specification.map(&:full_name)
end
##
# Are we doctoring a gem repository?
def gem_repository?
- not installed_specs.empty?
+ !installed_specs.empty?
end
##
@@ -74,8 +75,8 @@ class Gem::Doctor
Gem.use_paths @gem_repository.to_s
unless gem_repository?
- say 'This directory does not appear to be a RubyGems repository, ' +
- 'skipping'
+ say "This directory does not appear to be a RubyGems repository, " \
+ "skipping"
say
return
end
@@ -103,25 +104,25 @@ class Gem::Doctor
directory = File.join(@gem_repository, sub_directory)
Dir.entries(directory).sort.each do |ent|
- next if ent == "." || ent == ".."
+ next if [".", ".."].include?(ent)
child = File.join(directory, ent)
next unless File.exist?(child)
basename = File.basename(child, extension)
next if installed_specs.include? basename
- next if /^rubygems-\d/ =~ basename
- next if 'specifications' == sub_directory and 'default' == basename
- next if 'plugins' == sub_directory and Gem.plugin_suffix_regexp =~ basename
+ next if /^rubygems-\d/.match?(basename)
+ next if sub_directory == "specifications" && basename == "default"
+ next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ (basename)
- type = File.directory?(child) ? 'directory' : 'file'
+ type = File.directory?(child) ? "directory" : "file"
action = if @dry_run
- 'Extra'
- else
- FileUtils.rm_r(child)
- 'Removed'
- end
+ "Extra"
+ else
+ FileUtils.rm_r(child)
+ "Removed"
+ end
say "#{action} #{type} #{sub_directory}/#{File.basename(child)}"
end
diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb
index abee20651e..be6c34dc85 100644
--- a/lib/rubygems/errors.rb
+++ b/lib/rubygems/errors.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
#--
# This file contains all the various exceptions and other errors that are used
# inside of RubyGems.
@@ -59,11 +60,8 @@ module Gem
private
def build_message
- if name == "bundler" && message = Gem::BundlerVersionFinder.missing_version_message
- return message
- end
names = specs.map(&:full_name)
- "Could not find '#{name}' (#{requirement}) - did find: [#{names.join ','}]\n"
+ "Could not find '#{name}' (#{requirement}) - did find: [#{names.join ","}]\n"
end
end
@@ -136,11 +134,7 @@ module Gem
##
# A wordy description of the error.
def wordy
- "Found %s (%s), but was for platform%s %s" %
- [@name,
- @version,
- @platforms.size == 1 ? '' : 's',
- @platforms.join(' ,')]
+ format("Found %s (%s), but was for platform%s %s", @name, @version, @platforms.size == 1 ? "" : "s", @platforms.join(" ,"))
end
end
@@ -171,13 +165,12 @@ module Gem
# An English description of the error.
def wordy
- @source.uri.password = 'REDACTED' unless @source.uri.password.nil?
- "Unable to download data from #{@source.uri} - #{@error.message}"
+ "Unable to download data from #{Gem::Uri.redact(@source.uri)} - #{@error.message}"
end
##
# The "exception" alias allows you to call raise on a SourceFetchProblem.
- alias exception error
+ alias_method :exception, :error
end
end
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index 55755ddfba..0308b4687f 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rubygems/deprecate'
-require 'rubygems/unknown_command_spell_checker'
+require_relative "deprecate"
+require_relative "unknown_command_spell_checker"
##
# Base exception class for RubyGems. All exception raised by RubyGems are a
@@ -24,10 +24,14 @@ class Gem::UnknownCommandError < Gem::Exception
return if defined?(@attached)
if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable)
- DidYouMean::SPELL_CHECKERS['Gem::UnknownCommandError'] =
- Gem::UnknownCommandSpellChecker
+ if DidYouMean.respond_to?(:correct_error)
+ DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker)
+ else
+ DidYouMean::SPELL_CHECKERS["Gem::UnknownCommandError"] =
+ Gem::UnknownCommandSpellChecker
- prepend DidYouMean::Correctable
+ prepend DidYouMean::Correctable
+ end
end
@attached = true
@@ -150,7 +154,7 @@ class Gem::ImpossibleDependenciesError < Gem::Exception
def build_message # :nodoc:
requester = @request.requester
- requester = requester ? requester.spec.full_name : 'The user'
+ requester = requester ? requester.spec.full_name : "The user"
dependency = @request.dependency
message = "#{requester} requires #{dependency} but it conflicted:\n".dup
@@ -168,6 +172,7 @@ class Gem::ImpossibleDependenciesError < Gem::Exception
end
class Gem::InstallError < Gem::Exception; end
+
class Gem::RuntimeRequirementNotMetError < Gem::InstallError
attr_accessor :suggestion
def message
@@ -210,6 +215,16 @@ class Gem::RubyVersionMismatch < Gem::Exception; end
class Gem::VerificationError < Gem::Exception; end
##
+# Raised by Gem::WebauthnListener when an error occurs during security
+# device verification.
+
+class Gem::WebauthnVerificationError < Gem::Exception
+ def initialize(message)
+ super "Security device verification failed: #{message}"
+ end
+end
+
+##
# Raised to indicate that a system exit should occur with the specified
# exit_code
@@ -217,15 +232,13 @@ class Gem::SystemExitException < SystemExit
##
# The exit code for the process
- attr_accessor :exit_code
+ alias_method :exit_code, :status
##
# Creates a new SystemExitException with the given +exit_code+
def initialize(exit_code)
- @exit_code = exit_code
-
- super "Exiting RubyGems with exit_code #{exit_code}"
+ super exit_code, "Exiting RubyGems with exit_code #{exit_code}"
end
end
@@ -250,9 +263,9 @@ class Gem::UnsatisfiableDependencyError < Gem::DependencyError
# Gem::Resolver::DependencyRequest +dep+
def initialize(dep, platform_mismatch=nil)
- if platform_mismatch and !platform_mismatch.empty?
+ if platform_mismatch && !platform_mismatch.empty?
plats = platform_mismatch.map {|x| x.platform.to_s }.sort.uniq
- super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(', ')}"
+ super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(", ")}"
else
if dep.explicit?
super "Unable to resolve dependency: user requested '#{dep}'"
@@ -279,8 +292,3 @@ class Gem::UnsatisfiableDependencyError < Gem::DependencyError
@dependency.requirement
end
end
-
-##
-# Backwards compatible typo'd exception class for early RubyGems 2.0.x
-
-Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError # :nodoc:
diff --git a/lib/rubygems/ext.rb b/lib/rubygems/ext.rb
index bdd5bd9d82..b5ca126a08 100644
--- a/lib/rubygems/ext.rb
+++ b/lib/rubygems/ext.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
@@ -10,9 +11,10 @@
module Gem::Ext; end
-require_relative 'ext/build_error'
-require_relative 'ext/builder'
-require_relative 'ext/configure_builder'
-require_relative 'ext/ext_conf_builder'
-require_relative 'ext/rake_builder'
-require_relative 'ext/cmake_builder'
+require_relative "ext/build_error"
+require_relative "ext/builder"
+require_relative "ext/configure_builder"
+require_relative "ext/ext_conf_builder"
+require_relative "ext/rake_builder"
+require_relative "ext/cmake_builder"
+require_relative "ext/cargo_builder"
diff --git a/lib/rubygems/ext/build_error.rb b/lib/rubygems/ext/build_error.rb
index 8ef57ed91a..0329c1eec3 100644
--- a/lib/rubygems/ext/build_error.rb
+++ b/lib/rubygems/ext/build_error.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
+
##
# Raised when there is an error while building extensions.
-require_relative '../exceptions'
+require_relative "../exceptions"
class Gem::Ext::BuildError < Gem::InstallError
end
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index 14d5dde413..be1ba3031c 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -1,11 +1,13 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require_relative '../user_interaction'
+require_relative "../user_interaction"
+require_relative "../shellwords"
class Gem::Ext::Builder
include Gem::UserInteraction
@@ -17,63 +19,93 @@ class Gem::Ext::Builder
$1.downcase
end
- def self.make(dest_path, results, make_dir = Dir.pwd)
- unless File.exist? File.join(make_dir, 'Makefile')
- raise Gem::InstallError, 'Makefile not found'
+ def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"])
+ unless File.exist? File.join(make_dir, "Makefile")
+ raise Gem::InstallError, "Makefile not found"
end
# try to find make program from Ruby configure arguments first
- RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
- make_program = ENV['MAKE'] || ENV['make'] || $1
- unless make_program
- make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
- end
- make_program = Shellwords.split(make_program)
+ RbConfig::CONFIG["configure_args"] =~ /with-make-prog\=(\w+)/
+ make_program_name = ENV["MAKE"] || ENV["make"] || $1
+ make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make"
+ make_program = Shellwords.split(make_program_name)
+
+ # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform.
+ destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : ""
- destdir = 'DESTDIR=%s' % ENV['DESTDIR']
+ env = [destdir]
+
+ if sitedir
+ env << format("sitearchdir=%s", sitedir)
+ env << format("sitelibdir=%s", sitedir)
+ end
- ['clean', '', 'install'].each do |target|
+ targets.each do |target|
# Pass DESTDIR via command line to override what's in MAKEFLAGS
cmd = [
*make_program,
- destdir,
+ *env,
target,
].reject(&:empty?)
begin
run(cmd, results, "make #{target}".rstrip, make_dir)
rescue Gem::InstallError
- raise unless target == 'clean' # ignore clean failure
+ raise unless target == "clean" # ignore clean failure
end
end
end
- def self.run(command, results, command_name = nil, dir = Dir.pwd)
+ def self.ruby
+ # Gem.ruby is quoted if it contains whitespace
+ cmd = Shellwords.split(Gem.ruby)
+
+ # This load_path is only needed when running rubygems test without a proper installation.
+ # Prepending it in a normal installation will cause problem with order of $LOAD_PATH.
+ # Therefore only add load_path if it is not present in the default $LOAD_PATH.
+ load_path = File.expand_path("../..", __dir__)
+ case load_path
+ when RbConfig::CONFIG["sitelibdir"], RbConfig::CONFIG["vendorlibdir"], RbConfig::CONFIG["rubylibdir"]
+ cmd
+ else
+ cmd << "-I#{load_path}"
+ end
+ end
+
+ def self.run(command, results, command_name = nil, dir = Dir.pwd, env = {})
verbose = Gem.configuration.really_verbose
begin
- rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil
+ rubygems_gemdeps = ENV["RUBYGEMS_GEMDEPS"]
+ ENV["RUBYGEMS_GEMDEPS"] = nil
if verbose
puts("current directory: #{dir}")
p(command)
end
results << "current directory: #{dir}"
- results << command.shelljoin
+ results << Shellwords.join(command)
require "open3"
# Set $SOURCE_DATE_EPOCH for the subprocess.
- env = {'SOURCE_DATE_EPOCH' => Gem.source_date_epoch_string}
+ build_env = { "SOURCE_DATE_EPOCH" => Gem.source_date_epoch_string }.merge(env)
output, status = begin
- Open3.capture2e(env, *command, :chdir => dir)
- rescue => error
+ Open3.popen2e(build_env, *command, chdir: dir) do |_stdin, stdouterr, wait_thread|
+ output = String.new
+ while line = stdouterr.gets
+ output << line
+ if verbose
+ print line
+ end
+ end
+ [output, wait_thread.value]
+ end
+ rescue StandardError => error
raise Gem::InstallError, "#{command_name || class_name} failed#{error.message}"
end
- if verbose
- puts output
- else
+ unless verbose
results << output
end
ensure
- ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps
+ ENV["RUBYGEMS_GEMDEPS"] = rubygems_gemdeps
end
unless status.success?
@@ -121,6 +153,8 @@ class Gem::Ext::Builder
Gem::Ext::RakeBuilder
when /CMakeLists.txt/ then
Gem::Ext::CmakeBuilder
+ when /Cargo.toml/ then
+ Gem::Ext::CargoBuilder.new
else
build_error("No builder for extension '#{extension}'")
end
@@ -162,7 +196,7 @@ EOF
verbose { results.join("\n") }
write_gem_make_out results.join "\n"
- rescue => e
+ rescue StandardError => e
results << e.message
build_error(results.join("\n"), $@)
end
@@ -178,7 +212,7 @@ EOF
if @build_args.empty?
say "Building native extensions. This could take a while..."
else
- say "Building native extensions with: '#{@build_args.join ' '}'"
+ say "Building native extensions with: '#{@build_args.join " "}'"
say "This could take a while..."
end
@@ -200,11 +234,11 @@ EOF
# Writes +output+ to gem_make.out in the extension install directory.
def write_gem_make_out(output) # :nodoc:
- destination = File.join @spec.extension_dir, 'gem_make.out'
+ destination = File.join @spec.extension_dir, "gem_make.out"
FileUtils.mkdir_p @spec.extension_dir
- File.open destination, 'wb' do |io|
+ File.open destination, "wb" do |io|
io.puts output
end
diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb
new file mode 100644
index 0000000000..86a0e73f28
--- /dev/null
+++ b/lib/rubygems/ext/cargo_builder.rb
@@ -0,0 +1,359 @@
+# frozen_string_literal: true
+
+require_relative "../shellwords"
+
+# This class is used by rubygems to build Rust extensions. It is a thin-wrapper
+# over the `cargo rustc` command which takes care of building Rust code in a way
+# that Ruby can use.
+class Gem::Ext::CargoBuilder < Gem::Ext::Builder
+ attr_accessor :spec, :runner, :profile
+
+ def initialize
+ require_relative "../command"
+ require_relative "cargo_builder/link_flag_converter"
+
+ @runner = self.class.method(:run)
+ @profile = :release
+ end
+
+ def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd)
+ require "tempfile"
+ require "fileutils"
+
+ # Where's the Cargo.toml of the crate we're building
+ cargo_toml = File.join(cargo_dir, "Cargo.toml")
+ # What's the crate's name
+ crate_name = cargo_crate_name(cargo_dir, cargo_toml, results)
+
+ begin
+ # Create a tmp dir to do the build in
+ tmp_dest = Dir.mktmpdir(".gem.", cargo_dir)
+
+ # Run the build
+ cmd = cargo_command(cargo_toml, tmp_dest, args, crate_name)
+ runner.call(cmd, results, "cargo", cargo_dir, build_env)
+
+ # Where do we expect Cargo to write the compiled library
+ dylib_path = cargo_dylib_path(tmp_dest, crate_name)
+
+ # Helpful error if we didn't find the compiled library
+ raise DylibNotFoundError, tmp_dest unless File.exist?(dylib_path)
+
+ # Cargo and Ruby differ on how the library should be named, rename from
+ # what Cargo outputs to what Ruby expects
+ dlext_name = "#{crate_name}.#{makefile_config("DLEXT")}"
+ dlext_path = File.join(File.dirname(dylib_path), dlext_name)
+ FileUtils.cp(dylib_path, dlext_path)
+
+ nesting = extension_nesting(extension)
+
+ if Gem.install_extension_in_lib && lib_dir
+ nested_lib_dir = File.join(lib_dir, nesting)
+ FileUtils.mkdir_p nested_lib_dir
+ FileUtils.cp_r dlext_path, nested_lib_dir, remove_destination: true
+ end
+
+ # move to final destination
+ nested_dest_path = File.join(dest_path, nesting)
+ FileUtils.mkdir_p nested_dest_path
+ FileUtils.cp_r dlext_path, nested_dest_path, remove_destination: true
+ ensure
+ # clean up intermediary build artifacts
+ FileUtils.rm_rf tmp_dest if tmp_dest
+ end
+
+ results
+ end
+
+ def build_env
+ build_env = rb_config_env
+ build_env["RUBY_STATIC"] = "true" if ruby_static? && ENV.key?("RUBY_STATIC")
+ cfg = "--cfg=rb_sys_gem --cfg=rubygems --cfg=rubygems_#{Gem::VERSION.tr(".", "_")}"
+ build_env["RUSTFLAGS"] = [ENV["RUSTFLAGS"], cfg].compact.join(" ")
+ build_env
+ end
+
+ def cargo_command(cargo_toml, dest_path, args = [], crate_name = nil)
+ cmd = []
+ cmd += [cargo, "rustc"]
+ cmd += ["--crate-type", "cdylib"]
+ cmd += ["--target", ENV["CARGO_BUILD_TARGET"]] if ENV["CARGO_BUILD_TARGET"]
+ cmd += ["--target-dir", dest_path]
+ cmd += ["--manifest-path", cargo_toml]
+ cmd += ["--lib"]
+ cmd += ["--profile", profile.to_s]
+ cmd += ["--locked"]
+ cmd += Gem::Command.build_args
+ cmd += args
+ cmd += ["--"]
+ cmd += [*cargo_rustc_args(dest_path, crate_name)]
+ cmd
+ end
+
+ private
+
+ def cargo
+ ENV.fetch("CARGO", "cargo")
+ end
+
+ # returns the directory nesting of the extension, ignoring the first part, so
+ # "ext/foo/bar/Cargo.toml" becomes "foo/bar"
+ def extension_nesting(extension)
+ parts = extension.to_s.split(Regexp.union([File::SEPARATOR, File::ALT_SEPARATOR].compact))
+
+ parts = parts.each_with_object([]) do |segment, final|
+ next if segment == "."
+ if segment == ".."
+ raise Gem::InstallError, "extension outside of gem root" if final.empty?
+ next final.pop
+ end
+ final << segment
+ end
+
+ File.join(parts[1...-1])
+ end
+
+ def rb_config_env
+ result = {}
+ RbConfig::CONFIG.each {|k, v| result["RBCONFIG_#{k}"] = v }
+ result
+ end
+
+ def cargo_rustc_args(dest_dir, crate_name)
+ [
+ *linker_args,
+ *mkmf_libpath,
+ *rustc_dynamic_linker_flags(dest_dir, crate_name),
+ *rustc_lib_flags(dest_dir),
+ *platform_specific_rustc_args(dest_dir),
+ ]
+ end
+
+ def platform_specific_rustc_args(dest_dir, flags = [])
+ if mingw_target?
+ # On mingw platforms, mkmf adds libruby to the linker flags
+ flags += libruby_args(dest_dir)
+
+ # Make sure ALSR is used on mingw
+ # see https://github.com/rust-lang/rust/pull/75406/files
+ flags += ["-C", "link-arg=-Wl,--dynamicbase"]
+ flags += ["-C", "link-arg=-Wl,--disable-auto-image-base"]
+
+ # If the gem is installed on a host with build tools installed, but is
+ # run on one that isn't the missing libraries will cause the extension
+ # to fail on start.
+ flags += ["-C", "link-arg=-static-libgcc"]
+ elsif darwin_target?
+ # Ventura does not always have this flag enabled
+ flags += ["-C", "link-arg=-Wl,-undefined,dynamic_lookup"]
+ end
+
+ flags
+ end
+
+ # We want to use the same linker that Ruby uses, so that the linker flags from
+ # mkmf work properly.
+ def linker_args
+ cc_flag = Shellwords.split(makefile_config("CC"))
+ linker = cc_flag.shift
+ link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] }
+
+ return mswin_link_args if linker == "cl"
+
+ ["-C", "linker=#{linker}", *link_args]
+ end
+
+ def mswin_link_args
+ args = []
+ args += ["-l", makefile_config("LIBRUBYARG_SHARED").chomp(".lib")]
+ args += split_flags("LIBS").flat_map {|lib| ["-l", lib.chomp(".lib")] }
+ args += split_flags("LOCAL_LIBS").flat_map {|lib| ["-l", lib.chomp(".lib")] }
+ args
+ end
+
+ def libruby_args(dest_dir)
+ libs = makefile_config(ruby_static? ? "LIBRUBYARG_STATIC" : "LIBRUBYARG_SHARED")
+ raw_libs = Shellwords.split(libs)
+ raw_libs.flat_map {|l| ldflag_to_link_modifier(l) }
+ end
+
+ def ruby_static?
+ return true if %w[1 true].include?(ENV["RUBY_STATIC"])
+
+ makefile_config("ENABLE_SHARED") == "no"
+ end
+
+ def cargo_dylib_path(dest_path, crate_name)
+ prefix = so_ext == "dll" ? "" : "lib"
+ path_parts = [dest_path]
+ path_parts << ENV["CARGO_BUILD_TARGET"] if ENV["CARGO_BUILD_TARGET"]
+ path_parts += ["release", "#{prefix}#{crate_name}.#{so_ext}"]
+ File.join(*path_parts)
+ end
+
+ def cargo_crate_name(cargo_dir, manifest_path, results)
+ require "open3"
+ Gem.load_yaml
+
+ output, status =
+ begin
+ Open3.capture2e(cargo, "metadata", "--no-deps", "--format-version", "1", chdir: cargo_dir)
+ rescue StandardError => error
+ raise Gem::InstallError, "cargo metadata failed #{error.message}"
+ end
+
+ unless status.success?
+ if Gem.configuration.really_verbose
+ puts output
+ else
+ results << output
+ end
+
+ exit_reason =
+ if status.exited?
+ ", exit code #{status.exitstatus}"
+ elsif status.signaled?
+ ", uncaught signal #{status.termsig}"
+ end
+
+ raise Gem::InstallError, "cargo metadata failed#{exit_reason}"
+ end
+
+ # cargo metadata output is specified as json, but with the
+ # --format-version 1 option the output is compatible with YAML, so we can
+ # avoid the json dependency
+ metadata = Gem::SafeYAML.safe_load(output)
+ package = metadata["packages"].find {|pkg| normalize_path(pkg["manifest_path"]) == manifest_path }
+ unless package
+ found = metadata["packages"].map {|md| "#{md["name"]} at #{md["manifest_path"]}" }
+ raise Gem::InstallError, <<-EOF
+failed to determine cargo package name
+
+looking for: #{manifest_path}
+
+found:
+#{found.join("\n")}
+EOF
+ end
+ package["name"].tr("-", "_")
+ end
+
+ def normalize_path(path)
+ return path unless File::ALT_SEPARATOR
+
+ path.tr(File::ALT_SEPARATOR, File::SEPARATOR)
+ end
+
+ def rustc_dynamic_linker_flags(dest_dir, crate_name)
+ split_flags("DLDFLAGS").
+ map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir, crate_name) }.
+ compact.
+ flat_map {|arg| ldflag_to_link_modifier(arg) }
+ end
+
+ def rustc_lib_flags(dest_dir)
+ split_flags("LIBS").flat_map {|arg| ldflag_to_link_modifier(arg) }
+ end
+
+ def split_flags(var)
+ Shellwords.split(RbConfig::CONFIG.fetch(var, ""))
+ end
+
+ def ldflag_to_link_modifier(arg)
+ LinkFlagConverter.convert(arg)
+ end
+
+ def msvc_target?
+ makefile_config("target_os").include?("msvc")
+ end
+
+ def darwin_target?
+ makefile_config("target_os").include?("darwin")
+ end
+
+ def mingw_target?
+ makefile_config("target_os").include?("mingw")
+ end
+
+ def win_target?
+ target_platform = RbConfig::CONFIG["target_os"]
+ !!Gem::WIN_PATTERNS.find {|r| target_platform =~ r }
+ end
+
+ # Interpolate substitution vars in the arg (i.e. $(DEFFILE))
+ def maybe_resolve_ldflag_variable(input_arg, dest_dir, crate_name)
+ var_matches = input_arg.match(/\$\((\w+)\)/)
+
+ return input_arg unless var_matches
+
+ var_name = var_matches[1]
+
+ return input_arg if var_name.nil? || var_name.chomp.empty?
+
+ case var_name
+ # On windows, it is assumed that mkmf has setup an exports file for the
+ # extension, so we have to create one ourselves.
+ when "DEFFILE"
+ write_deffile(dest_dir, crate_name)
+ else
+ RbConfig::CONFIG[var_name]
+ end
+ end
+
+ def write_deffile(dest_dir, crate_name)
+ deffile_path = File.join(dest_dir, "#{crate_name}-#{RbConfig::CONFIG["arch"]}.def")
+ export_prefix = makefile_config("EXPORT_PREFIX") || ""
+
+ File.open(deffile_path, "w") do |f|
+ f.puts "EXPORTS"
+ f.puts "#{export_prefix.strip}Init_#{crate_name}"
+ end
+
+ deffile_path
+ end
+
+ # We have to basically reimplement <code>RbConfig::CONFIG['SOEXT']</code> here to support
+ # Ruby < 2.5
+ #
+ # @see https://github.com/ruby/ruby/blob/c87c027f18c005460746a74c07cd80ee355b16e4/configure.ac#L3185
+ def so_ext
+ return RbConfig::CONFIG["SOEXT"] if RbConfig::CONFIG.key?("SOEXT")
+
+ if win_target?
+ "dll"
+ elsif darwin_target?
+ "dylib"
+ else
+ "so"
+ end
+ end
+
+ # Corresponds to $(LIBPATH) in mkmf
+ def mkmf_libpath
+ ["-L", "native=#{makefile_config("libdir")}"]
+ end
+
+ def makefile_config(var_name)
+ val = RbConfig::MAKEFILE_CONFIG[var_name]
+
+ return unless val
+
+ RbConfig.expand(val.dup)
+ end
+
+ # Error raised when no cdylib artifact was created
+ class DylibNotFoundError < StandardError
+ def initialize(dir)
+ files = Dir.glob(File.join(dir, "**", "*")).map {|f| "- #{f}" }.join "\n"
+
+ super <<~MSG
+ Dynamic library not found for Rust extension (in #{dir})
+
+ Make sure you set "crate-type" in Cargo.toml to "cdylib"
+
+ Found files:
+ #{files}
+ MSG
+ end
+ end
+end
diff --git a/lib/rubygems/ext/cargo_builder/link_flag_converter.rb b/lib/rubygems/ext/cargo_builder/link_flag_converter.rb
new file mode 100644
index 0000000000..e4d196cb10
--- /dev/null
+++ b/lib/rubygems/ext/cargo_builder/link_flag_converter.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class Gem::Ext::CargoBuilder < Gem::Ext::Builder
+ # Converts Ruby link flags into something cargo understands
+ class LinkFlagConverter
+ FILTERED_PATTERNS = [
+ /compress-debug-sections/, # Not supported by all linkers, and not required for Rust
+ ].freeze
+
+ def self.convert(arg)
+ return [] if FILTERED_PATTERNS.any? {|p| p.match?(arg) }
+
+ case arg.chomp
+ when /^-L\s*(.+)$/
+ ["-L", "native=#{$1}"]
+ when /^--library=(\w+\S+)$/, /^-l\s*(\w+\S+)$/
+ ["-l", $1]
+ when /^-l\s*([^:\s])+/ # -lfoo, but not -l:libfoo.a
+ ["-l", $1]
+ when /^-F\s*(.*)$/
+ ["-l", "framework=#{$1}"]
+ else
+ ["-C", "link-args=#{arg}"]
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb
index 269e876cfa..b162664784 100644
--- a/lib/rubygems/ext/cmake_builder.rb
+++ b/lib/rubygems/ext/cmake_builder.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative '../command'
class Gem::Ext::CmakeBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd)
- unless File.exist?(File.join(cmake_dir, 'Makefile'))
+ unless File.exist?(File.join(cmake_dir, "Makefile"))
+ require_relative "../command"
cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args]
run cmd, results, class_name, cmake_dir
diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb
index eb2f9fce61..6b8590ba2e 100644
--- a/lib/rubygems/ext/configure_builder.rb
+++ b/lib/rubygems/ext/configure_builder.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
@@ -7,7 +8,7 @@
class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd)
- unless File.exist?(File.join(configure_dir, 'Makefile'))
+ unless File.exist?(File.join(configure_dir, "Makefile"))
cmd = ["sh", "./configure", "--prefix=#{dest_path}", *args]
run cmd, results, class_name, configure_dir
diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb
index fede270417..fb68a7a8cc 100644
--- a/lib/rubygems/ext/ext_conf_builder.rb
+++ b/lib/rubygems/ext/ext_conf_builder.rb
@@ -1,88 +1,63 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'shellwords'
-
class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd)
- require 'fileutils'
- require 'tempfile'
+ require "fileutils"
+ require "tempfile"
tmp_dest = Dir.mktmpdir(".gem.", extension_dir)
# Some versions of `mktmpdir` return absolute paths, which will break make
- # if the paths contain spaces. However, on Ruby 1.9.x on Windows, relative
- # paths cause all C extension builds to fail.
+ # if the paths contain spaces.
#
- # As such, we convert to a relative path unless we are using Ruby 1.9.x on
- # Windows. This means that when using Ruby 1.9.x on Windows, paths with
- # spaces do not work.
- #
- # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940
- tmp_dest = get_relative_path(tmp_dest, extension_dir)
-
- Tempfile.open %w[siteconf .rb], extension_dir do |siteconf|
- siteconf.puts "require 'rbconfig'"
- siteconf.puts "dest_path = #{tmp_dest.dump}"
- %w[sitearchdir sitelibdir].each do |dir|
- siteconf.puts "RbConfig::MAKEFILE_CONFIG['#{dir}'] = dest_path"
- siteconf.puts "RbConfig::CONFIG['#{dir}'] = dest_path"
- end
-
- siteconf.close
-
- destdir = ENV["DESTDIR"]
-
- begin
- # workaround for https://github.com/oracle/truffleruby/issues/2115
- siteconf_path = RUBY_ENGINE == "truffleruby" ? siteconf.path.dup : siteconf.path
- cmd = Gem.ruby.shellsplit << "-I" << File.expand_path("../../..", __FILE__) <<
- "-r" << get_relative_path(siteconf_path, extension_dir) << File.basename(extension)
- cmd.push(*args)
-
- begin
- run(cmd, results, class_name, extension_dir) do |s, r|
- mkmf_log = File.join(extension_dir, 'mkmf.log')
- if File.exist? mkmf_log
- unless s.success?
- r << "To see why this extension failed to compile, please check" \
- " the mkmf.log which can be found here:\n"
- r << " " + File.join(dest_path, 'mkmf.log') + "\n"
- end
- FileUtils.mv mkmf_log, dest_path
- end
+ # As such, we convert to a relative path.
+ tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir)
+
+ destdir = ENV["DESTDIR"]
+
+ begin
+ cmd = ruby << File.basename(extension)
+ cmd.push(*args)
+
+ run(cmd, results, class_name, extension_dir) do |s, r|
+ mkmf_log = File.join(extension_dir, "mkmf.log")
+ if File.exist? mkmf_log
+ unless s.success?
+ r << "To see why this extension failed to compile, please check" \
+ " the mkmf.log which can be found here:\n"
+ r << " " + File.join(dest_path, "mkmf.log") + "\n"
end
- siteconf.unlink
+ FileUtils.mv mkmf_log, dest_path
end
+ end
- ENV["DESTDIR"] = nil
+ ENV["DESTDIR"] = nil
- make dest_path, results, extension_dir
+ make dest_path, results, extension_dir, tmp_dest_relative
- if tmp_dest
- full_tmp_dest = File.join(extension_dir, tmp_dest)
+ full_tmp_dest = File.join(extension_dir, tmp_dest_relative)
- # TODO remove in RubyGems 3
- if Gem.install_extension_in_lib and lib_dir
- FileUtils.mkdir_p lib_dir
- entries = Dir.entries(full_tmp_dest) - %w[. ..]
- entries = entries.map {|entry| File.join full_tmp_dest, entry }
- FileUtils.cp_r entries, lib_dir, :remove_destination => true
- end
+ if Gem.install_extension_in_lib && lib_dir
+ FileUtils.mkdir_p lib_dir
+ entries = Dir.entries(full_tmp_dest) - %w[. ..]
+ entries = entries.map {|entry| File.join full_tmp_dest, entry }
+ FileUtils.cp_r entries, lib_dir, remove_destination: true
+ end
- FileUtils::Entry_.new(full_tmp_dest).traverse do |ent|
- destent = ent.class.new(dest_path, ent.rel)
- destent.exist? or FileUtils.mv(ent.path, destent.path)
- end
- end
- ensure
- ENV["DESTDIR"] = destdir
- siteconf.close!
+ FileUtils::Entry_.new(full_tmp_dest).traverse do |ent|
+ destent = ent.class.new(dest_path, ent.rel)
+ destent.exist? || FileUtils.mv(ent.path, destent.path)
end
+
+ make dest_path, results, extension_dir, tmp_dest_relative, ["clean"]
+ ensure
+ ENV["DESTDIR"] = destdir
end
results
@@ -90,10 +65,8 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
FileUtils.rm_rf tmp_dest if tmp_dest
end
- private
-
def self.get_relative_path(path, base)
- path[0..base.length - 1] = '.' if path.start_with?(base)
+ path[0..base.length - 1] = "." if path.start_with?(base)
path
end
end
diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb
index 64a6c0eb80..0171807b39 100644
--- a/lib/rubygems/ext/rake_builder.rb
+++ b/lib/rubygems/ext/rake_builder.rb
@@ -1,27 +1,28 @@
# frozen_string_literal: true
+
+require_relative "../shellwords"
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require "shellwords"
-
class Gem::Ext::RakeBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd)
- if File.basename(extension) =~ /mkrf_conf/i
+ if /mkrf_conf/i.match?(File.basename(extension))
run([Gem.ruby, File.basename(extension), *args], results, class_name, extension_dir)
end
- rake = ENV['rake']
+ rake = ENV["rake"]
if rake
- rake = rake.shellsplit
+ rake = Shellwords.split(rake)
else
begin
- rake = [Gem.ruby, "-I#{File.expand_path("../..", __dir__)}", "-rrubygems", Gem.bin_path('rake', 'rake')]
+ rake = ruby << "-rrubygems" << Gem.bin_path("rake", "rake")
rescue Gem::Exception
- rake = [Gem.default_exec_format % 'rake']
+ rake = [Gem.default_exec_format % "rake"]
end
end
diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb
index a36674503e..8335a0ad03 100644
--- a/lib/rubygems/gem_runner.rb
+++ b/lib/rubygems/gem_runner.rb
@@ -1,18 +1,14 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems'
-require 'rubygems/command_manager'
-require 'rubygems/deprecate'
-
-##
-# Load additional plugins from $LOAD_PATH
-
-Gem.load_env_plugins rescue nil
+require_relative "../rubygems"
+require_relative "command_manager"
+require_relative "deprecate"
##
# Run an instance of the gem program.
@@ -37,16 +33,23 @@ class Gem::GemRunner
do_configuration args
+ begin
+ Gem.load_env_plugins
+ rescue StandardError
+ nil
+ end
+ Gem.load_plugins
+
cmd = @command_manager_class.instance
cmd.command_names.each do |command_name|
config_args = Gem.configuration[command_name]
config_args = case config_args
when String
- config_args.split ' '
+ config_args.split " "
else
Array(config_args)
- end
+ end
Gem::Command.add_specific_extra_args command_name, config_args
end
@@ -58,7 +61,7 @@ class Gem::GemRunner
# other arguments in the list.
def extract_build_args(args) # :nodoc:
- return [] unless offset = args.index('--')
+ return [] unless offset = args.index("--")
build_args = args.slice!(offset...args.length)
@@ -75,5 +78,3 @@ class Gem::GemRunner
Gem::Command.extra_args = Gem.configuration[:gem]
end
end
-
-Gem.load_plugins
diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb
index 3687e776e2..a8361b7ff1 100644
--- a/lib/rubygems/gemcutter_utilities.rb
+++ b/lib/rubygems/gemcutter_utilities.rb
@@ -1,14 +1,17 @@
# frozen_string_literal: true
-require 'rubygems/remote_fetcher'
-require 'rubygems/text'
+
+require_relative "remote_fetcher"
+require_relative "text"
+require_relative "gemcutter_utilities/webauthn_listener"
+require_relative "gemcutter_utilities/webauthn_poller"
##
# Utility methods for using the RubyGems API.
module Gem::GemcutterUtilities
-
ERROR_CODE = 1
- API_SCOPES = %i[index_rubygems push_rubygem yank_rubygem add_owner remove_owner access_webhooks show_dashboard].freeze
+ API_SCOPES = [:index_rubygems, :push_rubygem, :yank_rubygem, :add_owner, :remove_owner, :access_webhooks].freeze
+ EXCLUSIVELY_API_SCOPES = [:show_dashboard].freeze
include Gem::Text
@@ -19,8 +22,8 @@ module Gem::GemcutterUtilities
# Add the --key option
def add_key_option
- add_option('-k', '--key KEYNAME', Symbol,
- 'Use the given API key',
+ add_option("-k", "--key KEYNAME", Symbol,
+ "Use the given API key",
"from #{Gem.configuration.credentials_path}") do |value,options|
options[:key] = value
end
@@ -30,8 +33,9 @@ module Gem::GemcutterUtilities
# Add the --otp option
def add_otp_option
- add_option('--otp CODE',
- 'Digit code for multifactor authentication') do |value, options|
+ add_option("--otp CODE",
+ "Digit code for multifactor authentication",
+ "You can also use the environment variable GEM_HOST_OTP_CODE") do |value, options|
options[:otp] = value
end
end
@@ -52,6 +56,13 @@ module Gem::GemcutterUtilities
end
##
+ # The OTP code from the command options or from the user's configuration.
+
+ def otp
+ options[:otp] || ENV["GEM_HOST_OTP_CODE"]
+ end
+
+ ##
# The host to connect to either from the RUBYGEMS_HOST environment variable
# or from the user's configuration
@@ -61,9 +72,8 @@ module Gem::GemcutterUtilities
@host ||=
begin
- env_rubygems_host = ENV['RUBYGEMS_HOST']
- env_rubygems_host = nil if
- env_rubygems_host and env_rubygems_host.empty?
+ env_rubygems_host = ENV["RUBYGEMS_HOST"]
+ env_rubygems_host = nil if env_rubygems_host&.empty?
env_rubygems_host || configured_host
end
@@ -74,8 +84,8 @@ module Gem::GemcutterUtilities
#
# If +allowed_push_host+ metadata is present, then it will only allow that host.
- def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, &block)
- require 'net/http'
+ def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, credentials: {}, &block)
+ require_relative "vendored_net_http"
self.host = host if host
unless self.host
@@ -84,8 +94,8 @@ module Gem::GemcutterUtilities
end
if allowed_push_host
- allowed_host_uri = URI.parse(allowed_push_host)
- host_uri = URI.parse(self.host)
+ allowed_host_uri = Gem::URI.parse(allowed_push_host)
+ host_uri = Gem::URI.parse(self.host)
unless (host_uri.scheme == allowed_host_uri.scheme) && (host_uri.host == allowed_host_uri.host)
alert_error "#{self.host.inspect} is not allowed by the gemspec, which only allows #{allowed_push_host.inspect}"
@@ -93,11 +103,11 @@ module Gem::GemcutterUtilities
end
end
- uri = URI.parse "#{self.host}/#{path}"
+ uri = Gem::URI.parse "#{self.host}/#{path}"
response = request_with_otp(method, uri, &block)
if mfa_unauthorized?(response)
- ask_otp
+ fetch_otp(credentials)
response = request_with_otp(method, uri, &block)
end
@@ -110,27 +120,27 @@ module Gem::GemcutterUtilities
end
def mfa_unauthorized?(response)
- response.kind_of?(Net::HTTPUnauthorized) && response.body.start_with?('You have enabled multifactor authentication')
+ response.is_a?(Gem::Net::HTTPUnauthorized) && response.body.start_with?("You have enabled multifactor authentication")
end
def update_scope(scope)
- sign_in_host = self.host
+ sign_in_host = host
pretty_host = pretty_host(sign_in_host)
update_scope_params = { scope => true }
say "The existing key doesn't have access of #{scope} on #{pretty_host}. Please sign in to update access."
- email = ask " Email: "
- password = ask_for_password "Password: "
+ identifier = ask "Username/email: "
+ password = ask_for_password " Password: "
response = rubygems_api_request(:put, "api/v1/api_key",
sign_in_host, scope: scope) do |request|
- request.basic_auth email, password
- request["OTP"] = options[:otp] if options[:otp]
- request.body = URI.encode_www_form({:api_key => api_key }.merge(update_scope_params))
+ request.basic_auth identifier, password
+ request["OTP"] = otp if otp
+ request.body = Gem::URI.encode_www_form({ api_key: api_key }.merge(update_scope_params))
end
- with_response response do |resp|
+ with_response response do |_resp|
say "Added #{scope} scope to the existing API key"
end
end
@@ -140,27 +150,34 @@ module Gem::GemcutterUtilities
# key.
def sign_in(sign_in_host = nil, scope: nil)
- sign_in_host ||= self.host
+ sign_in_host ||= host
return if api_key
pretty_host = pretty_host(sign_in_host)
say "Enter your #{pretty_host} credentials."
- say "Don't have an account yet? " +
+ say "Don't have an account yet? " \
"Create one at #{sign_in_host}/sign_up"
- email = ask " Email: "
- password = ask_for_password "Password: "
+ identifier = ask "Username/email: "
+ password = ask_for_password " Password: "
say "\n"
key_name = get_key_name(scope)
scope_params = get_scope_params(scope)
+ profile = get_user_profile(identifier, password)
+ mfa_params = get_mfa_params(profile)
+ all_params = scope_params.merge(mfa_params)
+ warning = profile["warning"]
+ credentials = { identifier: identifier, password: password }
+
+ say "#{warning}\n" if warning
response = rubygems_api_request(:post, "api/v1/api_key",
- sign_in_host, scope: scope) do |request|
- request.basic_auth email, password
- request["OTP"] = options[:otp] if options[:otp]
- request.body = URI.encode_www_form({ name: key_name }.merge(scope_params))
+ sign_in_host, credentials: credentials, scope: scope) do |request|
+ request.basic_auth identifier, password
+ request["OTP"] = otp if otp
+ request.body = Gem::URI.encode_www_form({ name: key_name }.merge(all_params))
end
with_response response do |resp|
@@ -187,16 +204,23 @@ module Gem::GemcutterUtilities
# block was given or shows the response body to the user.
#
# If the response was not successful, shows an error to the user including
- # the +error_prefix+ and the response body.
+ # the +error_prefix+ and the response body. If the response was a permanent redirect,
+ # shows an error to the user including the redirect location.
def with_response(response, error_prefix = nil)
case response
- when Net::HTTPSuccess then
+ when Gem::Net::HTTPSuccess then
if block_given?
yield response
else
say clean_text(response.body)
end
+ when Gem::Net::HTTPPermanentRedirect, Gem::Net::HTTPRedirection then
+ message = "The request has redirected permanently to #{response["location"]}. Please check your defined push host URL."
+ message = "#{error_prefix}: #{message}" if error_prefix
+
+ say clean_text(message)
+ terminate_interaction(ERROR_CODE)
else
message = response.body
message = "#{error_prefix}: #{message}" if error_prefix
@@ -211,7 +235,7 @@ module Gem::GemcutterUtilities
# +response+ text and no otp provided by options.
def set_api_key(host, key)
- if host == Gem::DEFAULT_HOST
+ if default_host?
Gem.configuration.rubygems_api_key = key
else
Gem.configuration.set_api_key host, key
@@ -221,37 +245,96 @@ module Gem::GemcutterUtilities
private
def request_with_otp(method, uri, &block)
- request_method = Net::HTTP.const_get method.to_s.capitalize
+ request_method = Gem::Net::HTTP.const_get method.to_s.capitalize
Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req|
- req["OTP"] = options[:otp] if options[:otp]
+ req["OTP"] = otp if otp
block.call(req)
end
end
- def ask_otp
- say 'You have enabled multi-factor authentication. Please enter OTP code.'
- options[:otp] = ask 'Code: '
+ def fetch_otp(credentials)
+ options[:otp] = if webauthn_url = webauthn_verification_url(credentials)
+ server = TCPServer.new 0
+ port = server.addr[1].to_s
+
+ url_with_port = "#{webauthn_url}?port=#{port}"
+ say "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option."
+
+ threads = [WebauthnListener.listener_thread(host, server), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)]
+ otp_thread = wait_for_otp_thread(*threads)
+
+ threads.each(&:join)
+
+ if error = otp_thread[:error]
+ alert_error error.message
+ terminate_interaction(1)
+ end
+
+ say "You are verified with a security device. You may close the browser window."
+ otp_thread[:otp]
+ else
+ say "You have enabled multi-factor authentication. Please enter OTP code."
+ ask "Code: "
+ end
+ end
+
+ def wait_for_otp_thread(*threads)
+ loop do
+ threads.each do |otp_thread|
+ return otp_thread unless otp_thread.alive?
+ end
+ sleep 0.1
+ end
+ ensure
+ threads.each(&:exit)
+ end
+
+ def webauthn_verification_url(credentials)
+ response = rubygems_api_request(:post, "api/v1/webauthn_verification") do |request|
+ if credentials.empty?
+ request.add_field "Authorization", api_key
+ else
+ request.basic_auth credentials[:identifier], credentials[:password]
+ end
+ end
+ response.is_a?(Gem::Net::HTTPSuccess) ? response.body : nil
end
def pretty_host(host)
- if Gem::DEFAULT_HOST == host
- 'RubyGems.org'
+ if default_host?
+ "RubyGems.org"
else
host
end
end
def get_scope_params(scope)
- scope_params = {}
+ scope_params = { index_rubygems: true }
if scope
scope_params = { scope => true }
else
- say "Please select scopes you want to enable for the API key (y/n)"
- API_SCOPES.each do |scope|
- selected = ask "#{scope} [y/N]: "
- scope_params[scope] = true if selected =~ /^[yY](es)?$/
+ say "The default access scope is:"
+ scope_params.each do |k, _v|
+ say " #{k}: y"
+ end
+ say "\n"
+ customise = ask_yes_no("Do you want to customise scopes?", false)
+ if customise
+ EXCLUSIVELY_API_SCOPES.each do |excl_scope|
+ selected = ask_yes_no("#{excl_scope} (exclusive scope, answering yes will not prompt for other scopes)", false)
+ next unless selected
+
+ return { excl_scope => true }
+ end
+
+ scope_params = {}
+
+ API_SCOPES.each do |s|
+ selected = ask_yes_no(s.to_s, false)
+ scope_params[s] = true if selected
+ end
end
say "\n"
end
@@ -259,6 +342,32 @@ module Gem::GemcutterUtilities
scope_params
end
+ def default_host?
+ host == Gem::DEFAULT_HOST
+ end
+
+ def get_user_profile(identifier, password)
+ return {} unless default_host?
+
+ response = rubygems_api_request(:get, "api/v1/profile/me.yaml") do |request|
+ request.basic_auth identifier, password
+ end
+
+ with_response response do |resp|
+ Gem::ConfigFile.load_with_rubygems_config_hash(clean_text(resp.body))
+ end
+ end
+
+ def get_mfa_params(profile)
+ mfa_level = profile["mfa"]
+ params = {}
+ if ["ui_only", "ui_and_gem_signin"].include?(mfa_level)
+ selected = ask_yes_no("Would you like to enable MFA for this key? (strongly recommended)")
+ params["mfa"] = true if selected
+ end
+ params
+ end
+
def get_key_name(scope)
hostname = Socket.gethostname || "unknown-host"
user = ENV["USER"] || ENV["USERNAME"] || "unknown-user"
@@ -274,6 +383,6 @@ module Gem::GemcutterUtilities
end
def api_key_forbidden?(response)
- response.kind_of?(Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access")
+ response.is_a?(Gem::Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access")
end
end
diff --git a/lib/rubygems/gemcutter_utilities/webauthn_listener.rb b/lib/rubygems/gemcutter_utilities/webauthn_listener.rb
new file mode 100644
index 0000000000..abf65efe37
--- /dev/null
+++ b/lib/rubygems/gemcutter_utilities/webauthn_listener.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require_relative "webauthn_listener/response"
+
+##
+# The WebauthnListener class retrieves an OTP after a user successfully WebAuthns with the Gem host.
+# An instance opens a socket using the TCPServer instance given and listens for a request from the Gem host.
+# The request should be a GET request to the root path and contains the OTP code in the form
+# of a query parameter `code`. The listener will return the code which will be used as the OTP for
+# API requests.
+#
+# Types of responses sent by the listener after receiving a request:
+# - 200 OK: OTP code was successfully retrieved
+# - 204 No Content: If the request was an OPTIONS request
+# - 400 Bad Request: If the request did not contain a query parameter `code`
+# - 404 Not Found: The request was not to the root path
+# - 405 Method Not Allowed: OTP code was not retrieved because the request was not a GET/OPTIONS request
+#
+# Example usage:
+#
+# thread = Gem::WebauthnListener.listener_thread("https://rubygems.example", server)
+# thread.join
+# otp = thread[:otp]
+# error = thread[:error]
+#
+
+module Gem::GemcutterUtilities
+ class WebauthnListener
+ attr_reader :host
+
+ def initialize(host)
+ @host = host
+ end
+
+ def self.listener_thread(host, server)
+ Thread.new do
+ thread = Thread.current
+ thread.abort_on_exception = true
+ thread.report_on_exception = false
+ thread[:otp] = new(host).wait_for_otp_code(server)
+ rescue Gem::WebauthnVerificationError => e
+ thread[:error] = e
+ ensure
+ server.close
+ end
+ end
+
+ def wait_for_otp_code(server)
+ loop do
+ socket = server.accept
+ request_line = socket.gets
+
+ method, req_uri, _protocol = request_line.split(" ")
+ req_uri = Gem::URI.parse(req_uri)
+
+ responder = SocketResponder.new(socket)
+
+ unless root_path?(req_uri)
+ responder.send(NotFoundResponse.for(host))
+ raise Gem::WebauthnVerificationError, "Page at #{req_uri.path} not found."
+ end
+
+ case method.upcase
+ when "OPTIONS"
+ responder.send(NoContentResponse.for(host))
+ next # will be GET
+ when "GET"
+ if otp = parse_otp_from_uri(req_uri)
+ responder.send(OkResponse.for(host))
+ return otp
+ end
+ responder.send(BadRequestResponse.for(host))
+ raise Gem::WebauthnVerificationError, "Did not receive OTP from #{host}."
+ else
+ responder.send(MethodNotAllowedResponse.for(host))
+ raise Gem::WebauthnVerificationError, "Invalid HTTP method #{method.upcase} received."
+ end
+ end
+ end
+
+ private
+
+ def root_path?(uri)
+ uri.path == "/"
+ end
+
+ def parse_otp_from_uri(uri)
+ require "cgi"
+
+ return if uri.query.nil?
+ CGI.parse(uri.query).dig("code", 0)
+ end
+
+ class SocketResponder
+ def initialize(socket)
+ @socket = socket
+ end
+
+ def send(response)
+ @socket.print response.to_s
+ @socket.close
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/gemcutter_utilities/webauthn_listener/response.rb b/lib/rubygems/gemcutter_utilities/webauthn_listener/response.rb
new file mode 100644
index 0000000000..17baa64fff
--- /dev/null
+++ b/lib/rubygems/gemcutter_utilities/webauthn_listener/response.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+##
+# The WebauthnListener Response class is used by the WebauthnListener to create
+# responses to be sent to the Gem host. It creates a Gem::Net::HTTPResponse instance
+# when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`.
+# Gem::Net::HTTPResponse instances cannot be directly sent over a socket.
+#
+# Types of response classes:
+# - OkResponse
+# - NoContentResponse
+# - BadRequestResponse
+# - NotFoundResponse
+# - MethodNotAllowedResponse
+#
+# Example usage:
+#
+# server = TCPServer.new(0)
+# socket = server.accept
+#
+# response = OkResponse.for("https://rubygems.example")
+# socket.print response.to_s
+# socket.close
+#
+
+module Gem::GemcutterUtilities
+ class WebauthnListener
+ class Response
+ attr_reader :http_response
+
+ def self.for(host)
+ new(host)
+ end
+
+ def initialize(host)
+ @host = host
+
+ build_http_response
+ end
+
+ def to_s
+ status_line = "HTTP/#{@http_response.http_version} #{@http_response.code} #{@http_response.message}\r\n"
+ headers = @http_response.to_hash.map {|header, value| "#{header}: #{value.join(", ")}\r\n" }.join + "\r\n"
+ body = @http_response.body ? "#{@http_response.body}\n" : ""
+
+ status_line + headers + body
+ end
+
+ private
+
+ # Must be implemented in subclasses
+ def code
+ raise NotImplementedError
+ end
+
+ def reason_phrase
+ raise NotImplementedError
+ end
+
+ def body; end
+
+ def build_http_response
+ response_class = Gem::Net::HTTPResponse::CODE_TO_OBJ[code.to_s]
+ @http_response = response_class.new("1.1", code, reason_phrase)
+ @http_response.instance_variable_set(:@read, true)
+
+ add_connection_header
+ add_access_control_headers
+ add_body
+ end
+
+ def add_connection_header
+ @http_response["connection"] = "close"
+ end
+
+ def add_access_control_headers
+ @http_response["access-control-allow-origin"] = @host
+ @http_response["access-control-allow-methods"] = "POST"
+ @http_response["access-control-allow-headers"] = %w[Content-Type Authorization x-csrf-token]
+ end
+
+ def add_body
+ return unless body
+ @http_response["content-type"] = "text/plain; charset=utf-8"
+ @http_response["content-length"] = body.bytesize
+ @http_response.instance_variable_set(:@body, body)
+ end
+ end
+
+ class OkResponse < Response
+ private
+
+ def code
+ 200
+ end
+
+ def reason_phrase
+ "OK"
+ end
+
+ def body
+ "success"
+ end
+ end
+
+ class NoContentResponse < Response
+ private
+
+ def code
+ 204
+ end
+
+ def reason_phrase
+ "No Content"
+ end
+ end
+
+ class BadRequestResponse < Response
+ private
+
+ def code
+ 400
+ end
+
+ def reason_phrase
+ "Bad Request"
+ end
+
+ def body
+ "missing code parameter"
+ end
+ end
+
+ class NotFoundResponse < Response
+ private
+
+ def code
+ 404
+ end
+
+ def reason_phrase
+ "Not Found"
+ end
+ end
+
+ class MethodNotAllowedResponse < Response
+ private
+
+ def code
+ 405
+ end
+
+ def reason_phrase
+ "Method Not Allowed"
+ end
+
+ def add_access_control_headers
+ super
+ @http_response["allow"] = %w[GET OPTIONS]
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/gemcutter_utilities/webauthn_poller.rb b/lib/rubygems/gemcutter_utilities/webauthn_poller.rb
new file mode 100644
index 0000000000..0fdd1d5bf4
--- /dev/null
+++ b/lib/rubygems/gemcutter_utilities/webauthn_poller.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+##
+# The WebauthnPoller class retrieves an OTP after a user successfully WebAuthns. An instance
+# polls the Gem host for the OTP code. The polling request (api/v1/webauthn_verification/<webauthn_token>/status.json)
+# is sent to the Gem host every 5 seconds and will timeout after 5 minutes. If the status field in the json response
+# is "success", the code field will contain the OTP code.
+#
+# Example usage:
+#
+# thread = Gem::WebauthnPoller.poll_thread(
+# {},
+# "RubyGems.org",
+# "https://rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY",
+# { email: "email@example.com", password: "password" }
+# )
+# thread.join
+# otp = thread[:otp]
+# error = thread[:error]
+#
+
+module Gem::GemcutterUtilities
+ class WebauthnPoller
+ include Gem::GemcutterUtilities
+ TIMEOUT_IN_SECONDS = 300
+
+ attr_reader :options, :host
+
+ def initialize(options, host)
+ @options = options
+ @host = host
+ end
+
+ def self.poll_thread(options, host, webauthn_url, credentials)
+ Thread.new do
+ thread = Thread.current
+ thread.abort_on_exception = true
+ thread.report_on_exception = false
+ thread[:otp] = new(options, host).poll_for_otp(webauthn_url, credentials)
+ rescue Gem::WebauthnVerificationError, Gem::Timeout::Error => e
+ thread[:error] = e
+ end
+ end
+
+ def poll_for_otp(webauthn_url, credentials)
+ Gem::Timeout.timeout(TIMEOUT_IN_SECONDS) do
+ loop do
+ response = webauthn_verification_poll_response(webauthn_url, credentials)
+ raise Gem::WebauthnVerificationError, response.message unless response.is_a?(Gem::Net::HTTPSuccess)
+
+ require "json"
+ parsed_response = JSON.parse(response.body)
+ case parsed_response["status"]
+ when "pending"
+ sleep 5
+ when "success"
+ return parsed_response["code"]
+ else
+ raise Gem::WebauthnVerificationError, parsed_response.fetch("message", "Invalid response from server")
+ end
+ end
+ end
+ end
+
+ private
+
+ def webauthn_verification_poll_response(webauthn_url, credentials)
+ webauthn_token = %r{(?<=\/)[^\/]+(?=$)}.match(webauthn_url)[0]
+ rubygems_api_request(:get, "api/v1/webauthn_verification/#{webauthn_token}/status.json") do |request|
+ if credentials.empty?
+ request.add_field "Authorization", api_key
+ else
+ request.basic_auth credentials[:email], credentials[:password]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/gemspec_helpers.rb b/lib/rubygems/gemspec_helpers.rb
new file mode 100644
index 0000000000..2b20fcafa1
--- /dev/null
+++ b/lib/rubygems/gemspec_helpers.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require_relative "../rubygems"
+
+##
+# Mixin methods for commands that work with gemspecs.
+
+module Gem::GemspecHelpers
+ def find_gemspec(glob = "*.gemspec")
+ gemspecs = Dir.glob(glob).sort
+
+ if gemspecs.size > 1
+ alert_error "Multiple gemspecs found: #{gemspecs}, please specify one"
+ terminate_interaction(1)
+ end
+
+ gemspecs.first
+ end
+end
diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb
deleted file mode 100644
index e595459c87..0000000000
--- a/lib/rubygems/indexer.rb
+++ /dev/null
@@ -1,425 +0,0 @@
-# frozen_string_literal: true
-require 'rubygems'
-require 'rubygems/package'
-require 'tmpdir'
-
-##
-# Top level class for building the gem repository index.
-
-class Gem::Indexer
- include Gem::UserInteraction
-
- ##
- # Build indexes for RubyGems 1.2.0 and newer when true
-
- attr_accessor :build_modern
-
- ##
- # Index install location
-
- attr_reader :dest_directory
-
- ##
- # Specs index install location
-
- attr_reader :dest_specs_index
-
- ##
- # Latest specs index install location
-
- attr_reader :dest_latest_specs_index
-
- ##
- # Prerelease specs index install location
-
- attr_reader :dest_prerelease_specs_index
-
- ##
- # Index build directory
-
- attr_reader :directory
-
- ##
- # Create an indexer that will index the gems in +directory+.
-
- def initialize(directory, options = {})
- require 'fileutils'
- require 'tmpdir'
- require 'zlib'
-
- options = { :build_modern => true }.merge options
-
- @build_modern = options[:build_modern]
-
- @dest_directory = directory
- @directory = Dir.mktmpdir 'gem_generate_index'
-
- marshal_name = "Marshal.#{Gem.marshal_version}"
-
- @master_index = File.join @directory, 'yaml'
- @marshal_index = File.join @directory, marshal_name
-
- @quick_dir = File.join @directory, 'quick'
- @quick_marshal_dir = File.join @quick_dir, marshal_name
- @quick_marshal_dir_base = File.join "quick", marshal_name # FIX: UGH
-
- @quick_index = File.join @quick_dir, 'index'
- @latest_index = File.join @quick_dir, 'latest_index'
-
- @specs_index = File.join @directory, "specs.#{Gem.marshal_version}"
- @latest_specs_index =
- File.join(@directory, "latest_specs.#{Gem.marshal_version}")
- @prerelease_specs_index =
- File.join(@directory, "prerelease_specs.#{Gem.marshal_version}")
- @dest_specs_index =
- File.join(@dest_directory, "specs.#{Gem.marshal_version}")
- @dest_latest_specs_index =
- File.join(@dest_directory, "latest_specs.#{Gem.marshal_version}")
- @dest_prerelease_specs_index =
- File.join(@dest_directory, "prerelease_specs.#{Gem.marshal_version}")
-
- @files = []
- end
-
- ##
- # Build various indices
-
- def build_indices
- specs = map_gems_to_specs gem_file_list
- Gem::Specification._resort! specs
- build_marshal_gemspecs specs
- build_modern_indices specs if @build_modern
-
- compress_indices
- end
-
- ##
- # Builds Marshal quick index gemspecs.
-
- def build_marshal_gemspecs(specs)
- count = specs.count
- progress = ui.progress_reporter count,
- "Generating Marshal quick index gemspecs for #{count} gems",
- "Complete"
-
- files = []
-
- Gem.time 'Generated Marshal quick index gemspecs' do
- specs.each do |spec|
- next if spec.default_gem?
- spec_file_name = "#{spec.original_name}.gemspec.rz"
- marshal_name = File.join @quick_marshal_dir, spec_file_name
-
- marshal_zipped = Gem.deflate Marshal.dump(spec)
-
- File.open marshal_name, 'wb' do |io|
- io.write marshal_zipped
- end
-
- files << marshal_name
-
- progress.updated spec.original_name
- end
-
- progress.done
- end
-
- @files << @quick_marshal_dir
-
- files
- end
-
- ##
- # Build a single index for RubyGems 1.2 and newer
-
- def build_modern_index(index, file, name)
- say "Generating #{name} index"
-
- Gem.time "Generated #{name} index" do
- File.open(file, 'wb') do |io|
- specs = index.map do |*spec|
- # We have to splat here because latest_specs is an array, while the
- # others are hashes.
- spec = spec.flatten.last
- platform = spec.original_platform
-
- # win32-api-1.0.4-x86-mswin32-60
- unless String === platform
- alert_warning "Skipping invalid platform in gem: #{spec.full_name}"
- next
- end
-
- platform = Gem::Platform::RUBY if platform.nil? or platform.empty?
- [spec.name, spec.version, platform]
- end
-
- specs = compact_specs(specs)
- Marshal.dump(specs, io)
- end
- end
- end
-
- ##
- # Builds indices for RubyGems 1.2 and newer. Handles full, latest, prerelease
-
- def build_modern_indices(specs)
- prerelease, released = specs.partition do |s|
- s.version.prerelease?
- end
- latest_specs =
- Gem::Specification._latest_specs specs
-
- build_modern_index(released.sort, @specs_index, 'specs')
- build_modern_index(latest_specs.sort, @latest_specs_index, 'latest specs')
- build_modern_index(prerelease.sort, @prerelease_specs_index,
- 'prerelease specs')
-
- @files += [@specs_index,
- "#{@specs_index}.gz",
- @latest_specs_index,
- "#{@latest_specs_index}.gz",
- @prerelease_specs_index,
- "#{@prerelease_specs_index}.gz"]
- end
-
- def map_gems_to_specs(gems)
- gems.map do |gemfile|
- if File.size(gemfile) == 0
- alert_warning "Skipping zero-length gem: #{gemfile}"
- next
- end
-
- begin
- spec = Gem::Package.new(gemfile).spec
- spec.loaded_from = gemfile
-
- spec.abbreviate
- spec.sanitize
-
- spec
- rescue SignalException
- alert_error "Received signal, exiting"
- raise
- rescue Exception => e
- msg = ["Unable to process #{gemfile}",
- "#{e.message} (#{e.class})",
- "\t#{e.backtrace.join "\n\t"}"].join("\n")
- alert_error msg
- end
- end.compact
- end
-
- ##
- # Compresses indices on disk
- #--
- # All future files should be compressed using gzip, not deflate
-
- def compress_indices
- say "Compressing indices"
-
- Gem.time 'Compressed indices' do
- if @build_modern
- gzip @specs_index
- gzip @latest_specs_index
- gzip @prerelease_specs_index
- end
- end
- end
-
- ##
- # Compacts Marshal output for the specs index data source by using identical
- # objects as much as possible.
-
- def compact_specs(specs)
- names = {}
- versions = {}
- platforms = {}
-
- specs.map do |(name, version, platform)|
- names[name] = name unless names.include? name
- versions[version] = version unless versions.include? version
- platforms[platform] = platform unless platforms.include? platform
-
- [names[name], versions[version], platforms[platform]]
- end
- end
-
- ##
- # Compress +filename+ with +extension+.
-
- def compress(filename, extension)
- data = Gem.read_binary filename
-
- zipped = Gem.deflate data
-
- File.open "#{filename}.#{extension}", 'wb' do |io|
- io.write zipped
- end
- end
-
- ##
- # List of gem file names to index.
-
- def gem_file_list
- Gem::Util.glob_files_in_dir("*.gem", File.join(@dest_directory, "gems"))
- end
-
- ##
- # Builds and installs indices.
-
- def generate_index
- make_temp_directories
- build_indices
- install_indices
- rescue SignalException
- ensure
- FileUtils.rm_rf @directory
- end
-
- ##
- # Zlib::GzipWriter wrapper that gzips +filename+ on disk.
-
- def gzip(filename)
- Zlib::GzipWriter.open "#{filename}.gz" do |io|
- io.write Gem.read_binary(filename)
- end
- end
-
- ##
- # Install generated indices into the destination directory.
-
- def install_indices
- verbose = Gem.configuration.really_verbose
-
- say "Moving index into production dir #{@dest_directory}" if verbose
-
- files = @files
- files.delete @quick_marshal_dir if files.include? @quick_dir
-
- if files.include? @quick_marshal_dir and not files.include? @quick_dir
- files.delete @quick_marshal_dir
-
- dst_name = File.join(@dest_directory, @quick_marshal_dir_base)
-
- FileUtils.mkdir_p File.dirname(dst_name), :verbose => verbose
- FileUtils.rm_rf dst_name, :verbose => verbose
- FileUtils.mv(@quick_marshal_dir, dst_name,
- :verbose => verbose, :force => true)
- end
-
- files = files.map do |path|
- path.sub(/^#{Regexp.escape @directory}\/?/, '') # HACK?
- end
-
- files.each do |file|
- src_name = File.join @directory, file
- dst_name = File.join @dest_directory, file
-
- FileUtils.rm_rf dst_name, :verbose => verbose
- FileUtils.mv(src_name, @dest_directory,
- :verbose => verbose, :force => true)
- end
- end
-
- ##
- # Make directories for index generation
-
- def make_temp_directories
- FileUtils.rm_rf @directory
- FileUtils.mkdir_p @directory, :mode => 0700
- FileUtils.mkdir_p @quick_marshal_dir
- end
-
- ##
- # Ensure +path+ and path with +extension+ are identical.
-
- def paranoid(path, extension)
- data = Gem.read_binary path
- compressed_data = Gem.read_binary "#{path}.#{extension}"
-
- unless data == Gem::Util.inflate(compressed_data)
- raise "Compressed file #{compressed_path} does not match uncompressed file #{path}"
- end
- end
-
- ##
- # Perform an in-place update of the repository from newly added gems.
-
- def update_index
- make_temp_directories
-
- specs_mtime = File.stat(@dest_specs_index).mtime
- newest_mtime = Time.at 0
-
- updated_gems = gem_file_list.select do |gem|
- gem_mtime = File.stat(gem).mtime
- newest_mtime = gem_mtime if gem_mtime > newest_mtime
- gem_mtime >= specs_mtime
- end
-
- if updated_gems.empty?
- say 'No new gems'
- terminate_interaction 0
- end
-
- specs = map_gems_to_specs updated_gems
- prerelease, released = specs.partition {|s| s.version.prerelease? }
-
- files = build_marshal_gemspecs specs
-
- Gem.time 'Updated indexes' do
- update_specs_index released, @dest_specs_index, @specs_index
- update_specs_index released, @dest_latest_specs_index, @latest_specs_index
- update_specs_index(prerelease,
- @dest_prerelease_specs_index,
- @prerelease_specs_index)
- end
-
- compress_indices
-
- verbose = Gem.configuration.really_verbose
-
- say "Updating production dir #{@dest_directory}" if verbose
-
- files << @specs_index
- files << "#{@specs_index}.gz"
- files << @latest_specs_index
- files << "#{@latest_specs_index}.gz"
- files << @prerelease_specs_index
- files << "#{@prerelease_specs_index}.gz"
-
- files = files.map do |path|
- path.sub(/^#{Regexp.escape @directory}\/?/, '') # HACK?
- end
-
- files.each do |file|
- src_name = File.join @directory, file
- dst_name = File.join @dest_directory, file # REFACTOR: duped above
-
- FileUtils.mv src_name, dst_name, :verbose => verbose,
- :force => true
-
- File.utime newest_mtime, newest_mtime, dst_name
- end
- end
-
- ##
- # Combines specs in +index+ and +source+ then writes out a new copy to
- # +dest+. For a latest index, does not ensure the new file is minimal.
-
- def update_specs_index(index, source, dest)
- specs_index = Marshal.load Gem.read_binary(source)
-
- index.each do |spec|
- platform = spec.original_platform
- platform = Gem::Platform::RUBY if platform.nil? or platform.empty?
- specs_index << [spec.name, spec.version, platform]
- end
-
- specs_index = compact_specs specs_index.uniq.sort
-
- File.open dest, 'wb' do |io|
- Marshal.dump specs_index, io
- end
- end
-end
diff --git a/lib/rubygems/install_default_message.rb b/lib/rubygems/install_default_message.rb
index f68fd2fd04..0640eaaf08 100644
--- a/lib/rubygems/install_default_message.rb
+++ b/lib/rubygems/install_default_message.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
-require 'rubygems'
-require 'rubygems/user_interaction'
+
+require_relative "../rubygems"
+require_relative "user_interaction"
##
# A post-install hook that displays "Successfully installed
diff --git a/lib/rubygems/install_message.rb b/lib/rubygems/install_message.rb
index 3c13888a84..a24e26b918 100644
--- a/lib/rubygems/install_message.rb
+++ b/lib/rubygems/install_message.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
-require 'rubygems'
-require 'rubygems/user_interaction'
+
+require_relative "../rubygems"
+require_relative "user_interaction"
##
# A default post-install hook that displays "Successfully installed
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index 54a3950b64..aad207a718 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems'
-require 'rubygems/security_option'
+require_relative "../rubygems"
+require_relative "security_option"
##
# Mixin methods for install and update options for Gem::Commands
@@ -18,106 +19,106 @@ module Gem::InstallUpdateOptions
# Add the install/update options to the option parser.
def add_install_update_options
- add_option(:"Install/Update", '-i', '--install-dir DIR',
- 'Gem repository directory to get installed',
- 'gems') do |value, options|
+ add_option(:"Install/Update", "-i", "--install-dir DIR",
+ "Gem repository directory to get installed",
+ "gems") do |value, options|
options[:install_dir] = File.expand_path(value)
end
- add_option(:"Install/Update", '-n', '--bindir DIR',
- 'Directory where executables will be',
- 'placed when the gem is installed') do |value, options|
+ add_option(:"Install/Update", "-n", "--bindir DIR",
+ "Directory where executables will be",
+ "placed when the gem is installed") do |value, options|
options[:bin_dir] = File.expand_path(value)
end
- add_option(:"Install/Update", '--document [TYPES]', Array,
- 'Generate documentation for installed gems',
- 'List the documentation types you wish to',
- 'generate. For example: rdoc,ri') do |value, options|
+ add_option(:"Install/Update", "--document [TYPES]", Array,
+ "Generate documentation for installed gems",
+ "List the documentation types you wish to",
+ "generate. For example: rdoc,ri") do |value, options|
options[:document] = case value
when nil then %w[ri]
when false then []
- else value
- end
+ else value
+ end
end
- add_option(:"Install/Update", '--build-root DIR',
- 'Temporary installation root. Useful for building',
- 'packages. Do not use this when installing remote gems.') do |value, options|
+ add_option(:"Install/Update", "--build-root DIR",
+ "Temporary installation root. Useful for building",
+ "packages. Do not use this when installing remote gems.") do |value, options|
options[:build_root] = File.expand_path(value)
end
- add_option(:"Install/Update", '--vendor',
- 'Install gem into the vendor directory.',
- 'Only for use by gem repackagers.') do |value, options|
+ add_option(:"Install/Update", "--vendor",
+ "Install gem into the vendor directory.",
+ "Only for use by gem repackagers.") do |_value, options|
unless Gem.vendor_dir
- raise OptionParser::InvalidOption.new 'your platform is not supported'
+ raise Gem::OptionParser::InvalidOption.new "your platform is not supported"
end
options[:vendor] = true
options[:install_dir] = Gem.vendor_dir
end
- add_option(:"Install/Update", '-N', '--no-document',
- 'Disable documentation generation') do |value, options|
+ add_option(:"Install/Update", "-N", "--no-document",
+ "Disable documentation generation") do |_value, options|
options[:document] = []
end
- add_option(:"Install/Update", '-E', '--[no-]env-shebang',
+ add_option(:"Install/Update", "-E", "--[no-]env-shebang",
"Rewrite the shebang line on installed",
"scripts to use /usr/bin/env") do |value, options|
options[:env_shebang] = value
end
- add_option(:"Install/Update", '-f', '--[no-]force',
- 'Force gem to install, bypassing dependency',
- 'checks') do |value, options|
+ add_option(:"Install/Update", "-f", "--[no-]force",
+ "Force gem to install, bypassing dependency",
+ "checks") do |value, options|
options[:force] = value
end
- add_option(:"Install/Update", '-w', '--[no-]wrappers',
- 'Use bin wrappers for executables',
- 'Not available on dosish platforms') do |value, options|
+ add_option(:"Install/Update", "-w", "--[no-]wrappers",
+ "Use bin wrappers for executables",
+ "Not available on dosish platforms") do |value, options|
options[:wrappers] = value
end
add_security_option
- add_option(:"Install/Update", '--ignore-dependencies',
- 'Do not install any required dependent gems') do |value, options|
+ add_option(:"Install/Update", "--ignore-dependencies",
+ "Do not install any required dependent gems") do |value, options|
options[:ignore_dependencies] = value
end
- add_option(:"Install/Update", '--[no-]format-executable',
- 'Make installed executable names match Ruby.',
- 'If Ruby is ruby18, foo_exec will be',
- 'foo_exec18') do |value, options|
+ add_option(:"Install/Update", "--[no-]format-executable",
+ "Make installed executable names match Ruby.",
+ "If Ruby is ruby18, foo_exec will be",
+ "foo_exec18") do |value, options|
options[:format_executable] = value
end
- add_option(:"Install/Update", '--[no-]user-install',
- 'Install in user\'s home directory instead',
- 'of GEM_HOME.') do |value, options|
+ add_option(:"Install/Update", "--[no-]user-install",
+ "Install in user's home directory instead",
+ "of GEM_HOME.") do |value, options|
options[:user_install] = value
end
add_option(:"Install/Update", "--development",
"Install additional development",
- "dependencies") do |value, options|
+ "dependencies") do |_value, options|
options[:development] = true
options[:dev_shallow] = true
end
add_option(:"Install/Update", "--development-all",
"Install development dependencies for all",
- "gems (including dev deps themselves)") do |value, options|
+ "gems (including dev deps themselves)") do |_value, options|
options[:development] = true
options[:dev_shallow] = false
end
add_option(:"Install/Update", "--conservative",
"Don't attempt to upgrade gems already",
- "meeting version requirement") do |value, options|
+ "meeting version requirement") do |_value, options|
options[:conservative] = true
options[:minimal_deps] = true
end
@@ -133,49 +134,49 @@ module Gem::InstallUpdateOptions
options[:post_install_message] = value
end
- add_option(:"Install/Update", '-g', '--file [FILE]',
- 'Read from a gem dependencies API file and',
- 'install the listed gems') do |v,o|
- v = Gem::GEM_DEP_FILES.find do |file|
+ add_option(:"Install/Update", "-g", "--file [FILE]",
+ "Read from a gem dependencies API file and",
+ "install the listed gems") do |v,_o|
+ v ||= Gem::GEM_DEP_FILES.find do |file|
File.exist? file
- end unless v
+ end
unless v
- message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ', '})"
+ message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ", "})"
- raise OptionParser::InvalidArgument,
+ raise Gem::OptionParser::InvalidArgument,
"cannot find gem dependencies file #{message}"
end
options[:gemdeps] = v
end
- add_option(:"Install/Update", '--without GROUPS', Array,
- 'Omit the named groups (comma separated)',
- 'when installing from a gem dependencies',
- 'file') do |v,o|
- options[:without_groups].concat v.map {|without| without.intern }
+ add_option(:"Install/Update", "--without GROUPS", Array,
+ "Omit the named groups (comma separated)",
+ "when installing from a gem dependencies",
+ "file") do |v,_o|
+ options[:without_groups].concat v.map(&:intern)
end
- add_option(:"Install/Update", '--default',
- 'Add the gem\'s full specification to',
- 'specifications/default and extract only its bin') do |v,o|
+ add_option(:"Install/Update", "--default",
+ "Add the gem's full specification to",
+ "specifications/default and extract only its bin") do |v,_o|
options[:install_as_default] = v
end
- add_option(:"Install/Update", '--explain',
- 'Rather than install the gems, indicate which would',
- 'be installed') do |v,o|
+ add_option(:"Install/Update", "--explain",
+ "Rather than install the gems, indicate which would",
+ "be installed") do |v,_o|
options[:explain] = v
end
- add_option(:"Install/Update", '--[no-]lock',
- 'Create a lock file (when used with -g/--file)') do |v,o|
+ add_option(:"Install/Update", "--[no-]lock",
+ "Create a lock file (when used with -g/--file)") do |v,_o|
options[:lock] = v
end
- add_option(:"Install/Update", '--[no-]suggestions',
- 'Suggest alternates when gems are not found') do |v,o|
+ add_option(:"Install/Update", "--[no-]suggestions",
+ "Suggest alternates when gems are not found") do |v,_o|
options[:suggest_alternate] = v
end
end
@@ -185,7 +186,7 @@ module Gem::InstallUpdateOptions
def install_update_options
{
- :document => %w[ri],
+ document: %w[ri],
}
end
@@ -193,7 +194,6 @@ module Gem::InstallUpdateOptions
# Default description for the gem install and update commands.
def install_update_defaults_str
- '--document=ri'
+ "--document=ri"
end
-
end
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 7af51056b7..8f6f9a5aa8 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -1,17 +1,17 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/command'
-require 'rubygems/installer_uninstaller_utils'
-require 'rubygems/exceptions'
-require 'rubygems/deprecate'
-require 'rubygems/package'
-require 'rubygems/ext'
-require 'rubygems/user_interaction'
+require_relative "installer_uninstaller_utils"
+require_relative "exceptions"
+require_relative "deprecate"
+require_relative "package"
+require_relative "ext"
+require_relative "user_interaction"
##
# The installer installs the files contained in the .gem into the Gem.home.
@@ -68,19 +68,28 @@ class Gem::Installer
@path_warning = false
- @install_lock = Mutex.new
-
class << self
- ##
- # True if we've warned about PATH not including Gem.bindir
+ #
+ # Changes in rubygems to lazily loading `rubygems/command` (in order to
+ # lazily load `optparse` as a side effect) affect bundler's custom installer
+ # which uses `Gem::Command` without requiring it (up until bundler 2.2.29).
+ # This hook is to compensate for that missing require.
+ #
+ # TODO: Remove when rubygems no longer supports running on bundler older
+ # than 2.2.29.
- attr_accessor :path_warning
+ def inherited(klass)
+ if klass.name == "Bundler::RubyGemsGemInstaller"
+ require "rubygems/command"
+ end
+
+ super(klass)
+ end
##
- # Certain aspects of the install process are not thread-safe. This lock is
- # used to allow multiple threads to install Gems at the same time.
+ # True if we've warned about PATH not including Gem.bindir
- attr_reader :install_lock
+ attr_accessor :path_warning
##
# Overrides the executable format.
@@ -117,14 +126,14 @@ class Gem::Installer
@spec = spec
end
- def extract_files(destination_dir, pattern = '*')
+ def extract_files(destination_dir, pattern = "*")
FileUtils.mkdir_p destination_dir
spec.files.each do |file|
file = File.join destination_dir, file
next if File.exist? file
FileUtils.mkdir_p File.dirname(file)
- File.open file, 'w' do |fp|
+ File.open file, "w" do |fp|
fp.puts "# #{file}"
end
end
@@ -169,7 +178,7 @@ class Gem::Installer
# :post_install_message:: Print gem post install message if true
def initialize(package, options={})
- require 'fileutils'
+ require "fileutils"
@options = options
@package = package
@@ -180,10 +189,12 @@ class Gem::Installer
@package.prog_mode = options[:prog_mode]
@package.data_mode = options[:data_mode]
- if options[:user_install]
- @gem_home = Gem.user_dir
- @bin_dir = Gem.bindir gem_home unless options[:bin_dir]
- @plugins_dir = Gem.plugindir(gem_home)
+ if @gem_home == Gem.user_dir
+ # If we get here, then one of the following likely happened:
+ # - `--user-install` was specified
+ # - `Gem::PathSupport#home` fell back to `Gem.user_dir`
+ # - GEM_HOME was manually set to `Gem.user_dir`
+
check_that_user_bin_dir_is_in_path
end
end
@@ -193,8 +204,8 @@ class Gem::Installer
#
# If +@force+ is set +filename+ is overwritten.
#
- # If +filename+ exists and is a RubyGems wrapper for different gem the user
- # is consulted.
+ # If +filename+ exists and it is a RubyGems wrapper for a different gem, then
+ # the user is consulted.
#
# If +filename+ exists and +@bin_dir+ is Gem.default_bindir (/usr/local) the
# user is consulted.
@@ -211,34 +222,45 @@ class Gem::Installer
ruby_executable = false
existing = nil
- File.open generated_bin, 'rb' do |io|
- next unless io.gets =~ /^#!/ # shebang
+ File.open generated_bin, "rb" do |io|
+ line = io.gets
+ shebang = /^#!.*ruby/o
+
+ # TruffleRuby uses a bash prelude in default launchers
+ if load_relative_enabled? || RUBY_ENGINE == "truffleruby"
+ until line.nil? || shebang.match?(line) do
+ line = io.gets
+ end
+ end
+
+ next unless line&.match?(shebang)
+
io.gets # blankline
- # TODO detect a specially formatted comment instead of trying
- # to run a regexp against Ruby code.
- next unless io.gets =~ /This file was generated by RubyGems/
+ # TODO: detect a specially formatted comment instead of trying
+ # to find a string inside Ruby code.
+ next unless io.gets&.include?("This file was generated by RubyGems")
ruby_executable = true
- existing = io.read.slice(%r{
+ existing = io.read.slice(/
^\s*(
gem \s |
load \s Gem\.bin_path\( |
load \s Gem\.activate_bin_path\(
)
(['"])(.*?)(\2),
- }x, 3)
+ /x, 3)
end
return if spec.name == existing
# somebody has written to RubyGems' directory, overwrite, too bad
- return if Gem.default_bindir != @bin_dir and not ruby_executable
+ return if Gem.default_bindir != @bin_dir && !ruby_executable
question = "#{spec.name}'s executable \"#{filename}\" conflicts with ".dup
if ruby_executable
- question << (existing || 'an unknown executable')
+ question << (existing || "an unknown executable")
return if ask_yes_no "#{question}\nOverwrite the executable?", false
@@ -267,8 +289,6 @@ class Gem::Installer
def spec
@package.spec
- rescue Gem::Package::Error => e
- raise Gem::InstallError, "invalid gem: #{e.message}"
end
##
@@ -285,8 +305,6 @@ class Gem::Installer
def install
pre_install_checks
- FileUtils.rm_f File.join gem_home, 'specifications', spec.spec_name
-
run_pre_install_hooks
# Set loaded_from to ensure extension_dir is correct
@@ -301,7 +319,7 @@ class Gem::Installer
FileUtils.rm_rf spec.extension_dir
dir_mode = options[:dir_mode]
- FileUtils.mkdir_p gem_dir, :mode => dir_mode && 0755
+ FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755
if @options[:install_as_default]
extract_bin
@@ -326,39 +344,37 @@ class Gem::Installer
say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil?
- Gem::Installer.install_lock.synchronize { Gem::Specification.reset }
+ Gem::Specification.add_spec(spec)
+
+ load_plugin
run_post_install_hooks
spec
-
- # TODO This rescue is in the wrong place. What is raising this exception?
- # move this rescue to around the code that actually might raise it.
- rescue Zlib::GzipFile::Error
- raise Gem::InstallError, "gzip error installing #{gem}"
+ rescue Errno::EACCES => e
+ # Permission denied - /path/to/foo
+ raise Gem::FilePermissionError, e.message.split(" - ").last
end
def run_pre_install_hooks # :nodoc:
Gem.pre_install_hooks.each do |hook|
- if hook.call(self) == false
- location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/
+ next unless hook.call(self) == false
+ location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/
- message = "pre-install hook#{location} failed for #{spec.full_name}"
- raise Gem::InstallError, message
- end
+ message = "pre-install hook#{location} failed for #{spec.full_name}"
+ raise Gem::InstallError, message
end
end
def run_post_build_hooks # :nodoc:
Gem.post_build_hooks.each do |hook|
- if hook.call(self) == false
- FileUtils.rm_rf gem_dir
+ next unless hook.call(self) == false
+ FileUtils.rm_rf gem_dir
- location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/
+ location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/
- message = "post-build hook#{location} failed for #{spec.full_name}"
- raise Gem::InstallError, message
- end
+ message = "post-build hook#{location} failed for #{spec.full_name}"
+ raise Gem::InstallError, message
end
end
@@ -374,11 +390,11 @@ class Gem::Installer
# we'll be installing into.
def installed_specs
- @specs ||= begin
+ @installed_specs ||= begin
specs = []
Gem::Util.glob_files_in_dir("*.gemspec", File.join(gem_home, "specifications")).each do |path|
- spec = Gem::Specification.load path.tap(&Gem::UNTAINT)
+ spec = Gem::Specification.load path
specs << spec if spec
end
@@ -404,10 +420,10 @@ class Gem::Installer
# True if the gems in the system satisfy +dependency+.
def installation_satisfies_dependency?(dependency)
- return true if @options[:development] and dependency.type == :development
+ return true if @options[:development] && dependency.type == :development
return true if installed_specs.detect {|s| dependency.matches_spec? s }
return false if @only_install_dir
- not dependency.matching_specs.empty?
+ !dependency.matching_specs.empty?
end
##
@@ -440,23 +456,20 @@ class Gem::Installer
# specifications directory.
def write_spec
- File.open spec_file, 'w' do |file|
- spec.installed_by_version = Gem.rubygems_version
-
- file.puts spec.to_ruby_for_cache
+ spec.installed_by_version = Gem.rubygems_version
- file.fsync rescue nil # for filesystems without fsync(2)
- end
+ Gem.write_binary(spec_file, spec.to_ruby_for_cache)
end
##
# Writes the full .gemspec specification (in Ruby) to the gem home's
# specifications/default directory.
+ #
+ # In contrast to #write_spec, this keeps file lists, so the `gem contents`
+ # command works.
def write_default_spec
- File.open(default_spec_file, "w") do |file|
- file.puts spec.to_ruby
- end
+ Gem.write_binary(default_spec_file, spec.to_ruby)
end
##
@@ -466,7 +479,7 @@ class Gem::Installer
if Gem.win_platform?
script_name = formatted_program_filename(filename) + ".bat"
script_path = File.join bindir, File.basename(script_name)
- File.open script_path, 'w' do |file|
+ File.open script_path, "w" do |file|
file.puts windows_stub_script(bindir, filename)
end
@@ -475,28 +488,19 @@ class Gem::Installer
end
def generate_bin # :nodoc:
- return if spec.executables.nil? or spec.executables.empty?
+ return if spec.executables.nil? || spec.executables.empty?
ensure_writable_dir @bin_dir
spec.executables.each do |filename|
- filename.tap(&Gem::UNTAINT)
bin_path = File.join gem_dir, spec.bindir, filename
-
- unless File.exist? bin_path
- if File.symlink? bin_path
- alert_warning "`#{bin_path}` is dangling symlink pointing to `#{File.readlink bin_path}`"
- else
- alert_warning "`#{bin_path}` does not exist, maybe `gem pristine #{spec.name}` will fix it?"
- end
- next
- end
+ next unless File.exist? bin_path
mode = File.stat(bin_path).mode
- dir_mode = options[:prog_mode] || (mode | 0111)
+ dir_mode = options[:prog_mode] || (mode | 0o111)
unless dir_mode == mode
- require 'fileutils'
+ require "fileutils"
FileUtils.chmod dir_mode, bin_path
end
@@ -511,7 +515,7 @@ class Gem::Installer
end
def generate_plugins # :nodoc:
- latest = Gem::Installer.install_lock.synchronize { Gem::Specification.latest_spec_for(spec.name) }
+ latest = Gem::Specification.latest_spec_for(spec.name)
return if latest && latest.version > spec.version
ensure_writable_dir @plugins_dir
@@ -521,6 +525,8 @@ class Gem::Installer
else
regenerate_plugins_for(spec, @plugins_dir)
end
+ rescue ArgumentError => e
+ raise e, "#{latest.name} #{latest.version} #{spec.name} #{spec.version}: #{e.message}"
end
##
@@ -528,17 +534,17 @@ class Gem::Installer
#--
# The Windows script is generated in addition to the regular one due to a
# bug or misfeature in the Windows shell's pipe. See
- # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/193379
+ # https://blade.ruby-lang.org/ruby-talk/193379
def generate_bin_script(filename, bindir)
bin_script_path = File.join bindir, formatted_program_filename(filename)
- require 'fileutils'
+ require "fileutils"
FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers
- File.open bin_script_path, 'wb', 0755 do |file|
+ File.open bin_script_path, "wb", 0o755 do |file|
file.print app_script_text(filename)
- file.chmod(options[:prog_mode] || 0755)
+ file.chmod(options[:prog_mode] || 0o755)
end
verbose bin_script_path
@@ -557,13 +563,13 @@ class Gem::Installer
if File.exist? dst
if File.symlink? dst
link = File.readlink(dst).split File::SEPARATOR
- cur_version = Gem::Version.create(link[-3].sub(/^.*-/, ''))
+ cur_version = Gem::Version.create(link[-3].sub(/^.*-/, ""))
return if spec.version < cur_version
end
File.unlink dst
end
- FileUtils.symlink src, dst, :verbose => Gem.configuration.really_verbose
+ FileUtils.symlink src, dst, verbose: Gem.configuration.really_verbose
rescue NotImplementedError, SystemCallError
alert_warning "Unable to use symlinks, installing wrapper"
generate_bin_script filename, bindir
@@ -585,9 +591,8 @@ class Gem::Installer
#
def shebang(bin_file_name)
- ruby_name = RbConfig::CONFIG['ruby_install_name'] if @env_shebang
path = File.join gem_dir, spec.bindir, bin_file_name
- first_line = File.open(path, "rb") {|file| file.gets } || ""
+ first_line = File.open(path, "rb", &:gets) || ""
if first_line.start_with?("#!")
# Preserve extra words on shebang line, like "-w". Thanks RPA.
@@ -598,7 +603,7 @@ class Gem::Installer
if which = Gem.configuration[:custom_shebang]
# replace bin_file_name with "ruby" to avoid endless loops
- which = which.gsub(/ #{bin_file_name}$/," #{RbConfig::CONFIG['ruby_install_name']}")
+ which = which.gsub(/ #{bin_file_name}$/," #{ruby_install_name}")
which = which.gsub(/\$(\w+)/) do
case $1
@@ -614,14 +619,12 @@ class Gem::Installer
end
"#!#{which}"
- elsif not ruby_name
- "#!#{Gem.ruby}#{opts}"
- elsif opts
- "#!/bin/sh\n'exec' #{ruby_name.dump} '-x' \"$0\" \"$@\"\n#{shebang}"
- else
+ elsif @env_shebang
# Create a plain shebang line.
@env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
- "#!#{@env_path} #{ruby_name}"
+ "#!#{@env_path} #{ruby_install_name}"
+ else
+ "#{bash_prolog_script}#!#{Gem.ruby}#{opts}"
end
end
@@ -631,7 +634,6 @@ class Gem::Installer
def ensure_loadable_spec
ruby = spec.to_ruby_for_cache
- ruby.tap(&Gem::UNTAINT)
begin
eval ruby
@@ -652,36 +654,41 @@ class Gem::Installer
def process_options # :nodoc:
@options = {
- :bin_dir => nil,
- :env_shebang => false,
- :force => false,
- :only_install_dir => false,
- :post_install_message => true,
+ bin_dir: nil,
+ env_shebang: false,
+ force: false,
+ only_install_dir: false,
+ post_install_message: true,
}.merge options
@env_shebang = options[:env_shebang]
@force = options[:force]
@install_dir = options[:install_dir]
- @gem_home = options[:install_dir] || Gem.dir
- @plugins_dir = Gem.plugindir(@gem_home)
+ @user_install = options[:user_install]
@ignore_dependencies = options[:ignore_dependencies]
@format_executable = options[:format_executable]
@wrappers = options[:wrappers]
@only_install_dir = options[:only_install_dir]
+ @bin_dir = options[:bin_dir]
+ @development = options[:development]
+ @build_root = options[:build_root]
+
+ @build_args = options[:build_args]
+
+ @gem_home = @install_dir || user_install_dir || Gem.dir
+
# If the user has asked for the gem to be installed in a directory that is
# the system gem directory, then use the system bin directory, else create
# (or use) a new bin dir under the gem_home.
- @bin_dir = options[:bin_dir] || Gem.bindir(gem_home)
- @development = options[:development]
- @build_root = options[:build_root]
+ @bin_dir ||= Gem.bindir(@gem_home)
- @build_args = options[:build_args] || Gem::Command.build_args
+ @plugins_dir = Gem.plugindir(@gem_home)
unless @build_root.nil?
- @bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, ''))
- @gem_home = File.join(@build_root, @gem_home.gsub(/^[a-zA-Z]:/, ''))
- @plugins_dir = File.join(@build_root, @plugins_dir.gsub(/^[a-zA-Z]:/, ''))
+ @bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, ""))
+ @gem_home = File.join(@build_root, @gem_home.gsub(/^[a-zA-Z]:/, ""))
+ @plugins_dir = File.join(@build_root, @plugins_dir.gsub(/^[a-zA-Z]:/, ""))
alert_warning "You build with buildroot.\n Build root: #{@build_root}\n Bin dir: #{@bin_dir}\n Gem home: #{@gem_home}\n Plugins dir: #{@plugins_dir}"
end
end
@@ -692,7 +699,7 @@ class Gem::Installer
user_bin_dir = @bin_dir || Gem.bindir(gem_home)
user_bin_dir = user_bin_dir.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
- path = ENV['PATH']
+ path = ENV["PATH"]
path = path.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
if Gem.win_platform?
@@ -703,7 +710,7 @@ class Gem::Installer
path = path.split(File::PATH_SEPARATOR)
unless path.include? user_bin_dir
- unless !Gem.win_platform? && (path.include? user_bin_dir.sub(ENV['HOME'], '~'))
+ unless !Gem.win_platform? && (path.include? user_bin_dir.sub(ENV["HOME"], "~"))
alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables will not run."
self.class.path_warning = true
end
@@ -711,24 +718,27 @@ class Gem::Installer
end
def verify_gem_home # :nodoc:
- FileUtils.mkdir_p gem_home, :mode => options[:dir_mode] && 0755
- raise Gem::FilePermissionError, gem_home unless File.writable?(gem_home)
+ FileUtils.mkdir_p gem_home, mode: options[:dir_mode] && 0o755
end
def verify_spec
- unless spec.name =~ Gem::Specification::VALID_NAME_PATTERN
+ unless Gem::Specification::VALID_NAME_PATTERN.match?(spec.name)
raise Gem::InstallError, "#{spec} has an invalid name"
end
- if spec.raw_require_paths.any?{|path| path =~ /\R/ }
+ if spec.raw_require_paths.any? {|path| path =~ /\R/ }
raise Gem::InstallError, "#{spec} has an invalid require_paths"
end
- if spec.extensions.any?{|ext| ext =~ /\R/ }
+ if spec.extensions.any? {|ext| ext =~ /\R/ }
raise Gem::InstallError, "#{spec} has an invalid extensions"
end
- unless spec.specification_version.to_s =~ /\A\d+\z/
+ if /\R/.match?(spec.platform.to_s)
+ raise Gem::InstallError, "#{spec.platform} is an invalid platform"
+ end
+
+ unless /\A\d+\z/.match?(spec.specification_version.to_s)
raise Gem::InstallError, "#{spec} has an invalid specification_version"
end
@@ -745,9 +755,9 @@ class Gem::Installer
# Return the text for an application file.
def app_script_text(bin_file_name)
- # note that the `load` lines cannot be indented, as old RG versions match
+ # NOTE: that the `load` lines cannot be indented, as old RG versions match
# against the beginning of the line
- return <<-TEXT
+ <<-TEXT
#{shebang bin_file_name}
#
# This file was generated by RubyGems.
@@ -757,14 +767,14 @@ class Gem::Installer
#
require 'rubygems'
-
+#{gemdeps_load(spec.name)}
version = "#{Gem::Requirement.default_prerelease}"
str = ARGV.first
if str
str = str.b[/\\A_(.*)_\\z/, 1]
if str and Gem::Version.correct?(str)
- version = str
+ #{explicit_version_requirement(spec.name)}
ARGV.shift
end
end
@@ -778,18 +788,36 @@ end
TEXT
end
+ def gemdeps_load(name)
+ return "" if name == "bundler"
+
+ <<-TEXT
+
+Gem.use_gemdeps
+TEXT
+ end
+
+ def explicit_version_requirement(name)
+ code = "version = str"
+ return code unless name == "bundler"
+
+ code += <<-TEXT
+
+ ENV['BUNDLER_VERSION'] = str
+TEXT
+ end
+
##
# return the stub script text used to launch the true Ruby script
def windows_stub_script(bindir, bin_file_name)
- rb_config = RbConfig::CONFIG
rb_topdir = RbConfig::TOPDIR || File.dirname(rb_config["bindir"])
# get ruby executable file name from RbConfig
- ruby_exe = "#{rb_config['RUBY_INSTALL_NAME']}#{rb_config['EXEEXT']}"
+ ruby_exe = "#{rb_config["RUBY_INSTALL_NAME"]}#{rb_config["EXEEXT"]}"
ruby_exe = "ruby.exe" if ruby_exe.empty?
- if File.exist?(File.join bindir, ruby_exe)
+ if File.exist?(File.join(bindir, ruby_exe))
# stub & ruby.exe within same folder. Portable
<<-TEXT
@ECHO OFF
@@ -797,7 +825,7 @@ TEXT
TEXT
elsif bindir.downcase.start_with? rb_topdir.downcase
# stub within ruby folder, but not standard bin. Portable
- require 'pathname'
+ require "pathname"
from = Pathname.new bindir
to = Pathname.new "#{rb_topdir}/bin"
rel = to.relative_path_from from
@@ -819,7 +847,7 @@ TEXT
# configure scripts and rakefiles or mkrf_conf files.
def build_extensions
- builder = Gem::Ext::Builder.new spec, @build_args
+ builder = Gem::Ext::Builder.new spec, build_args
builder.build_extensions
end
@@ -906,17 +934,17 @@ TEXT
# extensions.
def write_build_info_file
- return if @build_args.empty?
+ return if build_args.empty?
- build_info_dir = File.join gem_home, 'build_info'
+ build_info_dir = File.join gem_home, "build_info"
dir_mode = options[:dir_mode]
- FileUtils.mkdir_p build_info_dir, :mode => dir_mode && 0755
+ FileUtils.mkdir_p build_info_dir, mode: dir_mode && 0o755
build_info_file = File.join build_info_dir, "#{spec.full_name}.info"
- File.open build_info_file, 'w' do |io|
- @build_args.each do |arg|
+ File.open build_info_file, "w" do |io|
+ build_args.each do |arg|
io.puts arg
end
end
@@ -928,17 +956,85 @@ TEXT
# Writes the .gem file to the cache directory
def write_cache_file
- cache_file = File.join gem_home, 'cache', spec.file_name
+ cache_file = File.join gem_home, "cache", spec.file_name
@package.copy_to cache_file
end
def ensure_writable_dir(dir) # :nodoc:
begin
- Dir.mkdir dir, *[options[:dir_mode] && 0755].compact
+ Dir.mkdir dir, *[options[:dir_mode] && 0o755].compact
rescue SystemCallError
raise unless File.directory? dir
end
raise Gem::FilePermissionError.new(dir) unless File.writable? dir
end
+
+ private
+
+ def user_install_dir
+ # never install to user home in --build-root mode
+ return unless @build_root.nil?
+
+ # Please note that @user_install might have three states:
+ # * `true`: `--user-install`
+ # * `false`: `--no-user-install` and
+ # * `nil`: option was not specified
+ if @user_install || (@user_install.nil? && Gem.default_user_install)
+ Gem.user_dir
+ end
+ end
+
+ def build_args
+ @build_args ||= begin
+ require_relative "command"
+ Gem::Command.build_args
+ end
+ end
+
+ def rb_config
+ RbConfig::CONFIG
+ end
+
+ def ruby_install_name
+ rb_config["ruby_install_name"]
+ end
+
+ def load_relative_enabled?
+ rb_config["LIBRUBY_RELATIVE"] == "yes"
+ end
+
+ def bash_prolog_script
+ if load_relative_enabled?
+ script = +<<~EOS
+ bindir="${0%/*}"
+ EOS
+
+ script << %(exec "$bindir/#{ruby_install_name}" "-x" "$0" "$@"\n)
+
+ <<~EOS
+ #!/bin/sh
+ # -*- ruby -*-
+ _=_\\
+ =begin
+ #{script.chomp}
+ =end
+ EOS
+ else
+ ""
+ end
+ end
+
+ def load_plugin
+ specs = Gem::Specification.find_all_by_name(spec.name)
+ # If old version already exists, this plugin isn't loaded
+ # immediately. It's for avoiding a case that multiple versions
+ # are loaded at the same time.
+ return unless specs.size == 1
+
+ plugin_files = spec.plugins.map do |plugin|
+ File.join(@plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}")
+ end
+ Gem.load_plugin_files(plugin_files)
+ end
end
diff --git a/lib/rubygems/installer_test_case.rb b/lib/rubygems/installer_test_case.rb
deleted file mode 100644
index 416dac7be6..0000000000
--- a/lib/rubygems/installer_test_case.rb
+++ /dev/null
@@ -1,247 +0,0 @@
-# frozen_string_literal: true
-require 'rubygems/test_case'
-require 'rubygems/installer'
-
-class Gem::Installer
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :bin_dir
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :build_args
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :gem_dir
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :force
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :format
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :gem_home
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :env_shebang
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :ignore_dependencies
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :format_executable
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :security_policy
-
- ##
- # Available through requiring rubygems/installer_test_case
-
- attr_writer :wrappers
-end
-
-##
-# A test case for Gem::Installer.
-
-class Gem::InstallerTestCase < Gem::TestCase
- def setup
- super
-
- Gem::Installer.path_warning = false
- end
-
- ##
- # The path where installed executables live
-
- def util_inst_bindir
- File.join @gemhome, "bin"
- end
-
- ##
- # Adds an executable named "executable" to +spec+ with the given +shebang+.
- #
- # The executable is also written to the bin dir in @tmpdir and the installed
- # gem directory for +spec+.
-
- def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby", bindir = "bin")
- spec.executables = %w[executable]
- spec.bindir = bindir
-
- exec_path = spec.bin_file "executable"
- write_file exec_path do |io|
- io.puts shebang
- end
-
- bin_path = File.join @tempdir, "bin", "executable"
- write_file bin_path do |io|
- io.puts shebang
- end
- end
-
- ##
- # Creates the following instance variables:
- #
- # @spec::
- # a spec named 'a', intended for regular installs
- #
- # @gem::
- # the path to a built gem from @spec
- #
- # And returns a Gem::Installer for the @spec that installs into @gemhome
-
- def setup_base_installer(force = true)
- @gem = setup_base_gem
- util_installer @spec, @gemhome, false, force
- end
-
- ##
- # Creates the following instance variables:
- #
- # @spec::
- # a spec named 'a', intended for regular installs
- #
- # And returns a gem built for the @spec
-
- def setup_base_gem
- @spec = setup_base_spec
- util_build_gem @spec
- @spec.cache_file
- end
-
- ##
- # Sets up a generic specification for testing the rubygems installer
- #
- # And returns it
-
- def setup_base_spec
- quick_gem 'a' do |spec|
- util_make_exec spec
- end
- end
-
- ##
- # Creates the following instance variables:
- #
- # @spec::
- # a spec named 'a', intended for regular installs
- # @user_spec::
- # a spec named 'b', intended for user installs
- #
- # @gem::
- # the path to a built gem from @spec
- # @user_gem::
- # the path to a built gem from @user_spec
- #
- # And returns a Gem::Installer for the @user_spec that installs into Gem.user_dir
-
- def setup_base_user_installer
- @user_spec = quick_gem 'b' do |spec|
- util_make_exec spec
- end
-
- util_build_gem @user_spec
-
- @user_gem = @user_spec.cache_file
-
- util_installer @user_spec, Gem.user_dir, :user
- end
-
- ##
- # Sets up the base @gem, builds it and returns an installer for it.
- #
- def util_setup_installer(&block)
- @gem = setup_base_gem
-
- util_setup_gem(&block)
- end
-
- ##
- # Builds the @spec gem and returns an installer for it. The built gem
- # includes:
- #
- # bin/executable
- # lib/code.rb
- # ext/a/mkrf_conf.rb
-
- def util_setup_gem(ui = @ui, force = true)
- @spec.files << File.join('lib', 'code.rb')
- @spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb')
-
- Dir.chdir @tempdir do
- FileUtils.mkdir_p 'bin'
- FileUtils.mkdir_p 'lib'
- FileUtils.mkdir_p File.join('ext', 'a')
-
- File.open File.join('bin', 'executable'), 'w' do |f|
- f.puts "raise 'ran executable'"
- end
-
- File.open File.join('lib', 'code.rb'), 'w' do |f|
- f.puts '1'
- end
-
- File.open File.join('ext', 'a', 'mkrf_conf.rb'), 'w' do |f|
- f << <<-EOF
- File.open 'Rakefile', 'w' do |rf| rf.puts "task :default" end
- EOF
- end
-
- yield @spec if block_given?
-
- use_ui ui do
- FileUtils.rm_f @gem
-
- @gem = Gem::Package.build @spec
- end
- end
-
- Gem::Installer.at @gem, :force => force
- end
-
- ##
- # Creates an installer for +spec+ that will install into +gem_home+. If
- # +user+ is true a user-install will be performed.
-
- def util_installer(spec, gem_home, user=false, force=true)
- Gem::Installer.at(spec.cache_file,
- :install_dir => gem_home,
- :user_install => user,
- :force => force)
- end
-
- @@symlink_supported = nil
-
- # This is needed for Windows environment without symlink support enabled (the default
- # for non admin) to be able to skip test for features using symlinks.
- def symlink_supported?
- if @@symlink_supported.nil?
- begin
- File.symlink("", "")
- rescue Errno::ENOENT, Errno::EEXIST
- @@symlink_supported = true
- rescue NotImplementedError, SystemCallError
- @@symlink_supported = false
- end
- end
- @@symlink_supported
- end
-end
diff --git a/lib/rubygems/installer_uninstaller_utils.rb b/lib/rubygems/installer_uninstaller_utils.rb
index 2c8b7c635e..c5c2a52bab 100644
--- a/lib/rubygems/installer_uninstaller_utils.rb
+++ b/lib/rubygems/installer_uninstaller_utils.rb
@@ -4,17 +4,16 @@
# Helper methods for both Gem::Installer and Gem::Uninstaller
module Gem::InstallerUninstallerUtils
-
def regenerate_plugins_for(spec, plugins_dir)
plugins = spec.plugins
return if plugins.empty?
- require 'pathname'
+ require "pathname"
spec.plugins.each do |plugin|
plugin_script_path = File.join plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}"
- File.open plugin_script_path, 'wb' do |file|
+ File.open plugin_script_path, "wb" do |file|
file.puts "require_relative '#{Pathname.new(plugin).relative_path_from(Pathname.new(plugins_dir))}'"
end
@@ -25,5 +24,4 @@ module Gem::InstallerUninstallerUtils
def remove_plugins_for(spec, plugins_dir)
FileUtils.rm_f Gem::Util.glob_files_in_dir("#{spec.name}#{Gem.plugin_suffix_pattern}", plugins_dir)
end
-
end
diff --git a/lib/rubygems/local_remote_options.rb b/lib/rubygems/local_remote_options.rb
index 2d0509eb03..51a61213a5 100644
--- a/lib/rubygems/local_remote_options.rb
+++ b/lib/rubygems/local_remote_options.rb
@@ -1,32 +1,32 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'uri'
-require 'rubygems'
+require_relative "vendor/uri/lib/uri"
+require_relative "../rubygems"
##
# Mixin methods for local and remote Gem::Command options.
module Gem::LocalRemoteOptions
-
##
- # Allows OptionParser to handle HTTP URIs.
+ # Allows Gem::OptionParser to handle HTTP URIs.
def accept_uri_http
- OptionParser.accept URI::HTTP do |value|
+ Gem::OptionParser.accept Gem::URI::HTTP do |value|
begin
- uri = URI.parse value
- rescue URI::InvalidURIError
- raise OptionParser::InvalidArgument, value
+ uri = Gem::URI.parse value
+ rescue Gem::URI::InvalidURIError
+ raise Gem::OptionParser::InvalidArgument, value
end
valid_uri_schemes = ["http", "https", "file", "s3"]
unless valid_uri_schemes.include?(uri.scheme)
- msg = "Invalid uri scheme for #{value}\nPreface URLs with one of #{valid_uri_schemes.map{|s| "#{s}://" }}"
+ msg = "Invalid uri scheme for #{value}\nPreface URLs with one of #{valid_uri_schemes.map {|s| "#{s}://" }}"
raise ArgumentError, msg
end
@@ -38,18 +38,18 @@ module Gem::LocalRemoteOptions
# Add local/remote options to the command line parser.
def add_local_remote_options
- add_option(:"Local/Remote", '-l', '--local',
- 'Restrict operations to the LOCAL domain') do |value, options|
+ add_option(:"Local/Remote", "-l", "--local",
+ "Restrict operations to the LOCAL domain") do |_value, options|
options[:domain] = :local
end
- add_option(:"Local/Remote", '-r', '--remote',
- 'Restrict operations to the REMOTE domain') do |value, options|
+ add_option(:"Local/Remote", "-r", "--remote",
+ "Restrict operations to the REMOTE domain") do |_value, options|
options[:domain] = :remote
end
- add_option(:"Local/Remote", '-b', '--both',
- 'Allow LOCAL and REMOTE operations') do |value, options|
+ add_option(:"Local/Remote", "-b", "--both",
+ "Allow LOCAL and REMOTE operations") do |_value, options|
options[:domain] = :both
end
@@ -64,10 +64,9 @@ module Gem::LocalRemoteOptions
# Add the --bulk-threshold option
def add_bulk_threshold_option
- add_option(:"Local/Remote", '-B', '--bulk-threshold COUNT',
+ add_option(:"Local/Remote", "-B", "--bulk-threshold COUNT",
"Threshold for switching to bulk",
- "synchronization (default #{Gem.configuration.bulk_threshold})") do
- |value, options|
+ "synchronization (default #{Gem.configuration.bulk_threshold})") do |value, _options|
Gem.configuration.bulk_threshold = value.to_i
end
end
@@ -76,9 +75,8 @@ module Gem::LocalRemoteOptions
# Add the --clear-sources option
def add_clear_sources_option
- add_option(:"Local/Remote", '--clear-sources',
- 'Clear the gem sources') do |value, options|
-
+ add_option(:"Local/Remote", "--clear-sources",
+ "Clear the gem sources") do |_value, options|
Gem.sources = nil
options[:sources_cleared] = true
end
@@ -90,9 +88,9 @@ module Gem::LocalRemoteOptions
def add_proxy_option
accept_uri_http
- add_option(:"Local/Remote", '-p', '--[no-]http-proxy [URL]', URI::HTTP,
- 'Use HTTP proxy for remote operations') do |value, options|
- options[:http_proxy] = (value == false) ? :no_proxy : value
+ add_option(:"Local/Remote", "-p", "--[no-]http-proxy [URL]", Gem::URI::HTTP,
+ "Use HTTP proxy for remote operations") do |value, options|
+ options[:http_proxy] = value == false ? :no_proxy : value
Gem.configuration[:http_proxy] = options[:http_proxy]
end
end
@@ -103,10 +101,9 @@ module Gem::LocalRemoteOptions
def add_source_option
accept_uri_http
- add_option(:"Local/Remote", '-s', '--source URL', URI::HTTP,
- 'Append URL to list of remote gem sources') do |source, options|
-
- source << '/' if source !~ /\/\z/
+ add_option(:"Local/Remote", "-s", "--source URL", Gem::URI::HTTP,
+ "Append URL to list of remote gem sources") do |source, options|
+ source << "/" unless source.end_with?("/")
if options.delete :sources_cleared
Gem.sources = [source]
@@ -120,8 +117,8 @@ module Gem::LocalRemoteOptions
# Add the --update-sources option
def add_update_sources_option
- add_option(:Deprecated, '-u', '--[no-]update-sources',
- 'Update local source cache') do |value, options|
+ add_option(:Deprecated, "-u", "--[no-]update-sources",
+ "Update local source cache") do |value, _options|
Gem.configuration.update_sources = value
end
end
@@ -146,5 +143,4 @@ module Gem::LocalRemoteOptions
def remote?
options[:domain] == :remote || options[:domain] == :both
end
-
end
diff --git a/lib/rubygems/mock_gem_ui.rb b/lib/rubygems/mock_gem_ui.rb
deleted file mode 100644
index ec244fb7c6..0000000000
--- a/lib/rubygems/mock_gem_ui.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: true
-require 'rubygems/user_interaction'
-
-##
-# This Gem::StreamUI subclass records input and output to StringIO for
-# retrieval during tests.
-
-class Gem::MockGemUi < Gem::StreamUI
- ##
- # Raised when you haven't provided enough input to your MockGemUi
-
- class InputEOFError < RuntimeError
- def initialize(question)
- super "Out of input for MockGemUi on #{question.inspect}"
- end
- end
-
- class TermError < RuntimeError
- attr_reader :exit_code
-
- def initialize(exit_code)
- super
- @exit_code = exit_code
- end
- end
- class SystemExitException < RuntimeError; end
-
- module TTY
-
- attr_accessor :tty
-
- def tty?()
- @tty = true unless defined?(@tty)
- @tty
- end
-
- def noecho
- yield self
- end
- end
-
- def initialize(input = "")
- require 'stringio'
- ins = StringIO.new input
- outs = StringIO.new
- errs = StringIO.new
-
- ins.extend TTY
- outs.extend TTY
- errs.extend TTY
-
- super ins, outs, errs, true
-
- @terminated = false
- end
-
- def ask(question)
- raise InputEOFError, question if @ins.eof?
-
- super
- end
-
- def input
- @ins.string
- end
-
- def output
- @outs.string
- end
-
- def error
- @errs.string
- end
-
- def terminated?
- @terminated
- end
-
- def terminate_interaction(status=0)
- @terminated = true
-
- raise TermError, status if status != 0
- raise SystemExitException
- end
-end
diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb
index 3d0afa3094..3f4a6fcf3d 100644
--- a/lib/rubygems/name_tuple.rb
+++ b/lib/rubygems/name_tuple.rb
@@ -1,18 +1,17 @@
# frozen_string_literal: true
+
##
#
# Represents a gem of name +name+ at +version+ of +platform+. These
# wrap the data returned from the indexes.
class Gem::NameTuple
- def initialize(name, version, platform="ruby")
+ def initialize(name, version, platform=Gem::Platform::RUBY)
@name = name
@version = version
- unless platform.kind_of? Gem::Platform
- platform = "ruby" if !platform or platform.empty?
- end
-
+ platform &&= platform.to_s
+ platform = Gem::Platform::RUBY if !platform || platform.empty?
@platform = platform
end
@@ -31,7 +30,7 @@ class Gem::NameTuple
# [name, version, platform] tuples.
def self.to_basic(list)
- list.map {|t| t.to_a }
+ list.map(&:to_a)
end
##
@@ -48,11 +47,11 @@ class Gem::NameTuple
def full_name
case @platform
- when nil, 'ruby', ''
+ when nil, "", Gem::Platform::RUBY
"#{@name}-#{@version}"
else
"#{@name}-#{@version}-#{@platform}"
- end.dup.tap(&Gem::UNTAINT)
+ end
end
##
@@ -86,12 +85,11 @@ class Gem::NameTuple
"#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>"
end
- alias to_s inspect # :nodoc:
+ alias_method :to_s, :inspect # :nodoc:
def <=>(other)
- [@name, @version, @platform == Gem::Platform::RUBY ? -1 : 1] <=>
- [other.name, other.version,
- other.platform == Gem::Platform::RUBY ? -1 : 1]
+ [@name, @version, Gem::Platform.sort_priority(@platform)] <=>
+ [other.name, other.version, Gem::Platform.sort_priority(other.platform)]
end
include Comparable
@@ -103,8 +101,8 @@ class Gem::NameTuple
def ==(other)
case other
when self.class
- @name == other.name and
- @version == other.version and
+ @name == other.name &&
+ @version == other.version &&
@platform == other.platform
when Array
to_a == other
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index a4ae3e9ea5..1d5d764237 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -1,9 +1,17 @@
# frozen_string_literal: true
-#--
+
+# rubocop:disable Style/AsciiComments
+
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
-#++
-#
+
+# rubocop:enable Style/AsciiComments
+
+require_relative "../rubygems"
+require_relative "security"
+require_relative "user_interaction"
+
+##
# Example using a Gem::Package
#
# Builds a .gem file given a Gem::Specification. A .gem file is a tarball
@@ -41,10 +49,6 @@
# #files are the files in the .gem tar file, not the Ruby files in the gem
# #extract_files and #contents automatically call #verify
-require "rubygems"
-require 'rubygems/security'
-require 'rubygems/user_interaction'
-
class Gem::Package
include Gem::UserInteraction
@@ -55,9 +59,9 @@ class Gem::Package
def initialize(message, source = nil)
if source
- @path = source.path
+ @path = source.is_a?(String) ? source : source.path
- message = message + " in #{path}" if path
+ message += " in #{path}" if path
end
super message
@@ -66,8 +70,13 @@ class Gem::Package
class PathError < Error
def initialize(destination, destination_dir)
- super "installing into parent path %s of %s is not allowed" %
- [destination, destination_dir]
+ super format("installing into parent path %s of %s is not allowed", destination, destination_dir)
+ end
+ end
+
+ class SymlinkError < Error
+ def initialize(name, destination, destination_dir)
+ super format("installing symlink '%s' pointing to parent path %s of %s is not allowed", name, destination, destination_dir)
end
end
@@ -139,18 +148,18 @@ class Gem::Package
def self.new(gem, security_policy = nil)
gem = if gem.is_a?(Gem::Package::Source)
- gem
- elsif gem.respond_to? :read
- Gem::Package::IOSource.new gem
- else
- Gem::Package::FileSource.new gem
- end
+ gem
+ elsif gem.respond_to? :read
+ Gem::Package::IOSource.new gem
+ else
+ Gem::Package::FileSource.new gem
+ end
- return super unless Gem::Package == self
+ return super unless self == Gem::Package
return super unless gem.present?
return super unless gem.start
- return super unless gem.start.include? 'MD5SUM ='
+ return super unless gem.start.include? "MD5SUM ="
Gem::Package::Old.new gem
end
@@ -170,22 +179,22 @@ class Gem::Package
tar = Gem::Package::TarReader.new io
tar.each_entry do |entry|
case entry.full_name
- when 'metadata' then
+ when "metadata" then
metadata = entry.read
- when 'metadata.gz' then
+ when "metadata.gz" then
metadata = Gem::Util.gunzip entry.read
end
end
end
- return spec, metadata
+ [spec, metadata]
end
##
# Creates a new package that will read or write to the file +gem+.
def initialize(gem, security_policy) # :notnew:
- require 'zlib'
+ require "zlib"
@gem = gem
@@ -221,9 +230,9 @@ class Gem::Package
end
end
- tar.add_file_signed 'checksums.yaml.gz', 0444, @signer do |io|
+ tar.add_file_signed "checksums.yaml.gz", 0o444, @signer do |io|
gzip_to io do |gz_io|
- YAML.dump checksums_by_algorithm, gz_io
+ Psych.dump checksums_by_algorithm, gz_io
end
end
end
@@ -233,7 +242,7 @@ class Gem::Package
# and adds this file to the +tar+.
def add_contents(tar) # :nodoc:
- digests = tar.add_file_signed 'data.tar.gz', 0444, @signer do |io|
+ digests = tar.add_file_signed "data.tar.gz", 0o444, @signer do |io|
gzip_to io do |gz_io|
Gem::Package::TarWriter.new gz_io do |data_tar|
add_files data_tar
@@ -241,7 +250,7 @@ class Gem::Package
end
end
- @checksums['data.tar.gz'] = digests
+ @checksums["data.tar.gz"] = digests
end
##
@@ -258,8 +267,8 @@ class Gem::Package
next unless stat.file?
tar.add_file_simple file, stat.mode, stat.size do |dst_io|
- File.open file, 'rb' do |src_io|
- dst_io.write src_io.read 16384 until src_io.eof?
+ File.open file, "rb" do |src_io|
+ copy_stream(src_io, dst_io)
end
end
end
@@ -269,13 +278,13 @@ class Gem::Package
# Adds the package's Gem::Specification to the +tar+ file
def add_metadata(tar) # :nodoc:
- digests = tar.add_file_signed 'metadata.gz', 0444, @signer do |io|
+ digests = tar.add_file_signed "metadata.gz", 0o444, @signer do |io|
gzip_to io do |gz_io|
gz_io.write @spec.to_yaml
end
end
- @checksums['metadata.gz'] = digests
+ @checksums["metadata.gz"] = digests
end
##
@@ -327,7 +336,7 @@ EOM
gem_tar = Gem::Package::TarReader.new io
gem_tar.each do |entry|
- next unless entry.full_name == 'data.tar.gz'
+ next unless entry.full_name == "data.tar.gz"
open_tar_gz entry do |pkg_tar|
pkg_tar.each do |contents_entry|
@@ -338,6 +347,8 @@ EOM
return @contents
end
end
+ rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e
+ raise Gem::Package::FormatError.new e.message, @gem
end
##
@@ -346,18 +357,21 @@ EOM
def digest(entry) # :nodoc:
algorithms = if @checksums
- @checksums.keys
- else
- [Gem::Security::DIGEST_NAME].compact
- end
-
- algorithms.each do |algorithm|
- digester = Gem::Security.create_digest(algorithm)
+ @checksums.to_h {|algorithm, _| [algorithm, Gem::Security.create_digest(algorithm)] }
+ elsif Gem::Security::DIGEST_NAME
+ { Gem::Security::DIGEST_NAME => Gem::Security.create_digest(Gem::Security::DIGEST_NAME) }
+ end
- digester << entry.read(16384) until entry.eof?
+ return @digests if algorithms.nil? || algorithms.empty?
- entry.rewind
+ buf = String.new(capacity: 16_384, encoding: Encoding::BINARY)
+ until entry.eof?
+ entry.readpartial(16_384, buf)
+ algorithms.each_value {|digester| digester << buf }
+ end
+ entry.rewind
+ algorithms.each do |algorithm, digester|
@digests[algorithm][entry.full_name] = digester
end
@@ -373,19 +387,21 @@ EOM
def extract_files(destination_dir, pattern = "*")
verify unless @spec
- FileUtils.mkdir_p destination_dir, :mode => dir_mode && 0755
+ FileUtils.mkdir_p destination_dir, mode: dir_mode && 0o755
@gem.with_read_io do |io|
reader = Gem::Package::TarReader.new io
reader.each do |entry|
- next unless entry.full_name == 'data.tar.gz'
+ next unless entry.full_name == "data.tar.gz"
extract_tar_gz entry, destination_dir, pattern
- return # ignore further entries
+ break # ignore further entries
end
end
+ rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e
+ raise Gem::Package::FormatError.new e.message, @gem
end
##
@@ -400,46 +416,71 @@ EOM
# extracted.
def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
- directories = [] if dir_mode
+ destination_dir = File.realpath(destination_dir)
+
+ directories = []
+ symlinks = []
+
open_tar_gz io do |tar|
tar.each do |entry|
- next unless File.fnmatch pattern, entry.full_name, File::FNM_DOTMATCH
+ full_name = entry.full_name
+ next unless File.fnmatch pattern, full_name, File::FNM_DOTMATCH
+
+ destination = install_location full_name, destination_dir
- destination = install_location entry.full_name, destination_dir
+ if entry.symlink?
+ link_target = entry.header.linkname
+ real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination))
+
+ raise Gem::Package::SymlinkError.new(full_name, real_destination, destination_dir) unless
+ normalize_path(real_destination).start_with? normalize_path(destination_dir + "/")
+
+ symlinks << [full_name, link_target, destination, real_destination]
+ end
FileUtils.rm_rf destination
- mkdir_options = {}
- mkdir_options[:mode] = dir_mode ? 0755 : (entry.header.mode if entry.directory?)
mkdir =
if entry.directory?
destination
else
File.dirname destination
end
- directories << mkdir if directories
- mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name
-
- File.open destination, 'wb' do |out|
- out.write entry.read
- FileUtils.chmod file_mode(entry.header.mode), destination
- end if entry.file?
+ unless directories.include?(mkdir)
+ FileUtils.mkdir_p mkdir, mode: dir_mode ? 0o755 : (entry.header.mode if entry.directory?)
+ directories << mkdir
+ end
- File.symlink(entry.header.linkname, destination) if entry.symlink?
+ if entry.file?
+ File.open(destination, "wb") {|out| copy_stream(entry, out) }
+ FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination
+ end
verbose destination
end
end
- if directories
- directories.uniq!
+ symlinks.each do |name, target, destination, real_destination|
+ if File.exist?(real_destination)
+ File.symlink(target, destination)
+ else
+ alert_warning "#{@spec.full_name} ships with a dangling symlink named #{name} pointing to missing #{target} file. Ignoring"
+ end
+ end
+
+ if dir_mode
File.chmod(dir_mode, *directories)
end
end
def file_mode(mode) # :nodoc:
- ((mode & 0111).zero? ? data_mode : prog_mode) || mode
+ ((mode & 0o111).zero? ? data_mode : prog_mode) ||
+ # If we're not using one of the default modes, then we're going to fall
+ # back to the mode from the tarball. In this case we need to mask it down
+ # to fit into 2^16 bits (the maximum value for a mode in CRuby since it
+ # gets put into an unsigned short).
+ (mode & ((1 << 16) - 1))
end
##
@@ -464,25 +505,14 @@ EOM
def install_location(filename, destination_dir) # :nodoc:
raise Gem::Package::PathError.new(filename, destination_dir) if
- filename.start_with? '/'
+ filename.start_with? "/"
- destination_dir = File.expand_path(File.realpath(destination_dir))
- destination = File.expand_path(File.join(destination_dir, filename))
+ destination_dir = File.realpath(destination_dir)
+ destination = File.expand_path(filename, destination_dir)
raise Gem::Package::PathError.new(destination, destination_dir) unless
- destination.start_with? destination_dir + '/'
+ normalize_path(destination).start_with? normalize_path(destination_dir + "/")
- begin
- real_destination = File.expand_path(File.realpath(destination))
- rescue
- # it's fine if the destination doesn't exist, because rm -rf'ing it can't cause any damage
- nil
- else
- raise Gem::Package::PathError.new(real_destination, destination_dir) unless
- real_destination.start_with? destination_dir + '/'
- end
-
- destination.tap(&Gem::UNTAINT)
destination
end
@@ -494,30 +524,14 @@ EOM
end
end
- def mkdir_p_safe(mkdir, mkdir_options, destination_dir, file_name)
- destination_dir = File.realpath(File.expand_path(destination_dir))
- parts = mkdir.split(File::SEPARATOR)
- parts.reduce do |path, basename|
- path = File.realpath(path) unless path == ""
- path = File.expand_path(path + File::SEPARATOR + basename)
- lstat = File.lstat path rescue nil
- if !lstat || !lstat.directory?
- unless normalize_path(path).start_with? normalize_path(destination_dir) and (FileUtils.mkdir path, **mkdir_options rescue false)
- raise Gem::Package::PathError.new(file_name, destination_dir)
- end
- end
- path
- end
- end
-
##
# Loads a Gem::Specification from the TarEntry +entry+
def load_spec(entry) # :nodoc:
case entry.full_name
- when 'metadata' then
+ when "metadata" then
@spec = Gem::Specification.from_yaml entry.read
- when 'metadata.gz' then
+ when "metadata.gz" then
Zlib::GzipReader.wrap(entry, external_encoding: Encoding::UTF_8) do |gzio|
@spec = Gem::Specification.from_yaml gzio.read
end
@@ -541,7 +555,7 @@ EOM
def read_checksums(gem)
Gem.load_yaml
- @checksums = gem.seek 'checksums.yaml.gz' do |entry|
+ @checksums = gem.seek "checksums.yaml.gz" do |entry|
Zlib::GzipReader.wrap entry do |gz_io|
Gem::SafeYAML.safe_load gz_io.read
end
@@ -553,7 +567,7 @@ EOM
# certificate and key are not present only checksum generation is set up.
def setup_signer(signer_options: {})
- passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
+ passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"]
if @spec.signing_key
@signer =
Gem::Security::Signer.new(
@@ -564,10 +578,10 @@ EOM
)
@spec.signing_key = nil
- @spec.cert_chain = @signer.cert_chain.map {|cert| cert.to_s }
+ @spec.cert_chain = @signer.cert_chain.map(&:to_s)
else
@signer = Gem::Security::Signer.new nil, nil, passphrase
- @spec.cert_chain = @signer.cert_chain.map {|cert| cert.to_pem } if
+ @spec.cert_chain = @signer.cert_chain.map(&:to_pem) if
@signer.cert_chain
end
end
@@ -609,8 +623,7 @@ EOM
verify_checksums @digests, @checksums
- @security_policy.verify_signatures @spec, @digests, @signatures if
- @security_policy
+ @security_policy&.verify_signatures @spec, @digests, @signatures
true
rescue Gem::Security::Exception
@@ -619,7 +632,7 @@ EOM
raise
rescue Errno::ENOENT => e
raise Gem::Package::FormatError.new e.message
- rescue Gem::Package::TarInvalidError => e
+ rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e
raise Gem::Package::FormatError.new e.message, @gem
end
@@ -660,10 +673,10 @@ EOM
case file_name
when "metadata", "metadata.gz" then
load_spec entry
- when 'data.tar.gz' then
+ when "data.tar.gz" then
verify_gz entry
end
- rescue
+ rescue StandardError
warn "Exception while verifying #{@gem.path}"
raise
end
@@ -677,16 +690,16 @@ EOM
end
unless @spec
- raise Gem::Package::FormatError.new 'package metadata is missing', @gem
+ raise Gem::Package::FormatError.new "package metadata is missing", @gem
end
- unless @files.include? 'data.tar.gz'
+ unless @files.include? "data.tar.gz"
raise Gem::Package::FormatError.new \
- 'package content (data.tar.gz) is missing', @gem
+ "package content (data.tar.gz) is missing", @gem
end
- if duplicates = @files.group_by {|f| f }.select {|k,v| v.size > 1 }.map(&:first) and duplicates.any?
- raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(', ')})"
+ if (duplicates = @files.group_by {|f| f }.select {|_k,v| v.size > 1 }.map(&:first)) && duplicates.any?
+ raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(", ")})"
end
end
@@ -695,19 +708,30 @@ EOM
def verify_gz(entry) # :nodoc:
Zlib::GzipReader.wrap entry do |gzio|
- gzio.read 16384 until gzio.eof? # gzip checksum verification
+ # TODO: read into a buffer once zlib supports it
+ gzio.read 16_384 until gzio.eof? # gzip checksum verification
end
rescue Zlib::GzipFile::Error => e
raise Gem::Package::FormatError.new(e.message, entry.full_name)
end
+
+ if RUBY_ENGINE == "truffleruby"
+ def copy_stream(src, dst) # :nodoc:
+ dst.write src.read
+ end
+ else
+ def copy_stream(src, dst) # :nodoc:
+ IO.copy_stream(src, dst)
+ end
+ end
end
-require 'rubygems/package/digest_io'
-require 'rubygems/package/source'
-require 'rubygems/package/file_source'
-require 'rubygems/package/io_source'
-require 'rubygems/package/old'
-require 'rubygems/package/tar_header'
-require 'rubygems/package/tar_reader'
-require 'rubygems/package/tar_reader/entry'
-require 'rubygems/package/tar_writer'
+require_relative "package/digest_io"
+require_relative "package/source"
+require_relative "package/file_source"
+require_relative "package/io_source"
+require_relative "package/old"
+require_relative "package/tar_header"
+require_relative "package/tar_reader"
+require_relative "package/tar_reader/entry"
+require_relative "package/tar_writer"
diff --git a/lib/rubygems/package/digest_io.rb b/lib/rubygems/package/digest_io.rb
index 4736f76d93..f04ab97462 100644
--- a/lib/rubygems/package/digest_io.rb
+++ b/lib/rubygems/package/digest_io.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# IO wrapper that creates digests of contents written to the IO it wraps.
@@ -35,7 +36,7 @@ class Gem::Package::DigestIO
yield digest_io
- return digests
+ digests
end
##
diff --git a/lib/rubygems/package/file_source.rb b/lib/rubygems/package/file_source.rb
index 114a950c77..d9717e0f2a 100644
--- a/lib/rubygems/package/file_source.rb
+++ b/lib/rubygems/package/file_source.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The primary source of gems is a file on disk, including all usages
# internal to rubygems.
@@ -22,10 +23,10 @@ class Gem::Package::FileSource < Gem::Package::Source # :nodoc: all
end
def with_write_io(&block)
- File.open path, 'wb', &block
+ File.open path, "wb", &block
end
def with_read_io(&block)
- File.open path, 'rb', &block
+ File.open path, "rb", &block
end
end
diff --git a/lib/rubygems/package/io_source.rb b/lib/rubygems/package/io_source.rb
index 7d7383110b..227835dfce 100644
--- a/lib/rubygems/package/io_source.rb
+++ b/lib/rubygems/package/io_source.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Supports reading and writing gems from/to a generic IO object. This is
# useful for other applications built on top of rubygems, such as
@@ -32,10 +33,14 @@ class Gem::Package::IOSource < Gem::Package::Source # :nodoc: all
def with_read_io
yield io
+ ensure
+ io.rewind
end
def with_write_io
yield io
+ ensure
+ io.rewind
end
def path
diff --git a/lib/rubygems/package/old.rb b/lib/rubygems/package/old.rb
index 25317ef23f..1a13ac3e29 100644
--- a/lib/rubygems/package/old.rb
+++ b/lib/rubygems/package/old.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
@@ -19,8 +20,8 @@ class Gem::Package::Old < Gem::Package
# cannot be written.
def initialize(gem, security_policy)
- require 'fileutils'
- require 'zlib'
+ require "fileutils"
+ require "zlib"
Gem.load_yaml
@contents = nil
@@ -41,7 +42,7 @@ class Gem::Package::Old < Gem::Package
read_until_dashes io # spec
header = file_list io
- @contents = header.map {|file| file['path'] }
+ @contents = header.map {|file| file["path"] }
end
end
@@ -59,7 +60,7 @@ class Gem::Package::Old < Gem::Package
raise Gem::Exception, errstr unless header
header.each do |entry|
- full_name = entry['path']
+ full_name = entry["path"]
destination = install_location full_name, destination_dir
@@ -69,17 +70,17 @@ class Gem::Package::Old < Gem::Package
file_data << line
end
- file_data = file_data.strip.unpack("m")[0]
+ file_data = file_data.strip.unpack1("m")
file_data = Zlib::Inflate.inflate file_data
raise Gem::Package::FormatError, "#{full_name} in #{@gem} is corrupt" if
- file_data.length != entry['size'].to_i
+ file_data.length != entry["size"].to_i
FileUtils.rm_rf destination
- FileUtils.mkdir_p File.dirname(destination), :mode => dir_mode && 0755
+ FileUtils.mkdir_p File.dirname(destination), mode: dir_mode && 0o755
- File.open destination, 'wb', file_mode(entry['mode']) do |out|
+ File.open destination, "wb", file_mode(entry["mode"]) do |out|
out.write file_data
end
@@ -119,7 +120,7 @@ class Gem::Package::Old < Gem::Package
loop do
line = io.gets
- return if line.chomp == '__END__'
+ return if line.chomp == "__END__"
break unless line
end
@@ -145,7 +146,7 @@ class Gem::Package::Old < Gem::Package
begin
@spec = Gem::Specification.from_yaml yaml
- rescue YAML::SyntaxError
+ rescue Psych::SyntaxError
raise Gem::Exception, "Failed to parse gem specification out of gem file"
end
rescue ArgumentError
@@ -160,7 +161,7 @@ class Gem::Package::Old < Gem::Package
return true unless @security_policy
raise Gem::Security::Exception,
- 'old format gems do not contain signatures and cannot be verified' if
+ "old format gems do not contain signatures and cannot be verified" if
@security_policy.verify_data
true
diff --git a/lib/rubygems/package/source.rb b/lib/rubygems/package/source.rb
index 69701e55e9..8c44f8c305 100644
--- a/lib/rubygems/package/source.rb
+++ b/lib/rubygems/package/source.rb
@@ -1,3 +1,4 @@
# frozen_string_literal: true
+
class Gem::Package::Source # :nodoc:
end
diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb
index ce9b49e3eb..087f13f6c9 100644
--- a/lib/rubygems/package/tar_header.rb
+++ b/lib/rubygems/package/tar_header.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
-#--
+
+# rubocop:disable Style/AsciiComments
+
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
-#++
+
+# rubocop:enable Style/AsciiComments
##
#--
@@ -53,42 +56,42 @@ class Gem::Package::TarHeader
##
# Pack format for a tar header
- PACK_FORMAT = 'a100' + # name
- 'a8' + # mode
- 'a8' + # uid
- 'a8' + # gid
- 'a12' + # size
- 'a12' + # mtime
- 'a7a' + # chksum
- 'a' + # typeflag
- 'a100' + # linkname
- 'a6' + # magic
- 'a2' + # version
- 'a32' + # uname
- 'a32' + # gname
- 'a8' + # devmajor
- 'a8' + # devminor
- 'a155' # prefix
+ PACK_FORMAT = "a100" + # name
+ "a8" + # mode
+ "a8" + # uid
+ "a8" + # gid
+ "a12" + # size
+ "a12" + # mtime
+ "a7a" + # chksum
+ "a" + # typeflag
+ "a100" + # linkname
+ "a6" + # magic
+ "a2" + # version
+ "a32" + # uname
+ "a32" + # gname
+ "a8" + # devmajor
+ "a8" + # devminor
+ "a155" # prefix
##
# Unpack format for a tar header
- UNPACK_FORMAT = 'A100' + # name
- 'A8' + # mode
- 'A8' + # uid
- 'A8' + # gid
- 'A12' + # size
- 'A12' + # mtime
- 'A8' + # checksum
- 'A' + # typeflag
- 'A100' + # linkname
- 'A6' + # magic
- 'A2' + # version
- 'A32' + # uname
- 'A32' + # gname
- 'A8' + # devmajor
- 'A8' + # devminor
- 'A155' # prefix
+ UNPACK_FORMAT = "A100" + # name
+ "A8" + # mode
+ "A8" + # uid
+ "A8" + # gid
+ "A12" + # size
+ "A12" + # mtime
+ "A8" + # checksum
+ "A" + # typeflag
+ "A100" + # linkname
+ "A6" + # magic
+ "A2" + # version
+ "A32" + # uname
+ "A32" + # gname
+ "A8" + # devmajor
+ "A8" + # devminor
+ "A155" # prefix
attr_reader(*FIELDS)
@@ -99,32 +102,33 @@ class Gem::Package::TarHeader
def self.from(stream)
header = stream.read 512
- empty = (EMPTY_HEADER == header)
+ empty = (header == EMPTY_HEADER)
fields = header.unpack UNPACK_FORMAT
- new :name => fields.shift,
- :mode => strict_oct(fields.shift),
- :uid => oct_or_256based(fields.shift),
- :gid => oct_or_256based(fields.shift),
- :size => strict_oct(fields.shift),
- :mtime => strict_oct(fields.shift),
- :checksum => strict_oct(fields.shift),
- :typeflag => fields.shift,
- :linkname => fields.shift,
- :magic => fields.shift,
- :version => strict_oct(fields.shift),
- :uname => fields.shift,
- :gname => fields.shift,
- :devmajor => strict_oct(fields.shift),
- :devminor => strict_oct(fields.shift),
- :prefix => fields.shift,
-
- :empty => empty
+ new name: fields.shift,
+ mode: strict_oct(fields.shift),
+ uid: oct_or_256based(fields.shift),
+ gid: oct_or_256based(fields.shift),
+ size: strict_oct(fields.shift),
+ mtime: strict_oct(fields.shift),
+ checksum: strict_oct(fields.shift),
+ typeflag: fields.shift,
+ linkname: fields.shift,
+ magic: fields.shift,
+ version: strict_oct(fields.shift),
+ uname: fields.shift,
+ gname: fields.shift,
+ devmajor: strict_oct(fields.shift),
+ devminor: strict_oct(fields.shift),
+ prefix: fields.shift,
+
+ empty: empty
end
def self.strict_oct(str)
- return str.strip.oct if str.strip =~ /\A[0-7]*\z/
+ str.strip!
+ return str.oct if /\A[0-7]*\z/.match?(str)
raise ArgumentError, "#{str.inspect} is not an octal string"
end
@@ -134,7 +138,8 @@ class Gem::Package::TarHeader
# \ff flags a negative 256-based number
# In case we have a match, parse it as a signed binary value
# in big-endian order, except that the high-order bit is ignored.
- return str.unpack('N2').last if str =~ /\A[\x80\xff]/n
+
+ return str.unpack1("@4N") if /\A[\x80\xff]/n.match?(str)
strict_oct(str)
end
@@ -146,21 +151,23 @@ class Gem::Package::TarHeader
raise ArgumentError, ":name, :size, :prefix and :mode required"
end
- vals[:uid] ||= 0
- vals[:gid] ||= 0
- vals[:mtime] ||= 0
- vals[:checksum] ||= ""
- vals[:typeflag] = "0" if vals[:typeflag].nil? || vals[:typeflag].empty?
- vals[:magic] ||= "ustar"
- vals[:version] ||= "00"
- vals[:uname] ||= "wheel"
- vals[:gname] ||= "wheel"
- vals[:devmajor] ||= 0
- vals[:devminor] ||= 0
-
- FIELDS.each do |name|
- instance_variable_set "@#{name}", vals[name]
- end
+ @checksum = vals[:checksum] || ""
+ @devmajor = vals[:devmajor] || 0
+ @devminor = vals[:devminor] || 0
+ @gid = vals[:gid] || 0
+ @gname = vals[:gname] || "wheel"
+ @linkname = vals[:linkname]
+ @magic = vals[:magic] || "ustar"
+ @mode = vals[:mode]
+ @mtime = vals[:mtime] || 0
+ @name = vals[:name]
+ @prefix = vals[:prefix]
+ @size = vals[:size]
+ @typeflag = vals[:typeflag]
+ @typeflag = "0" if @typeflag.nil? || @typeflag.empty?
+ @uid = vals[:uid] || 0
+ @uname = vals[:uname] || "wheel"
+ @version = vals[:version] || "00"
@empty = vals[:empty]
end
@@ -173,23 +180,23 @@ class Gem::Package::TarHeader
end
def ==(other) # :nodoc:
- self.class === other and
- @checksum == other.checksum and
- @devmajor == other.devmajor and
- @devminor == other.devminor and
- @gid == other.gid and
- @gname == other.gname and
- @linkname == other.linkname and
- @magic == other.magic and
- @mode == other.mode and
- @mtime == other.mtime and
- @name == other.name and
- @prefix == other.prefix and
- @size == other.size and
- @typeflag == other.typeflag and
- @uid == other.uid and
- @uname == other.uname and
- @version == other.version
+ self.class === other &&
+ @checksum == other.checksum &&
+ @devmajor == other.devmajor &&
+ @devminor == other.devminor &&
+ @gid == other.gid &&
+ @gname == other.gname &&
+ @linkname == other.linkname &&
+ @magic == other.magic &&
+ @mode == other.mode &&
+ @mtime == other.mtime &&
+ @name == other.name &&
+ @prefix == other.prefix &&
+ @size == other.size &&
+ @typeflag == other.typeflag &&
+ @uid == other.uid &&
+ @uname == other.uname &&
+ @version == other.version
end
def to_s # :nodoc:
@@ -208,7 +215,7 @@ class Gem::Package::TarHeader
private
def calculate_checksum(header)
- header.unpack("C*").inject {|a, b| a + b }
+ header.sum(0)
end
def header(checksum = @checksum)
@@ -238,6 +245,6 @@ class Gem::Package::TarHeader
end
def oct(num, len)
- "%0#{len}o" % num
+ format("%0#{len}o", num)
end
end
diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb
index e7c5620533..25f9b2f945 100644
--- a/lib/rubygems/package/tar_reader.rb
+++ b/lib/rubygems/package/tar_reader.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
-#--
+
+# rubocop:disable Style/AsciiComments
+
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
-#++
+
+# rubocop:enable Style/AsciiComments
##
# TarReader reads tar files and allows iteration over their items
@@ -11,11 +14,6 @@ class Gem::Package::TarReader
include Enumerable
##
- # Raised if the tar IO is not seekable
-
- class UnexpectedEOF < StandardError; end
-
- ##
# Creates a new TarReader on +io+ and yields it to the block, if given.
def self.new(io)
@@ -53,44 +51,23 @@ class Gem::Package::TarReader
def each
return enum_for __method__ unless block_given?
- use_seek = @io.respond_to?(:seek)
-
until @io.eof? do
- header = Gem::Package::TarHeader.from @io
- return if header.empty?
+ begin
+ header = Gem::Package::TarHeader.from @io
+ rescue ArgumentError => e
+ # Specialize only exceptions from Gem::Package::TarHeader.strict_oct
+ raise e unless e.message.match?(/ is not an octal string$/)
+ raise Gem::Package::TarInvalidError, e.message
+ end
+ return if header.empty?
entry = Gem::Package::TarReader::Entry.new header, @io
- size = entry.header.size
-
yield entry
-
- skip = (512 - (size % 512)) % 512
- pending = size - entry.bytes_read
-
- if use_seek
- begin
- # avoid reading if the @io supports seeking
- @io.seek pending, IO::SEEK_CUR
- pending = 0
- rescue Errno::EINVAL
- end
- end
-
- # if seeking isn't supported or failed
- while pending > 0 do
- bytes_read = @io.read([pending, 4096].min).size
- raise UnexpectedEOF if @io.eof?
- pending -= bytes_read
- end
-
- @io.read skip # discard trailing zeros
-
- # make sure nobody can use #read, #getc or #rewind anymore
entry.close
end
end
- alias each_entry each
+ alias_method :each_entry, :each
##
# NOTE: Do not call #rewind during #each
@@ -115,10 +92,10 @@ class Gem::Package::TarReader
return unless found
- return yield found
+ yield found
ensure
rewind
end
end
-require 'rubygems/package/tar_reader/entry'
+require_relative "tar_reader/entry"
diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb
index 5865599d3a..5e9d9af5c6 100644
--- a/lib/rubygems/package/tar_reader/entry.rb
+++ b/lib/rubygems/package/tar_reader/entry.rb
@@ -1,14 +1,31 @@
# frozen_string_literal: true
-#++
+
+# rubocop:disable Style/AsciiComments
+
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
-#--
+
+# rubocop:enable Style/AsciiComments
##
# Class for reading entries out of a tar file
class Gem::Package::TarReader::Entry
##
+ # Creates a new tar entry for +header+ that will be read from +io+
+ # If a block is given, the entry is yielded and then closed.
+
+ def self.open(header, io, &block)
+ entry = new header, io
+ return entry unless block_given?
+ begin
+ yield entry
+ ensure
+ entry.close
+ end
+ end
+
+ ##
# Header for this tar entry
attr_reader :header
@@ -21,6 +38,7 @@ class Gem::Package::TarReader::Entry
@header = header
@io = io
@orig_pos = @io.pos
+ @end_pos = @orig_pos + @header.size
@read = 0
end
@@ -39,7 +57,14 @@ class Gem::Package::TarReader::Entry
# Closes the tar entry
def close
+ return if closed?
+ # Seek to the end of the entry if it wasn't fully read
+ seek(0, IO::SEEK_END)
+ # discard trailing zeros
+ skip = (512 - (@header.size % 512)) % 512
+ @io.read(skip)
@closed = true
+ nil
end
##
@@ -68,18 +93,16 @@ class Gem::Package::TarReader::Entry
@header.name
end
rescue ArgumentError => e
- raise unless e.message == 'string contains null byte'
+ raise unless e.message == "string contains null byte"
raise Gem::Package::TarInvalidError,
- 'tar is corrupt, name contains null byte'
+ "tar is corrupt, name contains null byte"
end
##
# Read one byte from the tar entry
def getc
- check_closed
-
- return nil if @read >= @header.size
+ return nil if eof?
ret = @io.getc
@read += 1 if ret
@@ -117,36 +140,43 @@ class Gem::Package::TarReader::Entry
bytes_read
end
+ ##
+ # Seek to the position in the tar entry
+
+ def pos=(new_pos)
+ seek(new_pos, IO::SEEK_SET)
+ end
+
def size
@header.size
end
- alias length size
+ alias_method :length, :size
##
- # Reads +len+ bytes from the tar file entry, or the rest of the entry if
- # nil
-
- def read(len = nil)
- check_closed
+ # Reads +maxlen+ bytes from the tar file entry, or the rest of the entry if nil
- return nil if @read >= @header.size
+ def read(maxlen = nil)
+ if eof?
+ return maxlen.to_i.zero? ? "" : nil
+ end
- len ||= @header.size - @read
- max_read = [len, @header.size - @read].min
+ max_read = [maxlen, @header.size - @read].compact.min
ret = @io.read max_read
+ if ret.nil?
+ return maxlen ? nil : "" # IO.read returns nil on EOF with len argument
+ end
@read += ret.size
ret
end
- def readpartial(maxlen = nil, outbuf = "".b)
- check_closed
-
- raise EOFError if @read >= @header.size
+ def readpartial(maxlen, outbuf = "".b)
+ if eof? && maxlen > 0
+ raise EOFError, "end of file reached"
+ end
- maxlen ||= @header.size - @read
max_read = [maxlen, @header.size - @read].min
@io.readpartial(max_read, outbuf)
@@ -156,12 +186,63 @@ class Gem::Package::TarReader::Entry
end
##
+ # Seeks to +offset+ bytes into the tar file entry
+ # +whence+ can be IO::SEEK_SET, IO::SEEK_CUR, or IO::SEEK_END
+
+ def seek(offset, whence = IO::SEEK_SET)
+ check_closed
+
+ new_pos =
+ case whence
+ when IO::SEEK_SET then @orig_pos + offset
+ when IO::SEEK_CUR then @io.pos + offset
+ when IO::SEEK_END then @end_pos + offset
+ else
+ raise ArgumentError, "invalid whence"
+ end
+
+ if new_pos < @orig_pos
+ new_pos = @orig_pos
+ elsif new_pos > @end_pos
+ new_pos = @end_pos
+ end
+
+ pending = new_pos - @io.pos
+
+ return 0 if pending == 0
+
+ if @io.respond_to?(:seek)
+ begin
+ # avoid reading if the @io supports seeking
+ @io.seek new_pos, IO::SEEK_SET
+ pending = 0
+ rescue Errno::EINVAL
+ end
+ end
+
+ # if seeking isn't supported or failed
+ # negative seek requires that we rewind and read
+ if pending < 0
+ @io.rewind
+ pending = new_pos
+ end
+
+ while pending > 0 do
+ size_read = @io.read([pending, 4096].min)&.size
+ raise(EOFError, "end of file reached") if size_read.nil?
+ pending -= size_read
+ end
+
+ @read = @io.pos - @orig_pos
+
+ 0
+ end
+
+ ##
# Rewinds to the beginning of the tar file entry
def rewind
check_closed
-
- @io.pos = @orig_pos
- @read = 0
+ seek(0, IO::SEEK_SET)
end
end
diff --git a/lib/rubygems/package/tar_test_case.rb b/lib/rubygems/package/tar_test_case.rb
deleted file mode 100644
index 1161d0a5a8..0000000000
--- a/lib/rubygems/package/tar_test_case.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-# frozen_string_literal: true
-require 'rubygems/test_case'
-require 'rubygems/package'
-
-##
-# A test case for Gem::Package::Tar* classes
-
-class Gem::Package::TarTestCase < Gem::TestCase
- def ASCIIZ(str, length)
- str + "\0" * (length - str.length)
- end
-
- def SP(s)
- s + " "
- end
-
- def SP_Z(s)
- s + " \0"
- end
-
- def Z(s)
- s + "\0"
- end
-
- def assert_headers_equal(expected, actual)
- expected = expected.to_s unless String === expected
- actual = actual.to_s unless String === actual
-
- fields = %w[
- name 100
- mode 8
- uid 8
- gid 8
- size 12
- mtime 12
- checksum 8
- typeflag 1
- linkname 100
- magic 6
- version 2
- uname 32
- gname 32
- devmajor 8
- devminor 8
- prefix 155
- ]
-
- offset = 0
-
- until fields.empty? do
- name = fields.shift
- length = fields.shift.to_i
-
- if name == "checksum"
- chksum_off = offset
- offset += length
- next
- end
-
- assert_equal expected[offset, length], actual[offset, length],
- "Field #{name} of the tar header differs."
-
- offset += length
- end
-
- assert_equal expected[chksum_off, 8], actual[chksum_off, 8]
- end
-
- def calc_checksum(header)
- sum = header.unpack("C*").inject{|s,a| s + a }
- SP(Z(to_oct(sum, 6)))
- end
-
- def header(type, fname, dname, length, mode, mtime, checksum = nil, linkname = "")
- checksum ||= " " * 8
-
- arr = [ # struct tarfile_entry_posix
- ASCIIZ(fname, 100), # char name[100]; ASCII + (Z unless filled)
- Z(to_oct(mode, 7)), # char mode[8]; 0 padded, octal null
- Z(to_oct(0, 7)), # char uid[8]; ditto
- Z(to_oct(0, 7)), # char gid[8]; ditto
- Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null
- Z(to_oct(mtime, 11)), # char mtime[12]; 0 padded, octal, null
- checksum, # char checksum[8]; 0 padded, octal, null, space
- type, # char typeflag[1]; file: "0" dir: "5"
- ASCIIZ(linkname, 100), # char linkname[100]; ASCII + (Z unless filled)
- "ustar\0", # char magic[6]; "ustar\0"
- "00", # char version[2]; "00"
- ASCIIZ("wheel", 32), # char uname[32]; ASCIIZ
- ASCIIZ("wheel", 32), # char gname[32]; ASCIIZ
- Z(to_oct(0, 7)), # char devmajor[8]; 0 padded, octal, null
- Z(to_oct(0, 7)), # char devminor[8]; 0 padded, octal, null
- ASCIIZ(dname, 155), # char prefix[155]; ASCII + (Z unless filled)
- ]
-
- h = arr.join
- ret = h + "\0" * (512 - h.size)
- assert_equal(512, ret.size)
- ret
- end
-
- def tar_dir_header(name, prefix, mode, mtime)
- h = header("5", name, prefix, 0, mode, mtime)
- checksum = calc_checksum(h)
- header("5", name, prefix, 0, mode, mtime, checksum)
- end
-
- def tar_file_header(fname, dname, mode, length, mtime)
- h = header("0", fname, dname, length, mode, mtime)
- checksum = calc_checksum(h)
- header("0", fname, dname, length, mode, mtime, checksum)
- end
-
- def tar_symlink_header(fname, prefix, mode, mtime, linkname)
- h = header("2", fname, prefix, 0, mode, mtime, nil, linkname)
- checksum = calc_checksum(h)
- header("2", fname, prefix, 0, mode, mtime, checksum, linkname)
- end
-
- def to_oct(n, pad_size)
- "%0#{pad_size}o" % n
- end
-
- def util_entry(tar)
- io = TempIO.new tar
-
- header = Gem::Package::TarHeader.from io
-
- Gem::Package::TarReader::Entry.new header, io
- end
-
- def util_dir_entry
- util_entry tar_dir_header("foo", "bar", 0, Time.now)
- end
-
- def util_symlink_entry
- util_entry tar_symlink_header("foo", "bar", 0, Time.now, "link")
- end
-end
diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb
index 877cc167c9..b24bdb63e7 100644
--- a/lib/rubygems/package/tar_writer.rb
+++ b/lib/rubygems/package/tar_writer.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
-#--
+
+# rubocop:disable Style/AsciiComments
+
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
-#++
+
+# rubocop:enable Style/AsciiComments
##
# Allows writing of tar files
@@ -113,9 +116,9 @@ class Gem::Package::TarWriter
final_pos = @io.pos
@io.pos = init_pos
- header = Gem::Package::TarHeader.new :name => name, :mode => mode,
- :size => size, :prefix => prefix,
- :mtime => Gem.source_date_epoch
+ header = Gem::Package::TarHeader.new name: name, mode: mode,
+ size: size, prefix: prefix,
+ mtime: Gem.source_date_epoch
@io.write header
@io.pos = final_pos
@@ -166,7 +169,7 @@ class Gem::Package::TarWriter
def add_file_signed(name, mode, signer)
digest_algorithms = [
signer.digest_algorithm,
- Gem::Security.create_digest('SHA512'),
+ Gem::Security.create_digest("SHA512"),
].compact.uniq
digests = add_file_digest name, mode, digest_algorithms do |io|
@@ -189,7 +192,7 @@ class Gem::Package::TarWriter
if signer.key
signature = signer.sign signature_digest.digest
- add_file_simple "#{name}.sig", 0444, signature.length do |io|
+ add_file_simple "#{name}.sig", 0o444, signature.length do |io|
io.write signature
end
end
@@ -206,9 +209,9 @@ class Gem::Package::TarWriter
name, prefix = split_name name
- header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
- :size => size, :prefix => prefix,
- :mtime => Gem.source_date_epoch).to_s
+ header = Gem::Package::TarHeader.new(name: name, mode: mode,
+ size: size, prefix: prefix,
+ mtime: Gem.source_date_epoch).to_s
@io.write header
os = BoundedStream.new @io, size
@@ -232,11 +235,11 @@ class Gem::Package::TarWriter
name, prefix = split_name name
- header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
- :size => 0, :typeflag => "2",
- :linkname => target,
- :prefix => prefix,
- :mtime => Gem.source_date_epoch).to_s
+ header = Gem::Package::TarHeader.new(name: name, mode: mode,
+ size: 0, typeflag: "2",
+ linkname: target,
+ prefix: prefix,
+ mtime: Gem.source_date_epoch).to_s
@io.write header
@@ -286,10 +289,10 @@ class Gem::Package::TarWriter
name, prefix = split_name(name)
- header = Gem::Package::TarHeader.new :name => name, :mode => mode,
- :typeflag => "5", :size => 0,
- :prefix => prefix,
- :mtime => Gem.source_date_epoch
+ header = Gem::Package::TarHeader.new name: name, mode: mode,
+ typeflag: "5", size: 0,
+ prefix: prefix,
+ mtime: Gem.source_date_epoch
@io.write header
@@ -304,17 +307,17 @@ class Gem::Package::TarWriter
raise Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)")
end
- prefix = ''
+ prefix = ""
if name.bytesize > 100
- parts = name.split('/', -1) # parts are never empty here
+ parts = name.split("/", -1) # parts are never empty here
name = parts.pop # initially empty for names with a trailing slash ("foo/.../bar/")
- prefix = parts.join('/') # if empty, then it's impossible to split (parts is empty too)
+ prefix = parts.join("/") # if empty, then it's impossible to split (parts is empty too)
while !parts.empty? && (prefix.bytesize > 155 || name.empty?)
- name = parts.pop + '/' + name
- prefix = parts.join('/')
+ name = parts.pop + "/" + name
+ prefix = parts.join("/")
end
- if name.bytesize > 100 or prefix.empty?
+ if name.bytesize > 100 || prefix.empty?
raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)")
end
@@ -323,6 +326,6 @@ class Gem::Package::TarWriter
end
end
- return name, prefix
+ [name, prefix]
end
end
diff --git a/lib/rubygems/package_task.rb b/lib/rubygems/package_task.rb
index d5a2885a64..d26411684d 100644
--- a/lib/rubygems/package_task.rb
+++ b/lib/rubygems/package_task.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# Copyright (c) 2003, 2004 Jim Weirich, 2009 Eric Hodel
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -20,9 +21,9 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-require 'rubygems'
-require 'rubygems/package'
-require 'rake/packagetask'
+require_relative "../rubygems"
+require_relative "package"
+require "rake/packagetask"
##
# Create a package based upon a Gem::Specification. Gem packages, as well as
@@ -96,13 +97,13 @@ class Gem::PackageTask < Rake::PackageTask
gem_path = File.join package_dir, gem_file
gem_dir = File.join package_dir, gem_spec.full_name
- task :package => [:gem]
+ task package: [:gem]
directory package_dir
directory gem_dir
desc "Build the gem file #{gem_file}"
- task :gem => [gem_path]
+ task gem: [gem_path]
trace = Rake.application.options.trace
Gem.configuration.verbose = trace
@@ -113,7 +114,7 @@ class Gem::PackageTask < Rake::PackageTask
Gem::Package.build gem_spec
verbose trace do
- mv gem_file, '..'
+ mv gem_file, ".."
end
end
end
diff --git a/lib/rubygems/path_support.rb b/lib/rubygems/path_support.rb
index 8103caf324..13091e29ba 100644
--- a/lib/rubygems/path_support.rb
+++ b/lib/rubygems/path_support.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
#
# Gem::PathSupport facilitates the GEM_HOME and GEM_PATH environment settings
@@ -23,23 +24,22 @@ class Gem::PathSupport
# hashtable, or defaults to ENV, the system environment.
#
def initialize(env)
- @home = env["GEM_HOME"] || Gem.default_dir
-
- if File::ALT_SEPARATOR
- @home = @home.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
- end
-
- @home = expand(@home)
-
+ @home = normalize_home_dir(env["GEM_HOME"] || Gem.default_dir)
@path = split_gem_path env["GEM_PATH"], @home
@spec_cache_dir = env["GEM_SPEC_CACHE"] || Gem.default_spec_cache_dir
-
- @spec_cache_dir = @spec_cache_dir.dup.tap(&Gem::UNTAINT)
end
private
+ def normalize_home_dir(home)
+ if File::ALT_SEPARATOR
+ home = home.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
+ end
+
+ expand(home)
+ end
+
##
# Split the Gem search path (as reported by Gem.path).
@@ -52,7 +52,7 @@ class Gem::PathSupport
gem_path = gpaths.split(Gem.path_separator)
# Handle the path_separator being set to a regexp, which will cause
# end_with? to error
- if gpaths =~ /#{Gem.path_separator}\z/
+ if /#{Gem.path_separator}\z/.match?(gpaths)
gem_path += default_path
end
@@ -72,12 +72,7 @@ class Gem::PathSupport
# Return the default Gem path
def default_path
- gem_path = Gem.default_path + [@home]
-
- if defined?(APPLE_GEM_HOME)
- gem_path << APPLE_GEM_HOME
- end
- gem_path
+ Gem.default_path + [@home]
end
def expand(path)
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index fd1c0a62ac..48b7344aee 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
-require "rubygems/deprecate"
+
+require_relative "deprecate"
##
# Available list of platforms for targeting Gem installations.
@@ -12,20 +13,28 @@ class Gem::Platform
attr_accessor :cpu, :os, :version
def self.local
- arch = RbConfig::CONFIG['arch']
- arch = "#{arch}_60" if arch =~ /mswin(?:32|64)$/
- @local ||= new(arch)
+ @local ||= begin
+ arch = RbConfig::CONFIG["arch"]
+ arch = "#{arch}_60" if /mswin(?:32|64)$/.match?(arch)
+ new(arch)
+ end
end
def self.match(platform)
match_platforms?(platform, Gem.platforms)
end
+ class << self
+ extend Gem::Deprecate
+ rubygems_deprecate :match, "Gem::Platform.match_spec? or match_gem?"
+ end
+
def self.match_platforms?(platform, platforms)
+ platform = Gem::Platform.new(platform) unless platform.is_a?(Gem::Platform)
platforms.any? do |local_platform|
- platform.nil? or
- local_platform == platform or
- (local_platform != Gem::Platform::RUBY and local_platform =~ platform)
+ platform.nil? ||
+ local_platform == platform ||
+ (local_platform != Gem::Platform::RUBY && platform =~ local_platform)
end
end
private_class_method :match_platforms?
@@ -34,10 +43,24 @@ class Gem::Platform
match_gem?(spec.platform, spec.name)
end
- def self.match_gem?(platform, gem_name)
- # Note: this method might be redefined by Ruby implementations to
- # customize behavior per RUBY_ENGINE, gem_name or other criteria.
- match_platforms?(platform, Gem.platforms)
+ if RUBY_ENGINE == "truffleruby"
+ def self.match_gem?(platform, gem_name)
+ raise "Not a string: #{gem_name.inspect}" unless String === gem_name
+
+ if REUSE_AS_BINARY_ON_TRUFFLERUBY.include?(gem_name)
+ match_platforms?(platform, [Gem::Platform::RUBY, Gem::Platform.local])
+ else
+ match_platforms?(platform, Gem.platforms)
+ end
+ end
+ else
+ def self.match_gem?(platform, gem_name)
+ match_platforms?(platform, Gem.platforms)
+ end
+ end
+
+ def self.sort_priority(platform)
+ platform == Gem::Platform::RUBY ? -1 : 1
end
def self.installable?(spec)
@@ -52,7 +75,7 @@ class Gem::Platform
case arch
when Gem::Platform::CURRENT then
Gem::Platform.local
- when Gem::Platform::RUBY, nil, '' then
+ when Gem::Platform::RUBY, nil, "" then
Gem::Platform::RUBY
else
super
@@ -64,9 +87,9 @@ class Gem::Platform
when Array then
@cpu, @os, @version = arch
when String then
- arch = arch.split '-'
+ arch = arch.split "-"
- if arch.length > 2 and arch.last !~ /\d/ # reassemble x86-linux-gnu
+ if arch.length > 2 && !arch.last.match?(/\d+(\.\d+)?$/) # reassemble x86-linux-{libc}
extra = arch.pop
arch.last << "-#{extra}"
end
@@ -74,44 +97,47 @@ class Gem::Platform
cpu = arch.shift
@cpu = case cpu
- when /i\d86/ then 'x86'
+ when /i\d86/ then "x86"
else cpu
- end
+ end
- if arch.length == 2 and arch.last =~ /^\d+(\.\d+)?$/ # for command-line
+ if arch.length == 2 && arch.last.match?(/^\d+(\.\d+)?$/) # for command-line
@os, @version = arch
return
end
os, = arch
- @cpu, os = nil, cpu if os.nil? # legacy jruby
+ if os.nil?
+ @cpu = nil
+ os = cpu
+ end # legacy jruby
@os, @version = case os
- when /aix(\d+)?/ then [ 'aix', $1 ]
- when /cygwin/ then [ 'cygwin', nil ]
- when /darwin(\d+)?/ then [ 'darwin', $1 ]
- when /^macruby$/ then [ 'macruby', nil ]
- when /freebsd(\d+)?/ then [ 'freebsd', $1 ]
- when /hpux(\d+)?/ then [ 'hpux', $1 ]
- when /^java$/, /^jruby$/ then [ 'java', nil ]
- when /^java([\d.]*)/ then [ 'java', $1 ]
- when /^dalvik(\d+)?$/ then [ 'dalvik', $1 ]
- when /^dotnet$/ then [ 'dotnet', nil ]
- when /^dotnet([\d.]*)/ then [ 'dotnet', $1 ]
- when /linux-?((?!gnu)\w+)?/ then [ 'linux', $1 ]
- when /mingw32/ then [ 'mingw32', nil ]
+ when /aix(\d+)?/ then ["aix", $1]
+ when /cygwin/ then ["cygwin", nil]
+ when /darwin(\d+)?/ then ["darwin", $1]
+ when /^macruby$/ then ["macruby", nil]
+ when /freebsd(\d+)?/ then ["freebsd", $1]
+ when /^java$/, /^jruby$/ then ["java", nil]
+ when /^java([\d.]*)/ then ["java", $1]
+ when /^dalvik(\d+)?$/ then ["dalvik", $1]
+ when /^dotnet$/ then ["dotnet", nil]
+ when /^dotnet([\d.]*)/ then ["dotnet", $1]
+ when /linux-?(\w+)?/ then ["linux", $1]
+ when /mingw32/ then ["mingw32", nil]
+ when /mingw-?(\w+)?/ then ["mingw", $1]
when /(mswin\d+)(\_(\d+))?/ then
- os, version = $1, $3
- @cpu = 'x86' if @cpu.nil? and os =~ /32$/
+ os = $1
+ version = $3
+ @cpu = "x86" if @cpu.nil? && os =~ /32$/
[os, version]
- when /netbsdelf/ then [ 'netbsdelf', nil ]
- when /openbsd(\d+\.\d+)?/ then [ 'openbsd', $1 ]
- when /bitrig(\d+\.\d+)?/ then [ 'bitrig', $1 ]
- when /solaris(\d+\.\d+)?/ then [ 'solaris', $1 ]
+ when /netbsdelf/ then ["netbsdelf", nil]
+ when /openbsd(\d+\.\d+)?/ then ["openbsd", $1]
+ when /solaris(\d+\.\d+)?/ then ["solaris", $1]
# test
- when /^(\w+_platform)(\d+)?/ then [ $1, $2 ]
- else [ 'unknown', nil ]
- end
+ when /^(\w+_platform)(\d+)?/ then [$1, $2]
+ else ["unknown", nil]
+ end
when Gem::Platform then
@cpu = arch.cpu
@os = arch.os
@@ -126,7 +152,7 @@ class Gem::Platform
end
def to_s
- to_a.compact.join '-'
+ to_a.compact.join "-"
end
##
@@ -134,10 +160,10 @@ class Gem::Platform
# the same CPU, OS and version.
def ==(other)
- self.class === other and to_a == other.to_a
+ self.class === other && to_a == other.to_a
end
- alias :eql? :==
+ alias_method :eql?, :==
def hash # :nodoc:
to_a.hash
@@ -146,23 +172,53 @@ class Gem::Platform
##
# Does +other+ match this platform? Two platforms match if they have the
# same CPU, or either has a CPU of 'universal', they have the same OS, and
- # they have the same version, or either has no version.
+ # they have the same version, or either one has no version
#
# Additionally, the platform will match if the local CPU is 'arm' and the
# other CPU starts with "arm" (for generic ARM family support).
+ #
+ # Of note, this method is not commutative. Indeed the OS 'linux' has a
+ # special case: the version is the libc name, yet while "no version" stands
+ # as a wildcard for a binary gem platform (as for other OSes), for the
+ # runtime platform "no version" stands for 'gnu'. To be able to distinguish
+ # these, the method receiver is the gem platform, while the argument is
+ # the runtime platform.
+ #
+ #--
+ # NOTE: Until it can be removed, changes to this method must also be reflected in `bundler/lib/bundler/rubygems_ext.rb`
def ===(other)
return nil unless Gem::Platform === other
+ # universal-mingw32 matches x64-mingw-ucrt
+ return true if (@cpu == "universal" || other.cpu == "universal") &&
+ @os.start_with?("mingw") && other.os.start_with?("mingw")
+
# cpu
- ([nil,'universal'].include?(@cpu) or [nil, 'universal'].include?(other.cpu) or @cpu == other.cpu or
- (@cpu == 'arm' and other.cpu.start_with?("arm"))) and
+ ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu ||
+ (@cpu == "arm" && other.cpu.start_with?("arm"))) &&
+
+ # os
+ @os == other.os &&
+
+ # version
+ (
+ (@os != "linux" && (@version.nil? || other.version.nil?)) ||
+ (@os == "linux" && (normalized_linux_version == other.normalized_linux_version || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) ||
+ @version == other.version
+ )
+ end
+
+ #--
+ # NOTE: Until it can be removed, changes to this method must also be reflected in `bundler/lib/bundler/rubygems_ext.rb`
- # os
- @os == other.os and
+ def normalized_linux_version
+ return nil unless @version
- # version
- (@version.nil? or other.version.nil? or @version == other.version)
+ without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "")
+ return nil if without_gnu_nor_abi_modifiers.empty?
+
+ without_gnu_nor_abi_modifiers
end
##
@@ -175,19 +231,19 @@ class Gem::Platform
when String then
# This data is from http://gems.rubyforge.org/gems/yaml on 19 Aug 2007
other = case other
- when /^i686-darwin(\d)/ then ['x86', 'darwin', $1 ]
- when /^i\d86-linux/ then ['x86', 'linux', nil ]
- when 'java', 'jruby' then [nil, 'java', nil ]
- when /^dalvik(\d+)?$/ then [nil, 'dalvik', $1 ]
- when /dotnet(\-(\d+\.\d+))?/ then ['universal','dotnet', $2 ]
- when /mswin32(\_(\d+))?/ then ['x86', 'mswin32', $2 ]
- when /mswin64(\_(\d+))?/ then ['x64', 'mswin64', $2 ]
- when 'powerpc-darwin' then ['powerpc', 'darwin', nil ]
- when /powerpc-darwin(\d)/ then ['powerpc', 'darwin', $1 ]
- when /sparc-solaris2.8/ then ['sparc', 'solaris', '2.8' ]
- when /universal-darwin(\d)/ then ['universal', 'darwin', $1 ]
- else other
- end
+ when /^i686-darwin(\d)/ then ["x86", "darwin", $1]
+ when /^i\d86-linux/ then ["x86", "linux", nil]
+ when "java", "jruby" then [nil, "java", nil]
+ when /^dalvik(\d+)?$/ then [nil, "dalvik", $1]
+ when /dotnet(\-(\d+\.\d+))?/ then ["universal","dotnet", $2]
+ when /mswin32(\_(\d+))?/ then ["x86", "mswin32", $2]
+ when /mswin64(\_(\d+))?/ then ["x64", "mswin64", $2]
+ when "powerpc-darwin" then ["powerpc", "darwin", nil]
+ when /powerpc-darwin(\d)/ then ["powerpc", "darwin", $1]
+ when /sparc-solaris2.8/ then ["sparc", "solaris", "2.8"]
+ when /universal-darwin(\d)/ then ["universal", "darwin", $1]
+ else other
+ end
other = Gem::Platform.new other
else
@@ -201,11 +257,11 @@ class Gem::Platform
# A pure-Ruby gem that may use Gem::Specification#extensions to build
# binary files.
- RUBY = 'ruby'.freeze
+ RUBY = "ruby"
##
# A platform-specific gem that is built for the packaging Ruby's platform.
# This will be replaced with Gem::Platform::local.
- CURRENT = 'current'.freeze
+ CURRENT = "current"
end
diff --git a/lib/rubygems/psych_additions.rb b/lib/rubygems/psych_additions.rb
deleted file mode 100644
index 1ddd74421c..0000000000
--- a/lib/rubygems/psych_additions.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-# This exists just to satisfy bugs in marshal'd gemspecs that
-# contain a reference to YAML::PrivateType. We prune these out
-# in Specification._load, but if we don't have the constant, Marshal
-# blows up.
-
-module Psych # :nodoc:
- class PrivateType # :nodoc:
- end
-end
diff --git a/lib/rubygems/psych_tree.rb b/lib/rubygems/psych_tree.rb
index 6f399a289e..24857adb9d 100644
--- a/lib/rubygems/psych_tree.rb
+++ b/lib/rubygems/psych_tree.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
module Gem
if defined? ::Psych::Visitors
class NoAliasYAMLTree < Psych::Visitors::YAMLTree
@@ -7,12 +8,16 @@ module Gem
end unless respond_to? :create
def visit_String(str)
- return super unless str == '=' # or whatever you want
+ return super unless str == "=" # or whatever you want
quote = Psych::Nodes::Scalar::SINGLE_QUOTED
@emitter.scalar str, nil, nil, false, true, quote
end
+ def visit_Hash(o)
+ super(o.compact)
+ end
+
# Noop this out so there are no anchors
def register(target, obj)
end
diff --git a/lib/rubygems/query_utils.rb b/lib/rubygems/query_utils.rb
index ea0f260ab4..a95a759401 100644
--- a/lib/rubygems/query_utils.rb
+++ b/lib/rubygems/query_utils.rb
@@ -1,52 +1,51 @@
# frozen_string_literal: true
-require 'rubygems/local_remote_options'
-require 'rubygems/spec_fetcher'
-require 'rubygems/version_option'
-require 'rubygems/text'
+require_relative "local_remote_options"
+require_relative "spec_fetcher"
+require_relative "version_option"
+require_relative "text"
module Gem::QueryUtils
-
include Gem::Text
include Gem::LocalRemoteOptions
include Gem::VersionOption
def add_query_options
- add_option('-i', '--[no-]installed',
- 'Check for installed gem') do |value, options|
+ add_option("-i", "--[no-]installed",
+ "Check for installed gem") do |value, options|
options[:installed] = value
end
- add_option('-I', 'Equivalent to --no-installed') do |value, options|
+ add_option("-I", "Equivalent to --no-installed") do |_value, options|
options[:installed] = false
end
add_version_option command, "for use with --installed"
- add_option('-d', '--[no-]details',
- 'Display detailed information of gem(s)') do |value, options|
+ add_option("-d", "--[no-]details",
+ "Display detailed information of gem(s)") do |value, options|
options[:details] = value
end
- add_option('--[no-]versions',
- 'Display only gem names') do |value, options|
+ add_option("--[no-]versions",
+ "Display only gem names") do |value, options|
options[:versions] = value
options[:details] = false unless value
end
- add_option('-a', '--all',
- 'Display all gem versions') do |value, options|
+ add_option("-a", "--all",
+ "Display all gem versions") do |value, options|
options[:all] = value
end
- add_option('-e', '--exact',
- 'Name of gem(s) to query on matches the',
- 'provided STRING') do |value, options|
+ add_option("-e", "--exact",
+ "Name of gem(s) to query on matches the",
+ "provided STRING") do |value, options|
options[:exact] = value
end
- add_option('--[no-]prerelease',
- 'Display prerelease versions') do |value, options|
+ add_option("--[no-]prerelease",
+ "Display prerelease versions") do |value, options|
options[:prerelease] = value
end
@@ -54,14 +53,14 @@ module Gem::QueryUtils
end
def defaults_str # :nodoc:
- "--local --name-matches // --no-details --versions --no-installed"
+ "--local --no-details --versions --no-installed"
end
def execute
- gem_names = Array(options[:name])
-
- if !args.empty?
- gem_names = options[:exact] ? args.map{|arg| /\A#{Regexp.escape(arg)}\Z/ } : args.map{|arg| /#{arg}/i }
+ gem_names = if args.empty?
+ [options[:name]]
+ else
+ options[:exact] ? args.map {|arg| /\A#{Regexp.escape(arg)}\Z/ } : args.map {|arg| /#{arg}/i }
end
terminate_interaction(check_installed_gems(gem_names)) if check_installed_gems?
@@ -85,7 +84,7 @@ module Gem::QueryUtils
installed = !installed unless options[:installed]
say(installed)
- exit_code = 1 if !installed
+ exit_code = 1 unless installed
end
exit_code
@@ -96,7 +95,7 @@ module Gem::QueryUtils
end
def gem_name?
- !options[:name].source.empty?
+ !options[:name].nil?
end
def prerelease
@@ -112,14 +111,14 @@ module Gem::QueryUtils
end
def display_header(type)
- if (ui.outs.tty? and Gem.configuration.verbose) or both?
+ if (ui.outs.tty? && Gem.configuration.verbose) || both?
say
say "*** #{type} GEMS ***"
say
end
end
- #Guts of original execute
+ # Guts of original execute
def show_gems(name)
show_local_gems(name) if local?
show_remote_gems(name) if remote?
@@ -129,12 +128,10 @@ module Gem::QueryUtils
display_header("LOCAL")
specs = Gem::Specification.find_all do |s|
- s.name =~ name and req =~ s.version
- end
+ name_matches = name ? s.name =~ name : true
+ version_matches = show_prereleases? || !s.version.prerelease?
- dep = Gem::Deprecate.skip_during { Gem::Dependency.new name, req }
- specs.select! do |s|
- dep.match?(s.name, s.version, show_prereleases?)
+ name_matches && version_matches
end
spec_tuples = specs.map do |spec|
@@ -149,19 +146,19 @@ module Gem::QueryUtils
fetcher = Gem::SpecFetcher.fetcher
- spec_tuples = if name.respond_to?(:source) && name.source.empty?
- fetcher.detect(specs_type) { true }
- else
- fetcher.detect(specs_type) do |name_tuple|
- name === name_tuple.name
- end
- end
+ spec_tuples = if name.nil?
+ fetcher.detect(specs_type) { true }
+ else
+ fetcher.detect(specs_type) do |name_tuple|
+ name === name_tuple.name && options[:version].satisfied_by?(name_tuple.version)
+ end
+ end
output_query_results(spec_tuples)
end
def specs_type
- if options[:all]
+ if options[:all] || options[:version].specific?
if options[:prerelease]
:complete
else
@@ -178,7 +175,7 @@ module Gem::QueryUtils
# Check if gem +name+ version +version+ is installed.
def installed?(name, req = Gem::Requirement.default)
- Gem::Specification.any? {|s| s.name =~ name and req =~ s.version }
+ Gem::Specification.any? {|s| s.name =~ name && req =~ s.version }
end
def output_query_results(spec_tuples)
@@ -199,7 +196,7 @@ module Gem::QueryUtils
end
def output_versions(output, versions)
- versions.each do |gem_name, matching_tuples|
+ versions.each do |_gem_name, matching_tuples|
matching_tuples = matching_tuples.sort_by {|n,_| n.version }.reverse
platforms = Hash.new {|h,version| h[version] = [] }
@@ -244,8 +241,8 @@ module Gem::QueryUtils
return unless options[:versions]
list =
- if platforms.empty? or options[:details]
- name_tuples.map {|n| n.version }.uniq
+ if platforms.empty? || options[:details]
+ name_tuples.map(&:version).uniq
else
platforms.sort.reverse.map do |version, pls|
out = version.to_s
@@ -259,14 +256,14 @@ module Gem::QueryUtils
if pls != [Gem::Platform::RUBY]
platform_list = [pls.delete(Gem::Platform::RUBY), *pls.sort].compact
- out = platform_list.unshift(out).join(' ')
+ out = platform_list.unshift(out).join(" ")
end
out
end
end
- entry << " (#{list.join ', '})"
+ entry << " (#{list.join ", "})"
end
def make_entry(entry_tuples, platforms)
@@ -285,22 +282,22 @@ module Gem::QueryUtils
end
def spec_authors(entry, spec)
- authors = "Author#{spec.authors.length > 1 ? 's' : ''}: ".dup
- authors << spec.authors.join(', ')
+ authors = "Author#{spec.authors.length > 1 ? "s" : ""}: ".dup
+ authors << spec.authors.join(", ")
entry << format_text(authors, 68, 4)
end
def spec_homepage(entry, spec)
- return if spec.homepage.nil? or spec.homepage.empty?
+ return if spec.homepage.nil? || spec.homepage.empty?
entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4)
end
def spec_license(entry, spec)
- return if spec.license.nil? or spec.license.empty?
+ return if spec.license.nil? || spec.license.empty?
- licenses = "License#{spec.licenses.length > 1 ? 's' : ''}: ".dup
- licenses << spec.licenses.join(', ')
+ licenses = "License#{spec.licenses.length > 1 ? "s" : ""}: ".dup
+ licenses << spec.licenses.join(", ")
entry << "\n" << format_text(licenses, 68, 4)
end
@@ -308,15 +305,15 @@ module Gem::QueryUtils
return unless spec.loaded_from
if specs.length == 1
- default = spec.default_gem? ? ' (default)' : nil
+ default = spec.default_gem? ? " (default)" : nil
entry << "\n" << " Installed at#{default}: #{spec.base_dir}"
else
- label = 'Installed at'
+ label = "Installed at"
specs.each do |s|
version = s.version.to_s
- version << ', default' if s.default_gem?
- entry << "\n" << " #{label} (#{version}): #{s.base_dir}"
- label = ' ' * label.length
+ default = ", default" if s.default_gem?
+ entry << "\n" << " #{label} (#{version}#{default}): #{s.base_dir}"
+ label = " " * label.length
end
end
end
@@ -329,16 +326,16 @@ module Gem::QueryUtils
return unless non_ruby
if platforms.length == 1
- title = platforms.values.length == 1 ? 'Platform' : 'Platforms'
- entry << " #{title}: #{platforms.values.sort.join(', ')}\n"
+ title = platforms.values.length == 1 ? "Platform" : "Platforms"
+ entry << " #{title}: #{platforms.values.sort.join(", ")}\n"
else
entry << " Platforms:\n"
- sorted_platforms = platforms.sort_by {|version,| version }
+ sorted_platforms = platforms.sort
sorted_platforms.each do |version, pls|
label = " #{version}: "
- data = format_text pls.sort.join(', '), 68, label.length
+ data = format_text pls.sort.join(", "), 68, label.length
data[0, label.length] = label
entry << data << "\n"
end
@@ -349,5 +346,4 @@ module Gem::QueryUtils
summary = truncate_text(spec.summary, "the summary for #{spec.full_name}")
entry << "\n\n" << format_text(summary, 68, 4)
end
-
end
diff --git a/lib/rubygems/rdoc.rb b/lib/rubygems/rdoc.rb
index c40bb7d9f1..907dcd9431 100644
--- a/lib/rubygems/rdoc.rb
+++ b/lib/rubygems/rdoc.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems'
+
+require_relative "../rubygems"
begin
- require 'rdoc/rubygems_hook'
+ require "rdoc/rubygems_hook"
module Gem
RDoc = ::RDoc::RubygemsHook
end
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index e3d4bfbeba..c3a41592f6 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-require 'rubygems'
-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_relative "../rubygems"
+require_relative "request"
+require_relative "request/connection_pools"
+require_relative "s3_uri_signer"
+require_relative "uri_formatter"
+require_relative "uri"
+require_relative "user_interaction"
##
# RemoteFetcher handles the details of fetching gems and gem information from
@@ -14,30 +14,24 @@ require 'resolv'
class Gem::RemoteFetcher
include Gem::UserInteraction
- 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.
attr_accessor :uri, :original_uri
def initialize(message, uri)
- super message
-
- uri = parse_uri(uri)
-
- @original_uri = uri.dup
+ uri = Gem::Uri.new(uri)
- uri.password = 'REDACTED' if uri.respond_to?(:password) && uri.password
+ super uri.redact_credentials_from(message)
- @uri = uri.to_s
+ @original_uri = uri.to_s
+ @uri = uri.redacted.to_s
end
def to_s # :nodoc:
@@ -59,7 +53,7 @@ class Gem::RemoteFetcher
# Cached RemoteFetcher instance.
def self.fetcher
- @fetcher ||= self.new Gem.configuration[:http_proxy]
+ @fetcher ||= new Gem.configuration[:http_proxy]
end
attr_accessor :headers
@@ -79,16 +73,16 @@ class Gem::RemoteFetcher
# fetching the gem.
def initialize(proxy=nil, dns=nil, headers={})
- require 'rubygems/core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled
- require 'net/http'
- require 'stringio'
- require 'uri'
+ require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled
+ require_relative "vendored_net_http"
+ require "stringio"
+ require_relative "vendor/uri/lib/uri"
Socket.do_not_reverse_lookup = true
@proxy = proxy
@pools = {}
- @pool_lock = Mutex.new
+ @pool_lock = Thread::Mutex.new
@cert_files = Gem::Request.get_cert_files
@headers = headers
@@ -121,7 +115,7 @@ class Gem::RemoteFetcher
cache_dir =
if Dir.pwd == install_dir # see fetch_command
install_dir
- elsif File.writable?(install_cache_dir) || (File.writable?(install_dir) && (not File.exist?(install_cache_dir)))
+ elsif File.writable?(install_cache_dir) || (File.writable?(install_dir) && !File.exist?(install_cache_dir))
install_cache_dir
else
File.join Gem.user_dir, "cache"
@@ -131,26 +125,30 @@ class Gem::RemoteFetcher
local_gem_path = File.join cache_dir, gem_file_name
require "fileutils"
- FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir
+ begin
+ FileUtils.mkdir_p cache_dir
+ rescue StandardError
+ nil
+ end unless File.exist? cache_dir
- source_uri = parse_uri(source_uri)
+ source_uri = Gem::Uri.new(source_uri)
scheme = source_uri.scheme
- # URI.parse gets confused by MS Windows paths with forward slashes.
- scheme = nil if scheme =~ /^[a-z]$/i
+ # Gem::URI.parse gets confused by MS Windows paths with forward slashes.
+ scheme = nil if /^[a-z]$/i.match?(scheme)
# REFACTOR: split this up and dispatch on scheme (eg download_http)
# REFACTOR: be sure to clean up fake fetcher when you do this... cleaner
case scheme
- when 'http', 'https', 's3' then
+ when "http", "https", "s3" then
unless File.exist? local_gem_path
begin
verbose "Downloading gem #{gem_file_name}"
remote_gem_path = source_uri + "gems/#{gem_file_name}"
- self.cache_update_path remote_gem_path, local_gem_path
+ cache_update_path remote_gem_path, local_gem_path
rescue FetchError
raise if spec.original_platform == spec.platform
@@ -160,15 +158,15 @@ class Gem::RemoteFetcher
remote_gem_path = source_uri + "gems/#{alternate_name}"
- self.cache_update_path remote_gem_path, local_gem_path
+ cache_update_path remote_gem_path, local_gem_path
end
end
- when 'file' then
+ when "file" then
begin
path = source_uri.path
- path = File.dirname(path) if File.extname(path) == '.gem'
+ path = File.dirname(path) if File.extname(path) == ".gem"
- remote_gem_path = Gem::Util.correct_for_windows_path(File.join(path, 'gems', gem_file_name))
+ remote_gem_path = Gem::Util.correct_for_windows_path(File.join(path, "gems", gem_file_name))
FileUtils.cp(remote_gem_path, local_gem_path)
rescue Errno::EACCES
@@ -176,13 +174,13 @@ class Gem::RemoteFetcher
end
verbose "Using local gem #{local_gem_path}"
- when nil then # TODO test for local overriding cache
+ when nil then # TODO: test for local overriding cache
source_path = if Gem.win_platform? && source_uri.scheme &&
- !source_uri.path.include?(':')
- "#{source_uri.scheme}:#{source_uri.path}"
- else
- source_uri.path
- end
+ !source_uri.path.include?(":")
+ "#{source_uri.scheme}:#{source_uri.path}"
+ else
+ source_uri.path
+ end
source_path = Gem::UriFormatter.new(source_path).unescape
@@ -212,23 +210,23 @@ class Gem::RemoteFetcher
# HTTP Fetcher. Dispatched by +fetch_path+. Use it instead.
def fetch_http(uri, last_modified = nil, head = false, depth = 0)
- fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get
+ fetch_type = head ? Gem::Net::HTTP::Head : Gem::Net::HTTP::Get
response = request uri, fetch_type, last_modified do |req|
headers.each {|k,v| req.add_field(k,v) }
end
case response
- when Net::HTTPOK, Net::HTTPNotModified then
+ when Gem::Net::HTTPOK, Gem::Net::HTTPNotModified then
response.uri = uri
head ? response : response.body
- when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
- Net::HTTPTemporaryRedirect then
- raise FetchError.new('too many redirects', uri) if depth > 10
+ when Gem::Net::HTTPMovedPermanently, Gem::Net::HTTPFound, Gem::Net::HTTPSeeOther,
+ Gem::Net::HTTPTemporaryRedirect then
+ raise FetchError.new("too many redirects", uri) if depth > 10
- unless location = response['Location']
+ unless location = response["Location"]
raise FetchError.new("redirecting but no redirect location was given", uri)
end
- location = parse_uri location
+ location = Gem::Uri.new location
if https?(uri) && !https?(location)
raise FetchError.new("redirecting to non-https resource: #{location}", uri)
@@ -240,13 +238,13 @@ class Gem::RemoteFetcher
end
end
- alias :fetch_https :fetch_http
+ alias_method :fetch_https, :fetch_http
##
# Downloads +uri+ and returns it as a String.
def fetch_path(uri, mtime = nil, head = false)
- uri = parse_uri uri
+ uri = Gem::Uri.new uri
unless uri.scheme
raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}"
@@ -254,7 +252,7 @@ class Gem::RemoteFetcher
data = send "fetch_#{uri.scheme}", uri, mtime, head
- if data and !head and uri.to_s.end_with?(".gz")
+ if data && !head && uri.to_s.end_with?(".gz")
begin
data = Gem::Util.gunzip data
rescue Zlib::GzipFile::Error
@@ -263,7 +261,7 @@ class Gem::RemoteFetcher
end
data
- rescue Timeout::Error, IOError, SocketError, SystemCallError,
+ rescue Gem::Timeout::Error, IOError, SocketError, SystemCallError,
*(OpenSSL::SSL::SSLError if Gem::HAVE_OPENSSL) => e
raise FetchError.new("#{e.class}: #{e}", uri)
end
@@ -287,7 +285,11 @@ class Gem::RemoteFetcher
# passes the data.
def cache_update_path(uri, path = nil, update = true)
- mtime = path && File.stat(path).mtime rescue nil
+ mtime = begin
+ path && File.stat(path).mtime
+ rescue StandardError
+ nil
+ end
data = fetch_path(uri, mtime)
@@ -295,7 +297,7 @@ class Gem::RemoteFetcher
return Gem.read_binary(path)
end
- if update and path
+ if update && path
Gem.write_binary(path, data)
end
@@ -303,8 +305,8 @@ class Gem::RemoteFetcher
end
##
- # Performs a Net::HTTP request of type +request_class+ on +uri+ returning
- # a Net::HTTP response object. request maintains a table of persistent
+ # Performs a Gem::Net::HTTP request of type +request_class+ on +uri+ returning
+ # a Gem::Net::HTTP response object. request maintains a table of persistent
# connections to reduce connect overhead.
def request(uri, request_class, last_modified = nil)
@@ -319,11 +321,11 @@ class Gem::RemoteFetcher
end
def https?(uri)
- uri.scheme.downcase == 'https'
+ uri.scheme.casecmp("https").zero?
end
def close_all
- @pools.each_value {|pool| pool.close_all }
+ @pools.each_value(&:close_all)
end
private
diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb
index 1ed0fbcb99..9116785231 100644
--- a/lib/rubygems/request.rb
+++ b/lib/rubygems/request.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
-require 'net/http'
-require 'rubygems/user_interaction'
+
+require_relative "vendored_net_http"
+require_relative "user_interaction"
class Gem::Request
extend Gem::UserInteraction
@@ -17,11 +18,11 @@ class Gem::Request
end
def self.proxy_uri(proxy) # :nodoc:
- require "uri"
+ require_relative "vendor/uri/lib/uri"
case proxy
when :no_proxy then nil
- when URI::HTTP then proxy
- else URI.parse(proxy)
+ when Gem::URI::HTTP then proxy
+ else Gem::URI.parse(proxy)
end
end
@@ -29,22 +30,27 @@ class Gem::Request
@uri = uri
@request_class = request_class
@last_modified = last_modified
- @requests = Hash.new 0
+ @requests = Hash.new(0).compare_by_identity
@user_agent = user_agent
@connection_pool = pool
end
- def proxy_uri; @connection_pool.proxy_uri; end
- def cert_files; @connection_pool.cert_files; end
+ def proxy_uri
+ @connection_pool.proxy_uri
+ end
+
+ def cert_files
+ @connection_pool.cert_files
+ end
def self.get_cert_files
- pattern = File.expand_path("./ssl_certs/*/*.pem", File.dirname(__FILE__))
+ pattern = File.expand_path("./ssl_certs/*/*.pem", __dir__)
Dir.glob(pattern)
end
def self.configure_connection_for_https(connection, cert_files)
- raise Gem::Exception.new('OpenSSl is not available. Install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources') unless Gem::HAVE_OPENSSL
+ raise Gem::Exception.new("OpenSSL is not available. Install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources") unless Gem::HAVE_OPENSSL
connection.use_ssl = true
connection.verify_mode =
@@ -96,8 +102,10 @@ class Gem::Request
return unless cert
case error_number
when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then
+ require "time"
"Certificate #{cert.subject} expired at #{cert.not_after.iso8601}"
when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then
+ require "time"
"Certificate #{cert.subject} not valid until #{cert.not_before.iso8601}"
when OpenSSL::X509::V_ERR_CERT_REJECTED then
"Certificate #{cert.subject} is rejected"
@@ -138,13 +146,13 @@ class Gem::Request
Gem::UriFormatter.new(@uri.password).unescape
end
- request.add_field 'User-Agent', @user_agent
- request.add_field 'Connection', 'keep-alive'
- request.add_field 'Keep-Alive', '30'
+ request.add_field "User-Agent", @user_agent
+ request.add_field "Connection", "keep-alive"
+ request.add_field "Keep-Alive", "30"
if @last_modified
- require 'time'
- request.add_field 'If-Modified-Since', @last_modified.httpdate
+ require "time"
+ request.add_field "If-Modified-Since", @last_modified.httpdate
end
yield request if block_given?
@@ -156,24 +164,23 @@ class Gem::Request
# Returns a proxy URI for the given +scheme+ if one is set in the
# environment variables.
- def self.get_proxy_from_env(scheme = 'http')
- _scheme = scheme.downcase
- _SCHEME = scheme.upcase
- env_proxy = ENV["#{_scheme}_proxy"] || ENV["#{_SCHEME}_PROXY"]
+ def self.get_proxy_from_env(scheme = "http")
+ downcase_scheme = scheme.downcase
+ upcase_scheme = scheme.upcase
+ env_proxy = ENV["#{downcase_scheme}_proxy"] || ENV["#{upcase_scheme}_PROXY"]
no_env_proxy = env_proxy.nil? || env_proxy.empty?
if no_env_proxy
- return (_scheme == 'https' || _scheme == 'http') ?
- :no_proxy : get_proxy_from_env('http')
+ return ["https", "http"].include?(downcase_scheme) ? :no_proxy : get_proxy_from_env("http")
end
require "uri"
- uri = URI(Gem::UriFormatter.new(env_proxy).normalize)
+ uri = Gem::URI(Gem::UriFormatter.new(env_proxy).normalize)
- if uri and uri.user.nil? and uri.password.nil?
- user = ENV["#{_scheme}_proxy_user"] || ENV["#{_SCHEME}_PROXY_USER"]
- password = ENV["#{_scheme}_proxy_pass"] || ENV["#{_SCHEME}_PROXY_PASS"]
+ if uri && uri.user.nil? && uri.password.nil?
+ user = ENV["#{downcase_scheme}_proxy_user"] || ENV["#{upcase_scheme}_PROXY_USER"]
+ password = ENV["#{downcase_scheme}_proxy_pass"] || ENV["#{upcase_scheme}_PROXY_PASS"]
uri.user = Gem::UriFormatter.new(user).escape
uri.password = Gem::UriFormatter.new(password).escape
@@ -189,16 +196,16 @@ class Gem::Request
bad_response = false
begin
- @requests[connection.object_id] += 1
+ @requests[connection] += 1
- verbose "#{request.method} #{@uri}"
+ verbose "#{request.method} #{Gem::Uri.redact(@uri)}"
file_name = File.basename(@uri.path)
# perform download progress reporter only for gems
if request.response_body_permitted? && file_name =~ /\.gem$/
reporter = ui.download_reporter
response = connection.request(request) do |incomplete_response|
- if Net::HTTPOK === incomplete_response
+ if Gem::Net::HTTPOK === incomplete_response
reporter.fetch(file_name, incomplete_response.content_length)
downloaded = 0
data = String.new
@@ -221,30 +228,29 @@ class Gem::Request
end
verbose "#{response.code} #{response.message}"
-
- rescue Net::HTTPBadResponse
+ rescue Gem::Net::HTTPBadResponse
verbose "bad response"
reset connection
- raise Gem::RemoteFetcher::FetchError.new('too many bad responses', @uri) if bad_response
+ raise Gem::RemoteFetcher::FetchError.new("too many bad responses", @uri) if bad_response
bad_response = true
retry
- rescue Net::HTTPFatalError
+ rescue Gem::Net::HTTPFatalError
verbose "fatal error"
- raise Gem::RemoteFetcher::FetchError.new('fatal error', @uri)
- # HACK work around EOFError bug in Net::HTTP
+ raise Gem::RemoteFetcher::FetchError.new("fatal error", @uri)
+ # HACK: work around EOFError bug in Gem::Net::HTTP
# NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
# to install gems.
- rescue EOFError, Timeout::Error,
+ rescue EOFError, Gem::Timeout::Error,
Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
- requests = @requests[connection.object_id]
+ requests = @requests[connection]
verbose "connection reset after #{requests} requests, retrying"
- raise Gem::RemoteFetcher::FetchError.new('too many connection resets', @uri) if retried
+ raise Gem::RemoteFetcher::FetchError.new("too many connection resets", @uri) if retried
reset connection
@@ -261,7 +267,7 @@ class Gem::Request
# Resets HTTP connection +connection+.
def reset(connection)
- @requests.delete connection.object_id
+ @requests.delete connection
connection.finish
connection.start
@@ -271,22 +277,22 @@ class Gem::Request
ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}".dup
ruby_version = RUBY_VERSION
- ruby_version += 'dev' if RUBY_PATCHLEVEL == -1
+ ruby_version += "dev" if RUBY_PATCHLEVEL == -1
ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}"
if RUBY_PATCHLEVEL >= 0
ua << " patchlevel #{RUBY_PATCHLEVEL}"
- elsif defined?(RUBY_REVISION)
+ else
ua << " revision #{RUBY_REVISION}"
end
ua << ")"
- ua << " #{RUBY_ENGINE}" if RUBY_ENGINE != 'ruby'
+ ua << " #{RUBY_ENGINE}" if RUBY_ENGINE != "ruby"
ua
end
end
-require 'rubygems/request/http_pool'
-require 'rubygems/request/https_pool'
-require 'rubygems/request/connection_pools'
+require_relative "request/http_pool"
+require_relative "request/https_pool"
+require_relative "request/connection_pools"
diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb
index 7f3988952c..6c1b04ab65 100644
--- a/lib/rubygems/request/connection_pools.rb
+++ b/lib/rubygems/request/connection_pools.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class Gem::Request::ConnectionPools # :nodoc:
- @client = Net::HTTP
+ @client = Gem::Net::HTTP
class << self
attr_accessor :client
@@ -11,7 +11,7 @@ class Gem::Request::ConnectionPools # :nodoc:
@proxy_uri = proxy_uri
@cert_files = cert_files
@pools = {}
- @pool_mutex = Mutex.new
+ @pool_mutex = Thread::Mutex.new
end
def pool_for(uri)
@@ -28,7 +28,7 @@ class Gem::Request::ConnectionPools # :nodoc:
end
def close_all
- @pools.each_value {|pool| pool.close_all }
+ @pools.each_value(&:close_all)
end
private
@@ -37,15 +37,15 @@ class Gem::Request::ConnectionPools # :nodoc:
# Returns list of no_proxy entries (if any) from the environment
def get_no_proxy_from_env
- env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
+ env_no_proxy = ENV["no_proxy"] || ENV["NO_PROXY"]
- return [] if env_no_proxy.nil? or env_no_proxy.empty?
+ return [] if env_no_proxy.nil? || env_no_proxy.empty?
env_no_proxy.split(/\s*,\s*/)
end
def https?(uri)
- uri.scheme.downcase == 'https'
+ uri.scheme.casecmp("https").zero?
end
def no_proxy?(host, env_no_proxy)
@@ -78,7 +78,7 @@ class Gem::Request::ConnectionPools # :nodoc:
no_proxy = get_no_proxy_from_env
- if proxy_uri and not no_proxy?(hostname, no_proxy)
+ if proxy_uri && !no_proxy?(hostname, no_proxy)
proxy_hostname = proxy_uri.respond_to?(:hostname) ? proxy_uri.hostname : proxy_uri.host
net_http_args + [
proxy_hostname,
diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb
index 9985bbafa6..52543de41f 100644
--- a/lib/rubygems/request/http_pool.rb
+++ b/lib/rubygems/request/http_pool.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A connection "pool" that only manages one connection for now. Provides
# thread safe `checkout` and `checkin` methods. The pool consists of one
@@ -12,7 +13,7 @@ class Gem::Request::HTTPPool # :nodoc:
@http_args = http_args
@cert_files = cert_files
@proxy_uri = proxy_uri
- @queue = SizedQueue.new 1
+ @queue = Thread::SizedQueue.new 1
@queue << nil
end
@@ -26,7 +27,7 @@ class Gem::Request::HTTPPool # :nodoc:
def close_all
until @queue.empty?
- if connection = @queue.pop(true) and connection.started?
+ if (connection = @queue.pop(true)) && connection.started?
connection.finish
end
end
diff --git a/lib/rubygems/request/https_pool.rb b/lib/rubygems/request/https_pool.rb
index 50f42d9e0d..cb1d4b59b6 100644
--- a/lib/rubygems/request/https_pool.rb
+++ b/lib/rubygems/request/https_pool.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
class Gem::Request::HTTPSPool < Gem::Request::HTTPPool # :nodoc:
private
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index 5190cfc904..875df7e019 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
-require 'tsort'
+
+require_relative "vendored_tsort"
##
# A RequestSet groups a request to activate a set of dependencies.
@@ -15,7 +16,7 @@ require 'tsort'
# #=> ["nokogiri-1.6.0", "mini_portile-0.5.1", "pg-0.17.0"]
class Gem::RequestSet
- include TSort
+ include Gem::TSort
##
# Array of gems to install even if already installed
@@ -107,7 +108,7 @@ class Gem::RequestSet
@requests = []
@sets = []
@soft_missing = false
- @sorted = nil
+ @sorted_requests = nil
@specs = nil
@vendor_set = nil
@source_set = nil
@@ -151,7 +152,7 @@ class Gem::RequestSet
@prerelease = options[:prerelease]
requests = []
- download_queue = Queue.new
+ download_queue = Thread::Queue.new
# Create a thread-safe list of gems to download
sorted_requests.each do |req|
@@ -159,7 +160,7 @@ class Gem::RequestSet
end
# Create N threads in a pool, have them download all the gems
- threads = Gem.configuration.concurrent_downloads.times.map do
+ threads = Array.new(Gem.configuration.concurrent_downloads) do
# When a thread pops this item, it knows to stop running. The symbol
# is queued here so that there will be one symbol per thread.
download_queue << :stop
@@ -254,7 +255,8 @@ class Gem::RequestSet
end
def install_into(dir, force = true, options = {})
- gem_home, ENV['GEM_HOME'] = ENV['GEM_HOME'], dir
+ gem_home = ENV["GEM_HOME"]
+ ENV["GEM_HOME"] = dir
existing = force ? [] : specs_in(dir)
existing.delete_if {|s| @always_install.include? s }
@@ -287,7 +289,7 @@ class Gem::RequestSet
installed
ensure
- ENV['GEM_HOME'] = gem_home
+ ENV["GEM_HOME"] = gem_home
end
##
@@ -303,7 +305,7 @@ class Gem::RequestSet
end
end
- require "rubygems/dependency_installer"
+ require_relative "dependency_installer"
inst = Gem::DependencyInstaller.new options
inst.installed_gems.replace specs
@@ -322,7 +324,7 @@ class Gem::RequestSet
@git_set.root_dir = @install_dir
- lock_file = "#{File.expand_path(path)}.lock".dup.tap(&Gem::UNTAINT)
+ lock_file = "#{File.expand_path(path)}.lock"
begin
tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file lock_file
parser = tokenizer.make_parser self, []
@@ -337,32 +339,32 @@ class Gem::RequestSet
end
def pretty_print(q) # :nodoc:
- q.group 2, '[RequestSet:', ']' do
+ q.group 2, "[RequestSet:", "]" do
q.breakable
if @remote
- q.text 'remote'
+ q.text "remote"
q.breakable
end
if @prerelease
- q.text 'prerelease'
+ q.text "prerelease"
q.breakable
end
if @development_shallow
- q.text 'shallow development'
+ q.text "shallow development"
q.breakable
elsif @development
- q.text 'development'
+ q.text "development"
q.breakable
end
if @soft_missing
- q.text 'soft missing'
+ q.text "soft missing"
end
- q.group 2, '[dependencies:', ']' do
+ q.group 2, "[dependencies:", "]" do
q.breakable
@dependencies.map do |dep|
q.text dep.to_s
@@ -371,10 +373,10 @@ class Gem::RequestSet
end
q.breakable
- q.text 'sets:'
+ q.text "sets:"
q.breakable
- q.pp @sets.map {|set| set.class }
+ q.pp @sets.map(&:class)
end
end
@@ -424,11 +426,11 @@ class Gem::RequestSet
end
def sorted_requests
- @sorted ||= strongly_connected_components.flatten
+ @sorted_requests ||= strongly_connected_components.flatten
end
def specs
- @specs ||= @requests.map {|r| r.full_spec }
+ @specs ||= @requests.map(&:full_spec)
end
def specs_in(dir)
@@ -443,14 +445,14 @@ class Gem::RequestSet
def tsort_each_child(node) # :nodoc:
node.spec.dependencies.each do |dep|
- next if dep.type == :development and not @development
+ next if dep.type == :development && !@development
match = @requests.find do |r|
- dep.match? r.spec.name, r.spec.version, @prerelease
+ dep.match?(r.spec.name, r.spec.version, r.spec.is_a?(Gem::Resolver::InstalledSpecification) || @prerelease)
end
unless match
- next if dep.type == :development and @development_shallow
+ next if dep.type == :development && @development_shallow
next if @soft_missing
raise Gem::DependencyError,
"Unresolved dependency found during sorting - #{dep} (requested by #{node.spec.full_name})"
@@ -461,6 +463,6 @@ class Gem::RequestSet
end
end
-require 'rubygems/request_set/gem_dependency_api'
-require 'rubygems/request_set/lockfile'
-require 'rubygems/request_set/lockfile/tokenizer'
+require_relative "request_set/gem_dependency_api"
+require_relative "request_set/lockfile"
+require_relative "request_set/lockfile/tokenizer"
diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb
index 7188b07346..4347d22ccb 100644
--- a/lib/rubygems/request_set/gem_dependency_api.rb
+++ b/lib/rubygems/request_set/gem_dependency_api.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A semi-compatible DSL for the Bundler Gemfile and Isolate gem dependencies
# files.
@@ -32,136 +33,136 @@
class Gem::RequestSet::GemDependencyAPI
ENGINE_MAP = { # :nodoc:
- :jruby => %w[jruby],
- :jruby_18 => %w[jruby],
- :jruby_19 => %w[jruby],
- :maglev => %w[maglev],
- :mri => %w[ruby],
- :mri_18 => %w[ruby],
- :mri_19 => %w[ruby],
- :mri_20 => %w[ruby],
- :mri_21 => %w[ruby],
- :rbx => %w[rbx],
- :truffleruby => %w[truffleruby],
- :ruby => %w[ruby rbx maglev truffleruby],
- :ruby_18 => %w[ruby rbx maglev truffleruby],
- :ruby_19 => %w[ruby rbx maglev truffleruby],
- :ruby_20 => %w[ruby rbx maglev truffleruby],
- :ruby_21 => %w[ruby rbx maglev truffleruby],
+ jruby: %w[jruby],
+ jruby_18: %w[jruby],
+ jruby_19: %w[jruby],
+ maglev: %w[maglev],
+ mri: %w[ruby],
+ mri_18: %w[ruby],
+ mri_19: %w[ruby],
+ mri_20: %w[ruby],
+ mri_21: %w[ruby],
+ rbx: %w[rbx],
+ truffleruby: %w[truffleruby],
+ ruby: %w[ruby rbx maglev truffleruby],
+ ruby_18: %w[ruby rbx maglev truffleruby],
+ ruby_19: %w[ruby rbx maglev truffleruby],
+ ruby_20: %w[ruby rbx maglev truffleruby],
+ ruby_21: %w[ruby rbx maglev truffleruby],
}.freeze
- mswin = Gem::Platform.new 'x86-mswin32'
- mswin64 = Gem::Platform.new 'x64-mswin64'
- x86_mingw = Gem::Platform.new 'x86-mingw32'
- x64_mingw = Gem::Platform.new 'x64-mingw32'
+ mswin = Gem::Platform.new "x86-mswin32"
+ mswin64 = Gem::Platform.new "x64-mswin64"
+ x86_mingw = Gem::Platform.new "x86-mingw32"
+ x64_mingw = Gem::Platform.new "x64-mingw32"
PLATFORM_MAP = { # :nodoc:
- :jruby => Gem::Platform::RUBY,
- :jruby_18 => Gem::Platform::RUBY,
- :jruby_19 => Gem::Platform::RUBY,
- :maglev => Gem::Platform::RUBY,
- :mingw => x86_mingw,
- :mingw_18 => x86_mingw,
- :mingw_19 => x86_mingw,
- :mingw_20 => x86_mingw,
- :mingw_21 => x86_mingw,
- :mri => Gem::Platform::RUBY,
- :mri_18 => Gem::Platform::RUBY,
- :mri_19 => Gem::Platform::RUBY,
- :mri_20 => Gem::Platform::RUBY,
- :mri_21 => Gem::Platform::RUBY,
- :mswin => mswin,
- :mswin_18 => mswin,
- :mswin_19 => mswin,
- :mswin_20 => mswin,
- :mswin_21 => mswin,
- :mswin64 => mswin64,
- :mswin64_19 => mswin64,
- :mswin64_20 => mswin64,
- :mswin64_21 => mswin64,
- :rbx => Gem::Platform::RUBY,
- :ruby => Gem::Platform::RUBY,
- :ruby_18 => Gem::Platform::RUBY,
- :ruby_19 => Gem::Platform::RUBY,
- :ruby_20 => Gem::Platform::RUBY,
- :ruby_21 => Gem::Platform::RUBY,
- :truffleruby => Gem::Platform::RUBY,
- :x64_mingw => x64_mingw,
- :x64_mingw_20 => x64_mingw,
- :x64_mingw_21 => x64_mingw,
+ jruby: Gem::Platform::RUBY,
+ jruby_18: Gem::Platform::RUBY,
+ jruby_19: Gem::Platform::RUBY,
+ maglev: Gem::Platform::RUBY,
+ mingw: x86_mingw,
+ mingw_18: x86_mingw,
+ mingw_19: x86_mingw,
+ mingw_20: x86_mingw,
+ mingw_21: x86_mingw,
+ mri: Gem::Platform::RUBY,
+ mri_18: Gem::Platform::RUBY,
+ mri_19: Gem::Platform::RUBY,
+ mri_20: Gem::Platform::RUBY,
+ mri_21: Gem::Platform::RUBY,
+ mswin: mswin,
+ mswin_18: mswin,
+ mswin_19: mswin,
+ mswin_20: mswin,
+ mswin_21: mswin,
+ mswin64: mswin64,
+ mswin64_19: mswin64,
+ mswin64_20: mswin64,
+ mswin64_21: mswin64,
+ rbx: Gem::Platform::RUBY,
+ ruby: Gem::Platform::RUBY,
+ ruby_18: Gem::Platform::RUBY,
+ ruby_19: Gem::Platform::RUBY,
+ ruby_20: Gem::Platform::RUBY,
+ ruby_21: Gem::Platform::RUBY,
+ truffleruby: Gem::Platform::RUBY,
+ x64_mingw: x64_mingw,
+ x64_mingw_20: x64_mingw,
+ x64_mingw_21: x64_mingw,
}.freeze
- gt_eq_0 = Gem::Requirement.new '>= 0'
- tilde_gt_1_8_0 = Gem::Requirement.new '~> 1.8.0'
- tilde_gt_1_9_0 = Gem::Requirement.new '~> 1.9.0'
- tilde_gt_2_0_0 = Gem::Requirement.new '~> 2.0.0'
- tilde_gt_2_1_0 = Gem::Requirement.new '~> 2.1.0'
+ gt_eq_0 = Gem::Requirement.new ">= 0"
+ tilde_gt_1_8_0 = Gem::Requirement.new "~> 1.8.0"
+ tilde_gt_1_9_0 = Gem::Requirement.new "~> 1.9.0"
+ tilde_gt_2_0_0 = Gem::Requirement.new "~> 2.0.0"
+ tilde_gt_2_1_0 = Gem::Requirement.new "~> 2.1.0"
VERSION_MAP = { # :nodoc:
- :jruby => gt_eq_0,
- :jruby_18 => tilde_gt_1_8_0,
- :jruby_19 => tilde_gt_1_9_0,
- :maglev => gt_eq_0,
- :mingw => gt_eq_0,
- :mingw_18 => tilde_gt_1_8_0,
- :mingw_19 => tilde_gt_1_9_0,
- :mingw_20 => tilde_gt_2_0_0,
- :mingw_21 => tilde_gt_2_1_0,
- :mri => gt_eq_0,
- :mri_18 => tilde_gt_1_8_0,
- :mri_19 => tilde_gt_1_9_0,
- :mri_20 => tilde_gt_2_0_0,
- :mri_21 => tilde_gt_2_1_0,
- :mswin => gt_eq_0,
- :mswin_18 => tilde_gt_1_8_0,
- :mswin_19 => tilde_gt_1_9_0,
- :mswin_20 => tilde_gt_2_0_0,
- :mswin_21 => tilde_gt_2_1_0,
- :mswin64 => gt_eq_0,
- :mswin64_19 => tilde_gt_1_9_0,
- :mswin64_20 => tilde_gt_2_0_0,
- :mswin64_21 => tilde_gt_2_1_0,
- :rbx => gt_eq_0,
- :ruby => gt_eq_0,
- :ruby_18 => tilde_gt_1_8_0,
- :ruby_19 => tilde_gt_1_9_0,
- :ruby_20 => tilde_gt_2_0_0,
- :ruby_21 => tilde_gt_2_1_0,
- :truffleruby => gt_eq_0,
- :x64_mingw => gt_eq_0,
- :x64_mingw_20 => tilde_gt_2_0_0,
- :x64_mingw_21 => tilde_gt_2_1_0,
+ jruby: gt_eq_0,
+ jruby_18: tilde_gt_1_8_0,
+ jruby_19: tilde_gt_1_9_0,
+ maglev: gt_eq_0,
+ mingw: gt_eq_0,
+ mingw_18: tilde_gt_1_8_0,
+ mingw_19: tilde_gt_1_9_0,
+ mingw_20: tilde_gt_2_0_0,
+ mingw_21: tilde_gt_2_1_0,
+ mri: gt_eq_0,
+ mri_18: tilde_gt_1_8_0,
+ mri_19: tilde_gt_1_9_0,
+ mri_20: tilde_gt_2_0_0,
+ mri_21: tilde_gt_2_1_0,
+ mswin: gt_eq_0,
+ mswin_18: tilde_gt_1_8_0,
+ mswin_19: tilde_gt_1_9_0,
+ mswin_20: tilde_gt_2_0_0,
+ mswin_21: tilde_gt_2_1_0,
+ mswin64: gt_eq_0,
+ mswin64_19: tilde_gt_1_9_0,
+ mswin64_20: tilde_gt_2_0_0,
+ mswin64_21: tilde_gt_2_1_0,
+ rbx: gt_eq_0,
+ ruby: gt_eq_0,
+ ruby_18: tilde_gt_1_8_0,
+ ruby_19: tilde_gt_1_9_0,
+ ruby_20: tilde_gt_2_0_0,
+ ruby_21: tilde_gt_2_1_0,
+ truffleruby: gt_eq_0,
+ x64_mingw: gt_eq_0,
+ x64_mingw_20: tilde_gt_2_0_0,
+ x64_mingw_21: tilde_gt_2_1_0,
}.freeze
WINDOWS = { # :nodoc:
- :mingw => :only,
- :mingw_18 => :only,
- :mingw_19 => :only,
- :mingw_20 => :only,
- :mingw_21 => :only,
- :mri => :never,
- :mri_18 => :never,
- :mri_19 => :never,
- :mri_20 => :never,
- :mri_21 => :never,
- :mswin => :only,
- :mswin_18 => :only,
- :mswin_19 => :only,
- :mswin_20 => :only,
- :mswin_21 => :only,
- :mswin64 => :only,
- :mswin64_19 => :only,
- :mswin64_20 => :only,
- :mswin64_21 => :only,
- :rbx => :never,
- :ruby => :never,
- :ruby_18 => :never,
- :ruby_19 => :never,
- :ruby_20 => :never,
- :ruby_21 => :never,
- :x64_mingw => :only,
- :x64_mingw_20 => :only,
- :x64_mingw_21 => :only,
+ mingw: :only,
+ mingw_18: :only,
+ mingw_19: :only,
+ mingw_20: :only,
+ mingw_21: :only,
+ mri: :never,
+ mri_18: :never,
+ mri_19: :never,
+ mri_20: :never,
+ mri_21: :never,
+ mswin: :only,
+ mswin_18: :only,
+ mswin_19: :only,
+ mswin_20: :only,
+ mswin_21: :only,
+ mswin64: :only,
+ mswin64_19: :only,
+ mswin64_20: :only,
+ mswin64_21: :only,
+ rbx: :never,
+ ruby: :never,
+ ruby_18: :never,
+ ruby_19: :never,
+ ruby_20: :never,
+ ruby_21: :never,
+ x64_mingw: :only,
+ x64_mingw_20: :only,
+ x64_mingw_21: :only,
}.freeze
##
@@ -214,7 +215,7 @@ class Gem::RequestSet::GemDependencyAPI
git_source :github do |repo_name|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include? "/"
- "git://github.com/#{repo_name}.git"
+ "https://github.com/#{repo_name}.git"
end
git_source :bitbucket do |repo_name|
@@ -261,7 +262,7 @@ class Gem::RequestSet::GemDependencyAPI
raise ArgumentError, "no gemspecs found at #{Dir.pwd}"
else
raise ArgumentError,
- "found multiple gemspecs at #{Dir.pwd}, " +
+ "found multiple gemspecs at #{Dir.pwd}, " \
"use the name: option to specify the one you want"
end
end
@@ -279,7 +280,7 @@ class Gem::RequestSet::GemDependencyAPI
# Loads the gem dependency file and returns self.
def load
- instance_eval File.read(@path).tap(&Gem::UNTAINT), @path, 1
+ instance_eval File.read(@path), @path, 1
self
end
@@ -356,7 +357,7 @@ class Gem::RequestSet::GemDependencyAPI
# Use the given tag for git:, gist: and github: dependencies.
def gem(name, *requirements)
- options = requirements.pop if requirements.last.kind_of?(Hash)
+ options = requirements.pop if requirements.last.is_a?(Hash)
options ||= {}
options[:git] = @current_repository if @current_repository
@@ -371,7 +372,7 @@ class Gem::RequestSet::GemDependencyAPI
duplicate = @dependencies.include? name
@dependencies[name] =
- if requirements.empty? and not source_set
+ if requirements.empty? && !source_set
Gem::Requirement.default
elsif source_set
Gem::Requirement.source_set
@@ -435,7 +436,6 @@ Gem dependencies file #{@path} requires #{name} more than once.
reference ||= ref
reference ||= branch
reference ||= tag
- reference ||= 'master'
if ref && branch
warn <<-WARNING
@@ -533,8 +533,8 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
# platform matches the current platform.
def gem_platforms(name, options) # :nodoc:
- platform_names = Array(options.delete :platform)
- platform_names.concat Array(options.delete :platforms)
+ platform_names = Array(options.delete(:platform))
+ platform_names.concat Array(options.delete(:platforms))
platform_names.concat @current_platforms if @current_platforms
return true if platform_names.empty?
@@ -593,7 +593,6 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
@current_repository = repository
yield
-
ensure
@current_repository = nil
end
@@ -637,8 +636,8 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
# :development. Only one group may be specified.
def gemspec(options = {})
- name = options.delete(:name) || '{,*}'
- path = options.delete(:path) || '.'
+ name = options.delete(:name) || "{,*}"
+ path = options.delete(:path) || "."
development_group = options.delete(:development_group) || :development
spec = find_gemspec name, path
@@ -685,7 +684,6 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
@current_groups = groups
yield
-
ensure
@current_groups = nil
end
@@ -697,11 +695,11 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
def pin_gem_source(name, type = :default, source = nil)
source_description =
case type
- when :default then '(default)'
+ when :default then "(default)"
when :path then "path: #{source}"
when :git then "git: #{source}"
when :source then "source: #{source}"
- else '(unknown)'
+ else "(unknown)"
end
raise ArgumentError,
@@ -760,7 +758,6 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
@current_platforms = platforms
yield
-
ensure
@current_platforms = nil
end
@@ -771,7 +768,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
# Block form for restricting gems to a particular set of platforms. See
# #platform.
- alias :platforms :platform
+ alias_method :platforms, :platform
##
# :category: Gem Dependencies DSL
@@ -788,20 +785,20 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
engine_version = options[:engine_version]
raise ArgumentError,
- 'You must specify engine_version along with the Ruby engine' if
- engine and not engine_version
+ "You must specify engine_version along with the Ruby engine" if
+ engine && !engine_version
return true if @installing
- unless RUBY_VERSION == version
- message = "Your Ruby version is #{RUBY_VERSION}, " +
+ unless version == RUBY_VERSION
+ message = "Your Ruby version is #{RUBY_VERSION}, " \
"but your #{gem_deps_file} requires #{version}"
raise Gem::RubyVersionMismatch, message
end
- if engine and engine != Gem.ruby_engine
- message = "Your Ruby engine is #{Gem.ruby_engine}, " +
+ if engine && engine != Gem.ruby_engine
+ message = "Your Ruby engine is #{Gem.ruby_engine}, " \
"but your #{gem_deps_file} requires #{engine}"
raise Gem::RubyVersionMismatch, message
@@ -810,14 +807,14 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
if engine_version
if engine_version != RUBY_ENGINE_VERSION
message =
- "Your Ruby engine version is #{Gem.ruby_engine} #{RUBY_ENGINE_VERSION}, " +
+ "Your Ruby engine version is #{Gem.ruby_engine} #{RUBY_ENGINE_VERSION}, " \
"but your #{gem_deps_file} requires #{engine} #{engine_version}"
raise Gem::RubyVersionMismatch, message
end
end
- return true
+ true
end
##
diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb
index 8f8f142fff..c446b3ae51 100644
--- a/lib/rubygems/request_set/lockfile.rb
+++ b/lib/rubygems/request_set/lockfile.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Parses a gem.deps.rb.lock file and constructs a LockSet containing the
# dependencies found inside. If the lock file is missing no LockSet is
@@ -56,10 +57,10 @@ class Gem::RequestSet::Lockfile
deps[name] = if [Gem::Resolver::VendorSpecification,
Gem::Resolver::GitSpecification].include? spec.class
- Gem::Requirement.source_set
- else
- requirement
- end
+ Gem::Requirement.source_set
+ else
+ requirement
+ end
end
deps
@@ -75,18 +76,13 @@ class Gem::RequestSet::Lockfile
@dependencies = dependencies
@gem_deps_file = File.expand_path(gem_deps_file)
@gem_deps_dir = File.dirname(@gem_deps_file)
-
- if RUBY_VERSION < '2.7'
- @gem_deps_file.untaint unless gem_deps_file.tainted?
- end
-
@platforms = []
end
def add_DEPENDENCIES(out) # :nodoc:
out << "DEPENDENCIES"
- out.concat @dependencies.sort_by {|name,| name }.map {|name, requirement|
+ out.concat @dependencies.sort.map {|name, requirement|
" #{name}#{requirement.for_lockfile}"
}
@@ -105,10 +101,10 @@ class Gem::RequestSet::Lockfile
out << " remote: #{group}"
out << " specs:"
- requests.sort_by {|request| request.name }.each do |request|
- next if request.spec.name == 'bundler'
+ requests.sort_by(&:name).each do |request|
+ next if request.spec.name == "bundler"
platform = "-#{request.spec.platform}" unless
- Gem::Platform::RUBY == request.spec.platform
+ request.spec.platform == Gem::Platform::RUBY
out << " #{request.name} (#{request.version}#{platform})"
@@ -137,10 +133,10 @@ class Gem::RequestSet::Lockfile
out << " revision: #{revision}"
out << " specs:"
- requests.sort_by {|request| request.name }.each do |request|
+ requests.sort_by(&:name).each do |request|
out << " #{request.name} (#{request.version})"
- dependencies = request.spec.dependencies.sort_by {|dep| dep.name }
+ dependencies = request.spec.dependencies.sort_by(&:name)
dependencies.each do |dep|
out << " #{dep.name}#{dep.requirement.for_lockfile}"
end
@@ -156,7 +152,7 @@ class Gem::RequestSet::Lockfile
if dest.index(base) == 0
offset = dest[base.size + 1..-1]
- return '.' unless offset
+ return "." unless offset
offset
else
@@ -184,7 +180,7 @@ class Gem::RequestSet::Lockfile
platforms = requests.map {|request| request.spec.platform }.uniq
- platforms = platforms.sort_by {|platform| platform.to_s }
+ platforms = platforms.sort_by(&:to_s)
platforms.each do |platform|
out << " #{platform}"
@@ -224,7 +220,7 @@ class Gem::RequestSet::Lockfile
def write
content = to_s
- File.open "#{@gem_deps_file}.lock", 'w' do |io|
+ File.open "#{@gem_deps_file}.lock", "w" do |io|
io.write content
end
end
@@ -236,4 +232,4 @@ class Gem::RequestSet::Lockfile
end
end
-require 'rubygems/request_set/lockfile/tokenizer'
+require_relative "lockfile/tokenizer"
diff --git a/lib/rubygems/request_set/lockfile/parser.rb b/lib/rubygems/request_set/lockfile/parser.rb
index 8c12b435af..e751a1445e 100644
--- a/lib/rubygems/request_set/lockfile/parser.rb
+++ b/lib/rubygems/request_set/lockfile/parser.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
class Gem::RequestSet::Lockfile::Parser
###
# Parses lockfiles
@@ -19,18 +20,18 @@ class Gem::RequestSet::Lockfile::Parser
@tokens.skip :newline
case token.value
- when 'DEPENDENCIES' then
+ when "DEPENDENCIES" then
parse_DEPENDENCIES
- when 'GIT' then
+ when "GIT" then
parse_GIT
- when 'GEM' then
+ when "GEM" then
parse_GEM
- when 'PATH' then
+ when "PATH" then
parse_PATH
- when 'PLATFORMS' then
+ when "PLATFORMS" then
parse_PLATFORMS
else
- token = get until @tokens.empty? or peek.first == :section
+ token = get until @tokens.empty? || peek.first == :section
end
else
raise "BUG: unhandled token #{token.type} (#{token.value.inspect}) at line #{token.line} column #{token.column}"
@@ -44,20 +45,20 @@ class Gem::RequestSet::Lockfile::Parser
def get(expected_types = nil, expected_value = nil) # :nodoc:
token = @tokens.shift
- if expected_types and not Array(expected_types).include? token.type
+ if expected_types && !Array(expected_types).include?(token.type)
unget token
- message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " +
+ message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " \
"expected #{expected_types.inspect}"
raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename
end
- if expected_value and expected_value != token.value
+ if expected_value && expected_value != token.value
unget token
- message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " +
- "expected [#{expected_types.inspect}, " +
+ message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " \
+ "expected [#{expected_types.inspect}, " \
"#{expected_value.inspect}]"
raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename
@@ -67,7 +68,7 @@ class Gem::RequestSet::Lockfile::Parser
end
def parse_DEPENDENCIES # :nodoc:
- while not @tokens.empty? and :text == peek.type do
+ while !@tokens.empty? && peek.type == :text do
token = get :text
requirements = []
@@ -110,8 +111,8 @@ class Gem::RequestSet::Lockfile::Parser
def parse_GEM # :nodoc:
sources = []
- while [:entry, 'remote'] == peek.first(2) do
- get :entry, 'remote'
+ while peek.first(2) == [:entry, "remote"] do
+ get :entry, "remote"
data = get(:text).value
skip :newline
@@ -120,14 +121,14 @@ class Gem::RequestSet::Lockfile::Parser
sources << Gem::Source.new(Gem::DEFAULT_HOST) if sources.empty?
- get :entry, 'specs'
+ get :entry, "specs"
skip :newline
set = Gem::Resolver::LockSet.new sources
last_specs = nil
- while not @tokens.empty? and :text == peek.type do
+ while !@tokens.empty? && peek.type == :text do
token = get :text
name = token.value
column = token.column
@@ -144,8 +145,8 @@ class Gem::RequestSet::Lockfile::Parser
type = token.type
data = token.value
- if type == :text and column == 4
- version, platform = data.split '-', 2
+ if type == :text && column == 4
+ version, platform = data.split "-", 2
platform =
platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
@@ -171,26 +172,26 @@ class Gem::RequestSet::Lockfile::Parser
end
def parse_GIT # :nodoc:
- get :entry, 'remote'
+ get :entry, "remote"
repository = get(:text).value
skip :newline
- get :entry, 'revision'
+ get :entry, "revision"
revision = get(:text).value
skip :newline
type = peek.type
value = peek.value
- if type == :entry and %w[branch ref tag].include? value
+ if type == :entry && %w[branch ref tag].include?(value)
get
get :text
skip :newline
end
- get :entry, 'specs'
+ get :entry, "specs"
skip :newline
@@ -199,7 +200,7 @@ class Gem::RequestSet::Lockfile::Parser
last_spec = nil
- while not @tokens.empty? and :text == peek.type do
+ while !@tokens.empty? && peek.type == :text do
token = get :text
name = token.value
column = token.column
@@ -214,7 +215,7 @@ class Gem::RequestSet::Lockfile::Parser
type = token.type
data = token.value
- if type == :text and column == 4
+ if type == :text && column == 4
last_spec = set.add_git_spec name, data, repository, revision, true
else
dependency = parse_dependency name, data
@@ -234,19 +235,19 @@ class Gem::RequestSet::Lockfile::Parser
end
def parse_PATH # :nodoc:
- get :entry, 'remote'
+ get :entry, "remote"
directory = get(:text).value
skip :newline
- get :entry, 'specs'
+ get :entry, "specs"
skip :newline
set = Gem::Resolver::VendorSet.new
last_spec = nil
- while not @tokens.empty? and :text == peek.first do
+ while !@tokens.empty? && peek.first == :text do
token = get :text
name = token.value
column = token.column
@@ -261,7 +262,7 @@ class Gem::RequestSet::Lockfile::Parser
type = token.type
data = token.value
- if type == :text and column == 4
+ if type == :text && column == 4
last_spec = set.add_vendor_gem name, directory
else
dependency = parse_dependency name, data
@@ -281,7 +282,7 @@ class Gem::RequestSet::Lockfile::Parser
end
def parse_PLATFORMS # :nodoc:
- while not @tokens.empty? and :text == peek.first do
+ while !@tokens.empty? && peek.first == :text do
name = get(:text).value
@platforms << name
@@ -331,7 +332,7 @@ class Gem::RequestSet::Lockfile::Parser
set.find_all(requirement)
end.compact.first
- specification && specification.version
+ specification&.version
end
##
diff --git a/lib/rubygems/request_set/lockfile/tokenizer.rb b/lib/rubygems/request_set/lockfile/tokenizer.rb
index 6918e8e1a5..65cef3baa0 100644
--- a/lib/rubygems/request_set/lockfile/tokenizer.rb
+++ b/lib/rubygems/request_set/lockfile/tokenizer.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
-require 'rubygems/request_set/lockfile/parser'
+
+# ) frozen_string_literal: true
+require_relative "parser"
class Gem::RequestSet::Lockfile::Tokenizer
Token = Struct.new :type, :value, :column, :line
@@ -26,7 +28,7 @@ class Gem::RequestSet::Lockfile::Tokenizer
end
def skip(type)
- @tokens.shift while not @tokens.empty? and peek.type == type
+ @tokens.shift while !@tokens.empty? && peek.type == type
end
##
@@ -48,7 +50,7 @@ class Gem::RequestSet::Lockfile::Tokenizer
def next_token
@tokens.shift
end
- alias :shift :next_token
+ alias_method :shift, :next_token
def peek
@tokens.first || EOF
@@ -57,7 +59,7 @@ class Gem::RequestSet::Lockfile::Tokenizer
private
def tokenize(input)
- require 'strscan'
+ require "strscan"
s = StringScanner.new input
until s.eos? do
@@ -73,13 +75,14 @@ class Gem::RequestSet::Lockfile::Tokenizer
end
@tokens <<
- case
- when s.scan(/\r?\n/) then
+ if s.scan(/\r?\n/)
+
token = Token.new(:newline, nil, *token_pos(pos))
@line_pos = s.pos
@line += 1
token
- when s.scan(/[A-Z]+/) then
+ elsif s.scan(/[A-Z]+/)
+
if leading_whitespace
text = s.matched
text += s.scan(/[^\s)]*/).to_s # in case of no match
@@ -87,20 +90,27 @@ class Gem::RequestSet::Lockfile::Tokenizer
else
Token.new(:section, s.matched, *token_pos(pos))
end
- when s.scan(/([a-z]+):\s/) then
+ elsif s.scan(/([a-z]+):\s/)
+
s.pos -= 1 # rewind for possible newline
Token.new(:entry, s[1], *token_pos(pos))
- when s.scan(/\(/) then
+ elsif s.scan(/\(/)
+
Token.new(:l_paren, nil, *token_pos(pos))
- when s.scan(/\)/) then
+ elsif s.scan(/\)/)
+
Token.new(:r_paren, nil, *token_pos(pos))
- when s.scan(/<=|>=|=|~>|<|>|!=/) then
+ elsif s.scan(/<=|>=|=|~>|<|>|!=/)
+
Token.new(:requirement, s.matched, *token_pos(pos))
- when s.scan(/,/) then
+ elsif s.scan(/,/)
+
Token.new(:comma, nil, *token_pos(pos))
- when s.scan(/!/) then
+ elsif s.scan(/!/)
+
Token.new(:bang, nil, *token_pos(pos))
- when s.scan(/[^\s),!]*/) then
+ elsif s.scan(/[^\s),!]*/)
+
Token.new(:text, s.matched, *token_pos(pos))
else
raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}"
diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb
index 6721de4055..02543cb14a 100644
--- a/lib/rubygems/requirement.rb
+++ b/lib/rubygems/requirement.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
-require "rubygems/deprecate"
+
+require_relative "version"
##
# A Requirement is a set of one or more version restrictions. It supports a
@@ -9,14 +10,14 @@ require "rubygems/deprecate"
# together in RubyGems.
class Gem::Requirement
- OPS = { #:nodoc:
- "=" => lambda {|v, r| v == r },
- "!=" => lambda {|v, r| v != r },
- ">" => lambda {|v, r| v > r },
- "<" => lambda {|v, r| v < r },
- ">=" => lambda {|v, r| v >= r },
- "<=" => lambda {|v, r| v <= r },
- "~>" => lambda {|v, r| v >= r && v.release < r.bump },
+ OPS = { # :nodoc:
+ "=" => lambda {|v, r| v == r },
+ "!=" => lambda {|v, r| v != r },
+ ">" => lambda {|v, r| v > r },
+ "<" => lambda {|v, r| v < r },
+ ">=" => lambda {|v, r| v >= r },
+ "<=" => lambda {|v, r| v <= r },
+ "~>" => lambda {|v, r| v >= r && v.release < r.bump },
}.freeze
SOURCE_SET_REQUIREMENT = Struct.new(:for_lockfile).new "!" # :nodoc:
@@ -27,7 +28,7 @@ class Gem::Requirement
##
# A regular expression that matches a requirement
- PATTERN = /\A#{PATTERN_RAW}\z/.freeze
+ PATTERN = /\A#{PATTERN_RAW}\z/
##
# The default requirement matches any non-prerelease version
@@ -61,7 +62,7 @@ class Gem::Requirement
input
when Gem::Version, Array then
new input
- when '!' then
+ when "!" then
source_set
else
if input.respond_to? :to_str
@@ -73,11 +74,11 @@ class Gem::Requirement
end
def self.default
- new '>= 0'
+ new ">= 0"
end
def self.default_prerelease
- new '>= 0.a'
+ new ">= 0.a"
end
###
@@ -119,7 +120,7 @@ class Gem::Requirement
# An array of requirement pairs. The first element of the pair is
# the op, and the second is the Gem::Version.
- attr_reader :requirements #:nodoc:
+ attr_reader :requirements # :nodoc:
##
# Constructs a requirement from +requirements+. Requirements can be
@@ -155,7 +156,7 @@ class Gem::Requirement
# Formats this requirement for use in a Gem::RequestSet::Lockfile.
def for_lockfile # :nodoc:
- return if [DefaultRequirement] == @requirements
+ return if @requirements == [DefaultRequirement]
list = requirements.sort_by do |_, version|
version
@@ -163,7 +164,7 @@ class Gem::Requirement
"#{op} #{version}"
end.uniq
- " (#{list.join ', '})"
+ " (#{list.join ", "})"
end
##
@@ -194,24 +195,19 @@ class Gem::Requirement
end
def marshal_dump # :nodoc:
- fix_syck_default_key_in_requirements
-
[@requirements]
end
def marshal_load(array) # :nodoc:
@requirements = array[0]
- fix_syck_default_key_in_requirements
+ raise TypeError, "wrong @requirements" unless Array === @requirements
end
def yaml_initialize(tag, vals) # :nodoc:
vals.each do |ivar, val|
instance_variable_set "@#{ivar}", val
end
-
- Gem.load_yaml
- fix_syck_default_key_in_requirements
end
def init_with(coder) # :nodoc:
@@ -223,7 +219,7 @@ class Gem::Requirement
end
def encode_with(coder) # :nodoc:
- coder.add 'requirements', @requirements
+ coder.add "requirements", @requirements
end
##
@@ -235,7 +231,7 @@ class Gem::Requirement
end
def pretty_print(q) # :nodoc:
- q.group 1, 'Gem::Requirement.new(', ')' do
+ q.group 1, "Gem::Requirement.new(", ")" do
q.pp as_list
end
end
@@ -246,12 +242,11 @@ class Gem::Requirement
def satisfied_by?(version)
raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless
Gem::Version === version
- # #28965: syck has a bug with unquoted '=' YAML.loading as YAML::DefaultKey
- requirements.all? {|op, rv| (OPS[op] || OPS["="]).call version, rv }
+ requirements.all? {|op, rv| OPS[op].call version, rv }
end
- alias :=== :satisfied_by?
- alias :=~ :satisfied_by?
+ alias_method :===, :satisfied_by?
+ alias_method :=~, :satisfied_by?
##
# True if the requirement will not always match the latest version.
@@ -259,7 +254,7 @@ class Gem::Requirement
def specific?
return true if @requirements.length > 1 # GIGO, > 1, > 2 is silly
- not %w[> >=].include? @requirements.first.first # grab the operator
+ !%w[> >=].include? @requirements.first.first # grab the operator
end
def to_s # :nodoc:
@@ -290,17 +285,9 @@ class Gem::Requirement
@_tilde_requirements ||= _sorted_requirements.select {|r| r.first == "~>" }
end
- private
-
- def fix_syck_default_key_in_requirements # :nodoc:
- Gem.load_yaml
-
- # Fixup the Syck DefaultKey bug
- @requirements.each do |r|
- if r[0].kind_of? Gem::SyckDefaultKey
- r[0] = "="
- end
- end
+ def initialize_copy(other) # :nodoc:
+ @requirements = other.requirements.dup
+ super
end
end
diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb
index 71c35ea3d3..115c716b6b 100644
--- a/lib/rubygems/resolver.rb
+++ b/lib/rubygems/resolver.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
-require 'rubygems/dependency'
-require 'rubygems/exceptions'
-require 'rubygems/util/list'
+
+require_relative "dependency"
+require_relative "exceptions"
+require_relative "util/list"
##
# Given a set of Gem::Dependency objects as +needed+ and a way to query the
@@ -10,14 +11,14 @@ require 'rubygems/util/list'
# all the requirements.
class Gem::Resolver
- require 'rubygems/resolver/molinillo'
+ require_relative "vendored_molinillo"
##
# If the DEBUG_RESOLVER environment variable is set then debugging mode is
# enabled for the resolver. This will display information about the state
# of the resolver while a set of dependencies is being resolved.
- DEBUG_RESOLVER = !ENV['DEBUG_RESOLVER'].nil?
+ DEBUG_RESOLVER = !ENV["DEBUG_RESOLVER"].nil?
##
# Set to true if all development dependencies should be considered.
@@ -37,8 +38,6 @@ class Gem::Resolver
##
# List of dependencies that could not be found in the configured sources.
- attr_reader :missing
-
attr_reader :stats
##
@@ -48,8 +47,7 @@ class Gem::Resolver
attr_accessor :skip_gems
##
- # When a missing dependency, don't stop. Just go on and record what was
- # missing.
+ #
attr_accessor :soft_missing
@@ -74,7 +72,7 @@ class Gem::Resolver
case sets.length
when 0 then
- raise ArgumentError, 'one set in the composition must be non-nil'
+ raise ArgumentError, "one set in the composition must be non-nil"
when 1 then
sets.first
else
@@ -105,7 +103,6 @@ class Gem::Resolver
@development = false
@development_shallow = false
@ignore_dependencies = false
- @missing = []
@skip_gems = {}
@soft_missing = false
@stats = Gem::Resolver::Stats.new
@@ -114,7 +111,7 @@ class Gem::Resolver
def explain(stage, *data) # :nodoc:
return unless DEBUG_RESOLVER
- d = data.map {|x| x.pretty_inspect }.join(", ")
+ d = data.map(&:pretty_inspect).join(", ")
$stderr.printf "%10s %s\n", stage.to_s.upcase, d
end
@@ -124,7 +121,7 @@ class Gem::Resolver
data = yield
$stderr.printf "%10s (%d entries)\n", stage.to_s.upcase, data.size
unless data.empty?
- require 'pp'
+ require "pp"
PP.pp data, $stderr
end
end
@@ -144,7 +141,7 @@ class Gem::Resolver
activation_request =
Gem::Resolver::ActivationRequest.new spec, dep, possible
- return spec, activation_request
+ [spec, activation_request]
end
def requests(s, act, reqs=[]) # :nodoc:
@@ -153,10 +150,10 @@ class Gem::Resolver
s.fetch_development_dependencies if @development
s.dependencies.reverse_each do |d|
- next if d.type == :development and not @development
- next if d.type == :development and @development_shallow and
+ next if d.type == :development && !@development
+ next if d.type == :development && @development_shallow &&
act.development?
- next if d.type == :development and @development_shallow and
+ next if d.type == :development && @development_shallow &&
act.parent
reqs << Gem::Resolver::DependencyRequest.new(d, act)
@@ -170,29 +167,28 @@ class Gem::Resolver
reqs
end
- include Molinillo::UI
+ include Gem::Molinillo::UI
def output
- @output ||= debug? ? $stdout : File.open(IO::NULL, 'w')
+ @output ||= debug? ? $stdout : File.open(IO::NULL, "w")
end
def debug?
DEBUG_RESOLVER
end
- include Molinillo::SpecificationProvider
+ include Gem::Molinillo::SpecificationProvider
##
# Proceed with resolution! Returns an array of ActivationRequest objects.
def resolve
- locking_dg = Molinillo::DependencyGraph.new
- Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }, locking_dg).tsort.map(&:payload).compact
- rescue Molinillo::VersionConflict => e
+ Gem::Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }).tsort.map(&:payload).compact
+ rescue Gem::Molinillo::VersionConflict => e
conflict = e.conflicts.values.first
raise Gem::DependencyResolutionError, Conflict.new(conflict.requirement_trees.first.first, conflict.existing, conflict.requirement)
ensure
- @output.close if defined?(@output) and !debug?
+ @output.close if defined?(@output) && !debug?
end
##
@@ -212,7 +208,7 @@ class Gem::Resolver
matching_platform = select_local_platforms all
- return matching_platform, all
+ [matching_platform, all]
end
##
@@ -227,7 +223,6 @@ class Gem::Resolver
def search_for(dependency)
possibles, all = find_possible(dependency)
if !@soft_missing && possibles.empty?
- @missing << dependency
exc = Gem::UnsatisfiableDependencyError.new dependency, all
exc.errors = @set.errors
raise exc
@@ -246,7 +241,7 @@ class Gem::Resolver
sources.each do |source|
groups[source].
- sort_by {|spec| [spec.version, Gem::Platform.local =~ spec.platform ? 1 : 0] }.
+ sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch
map {|spec| ActivationRequest.new spec, dependency }.
each {|activation_request| activation_requests << activation_request }
end
@@ -274,7 +269,6 @@ class Gem::Resolver
end
def allow_missing?(dependency)
- @missing << dependency
@soft_missing
end
@@ -318,30 +312,30 @@ class Gem::Resolver
private :amount_constrained
end
-require 'rubygems/resolver/activation_request'
-require 'rubygems/resolver/conflict'
-require 'rubygems/resolver/dependency_request'
-require 'rubygems/resolver/requirement_list'
-require 'rubygems/resolver/stats'
-
-require 'rubygems/resolver/set'
-require 'rubygems/resolver/api_set'
-require 'rubygems/resolver/composed_set'
-require 'rubygems/resolver/best_set'
-require 'rubygems/resolver/current_set'
-require 'rubygems/resolver/git_set'
-require 'rubygems/resolver/index_set'
-require 'rubygems/resolver/installer_set'
-require 'rubygems/resolver/lock_set'
-require 'rubygems/resolver/vendor_set'
-require 'rubygems/resolver/source_set'
-
-require 'rubygems/resolver/specification'
-require 'rubygems/resolver/spec_specification'
-require 'rubygems/resolver/api_specification'
-require 'rubygems/resolver/git_specification'
-require 'rubygems/resolver/index_specification'
-require 'rubygems/resolver/installed_specification'
-require 'rubygems/resolver/local_specification'
-require 'rubygems/resolver/lock_specification'
-require 'rubygems/resolver/vendor_specification'
+require_relative "resolver/activation_request"
+require_relative "resolver/conflict"
+require_relative "resolver/dependency_request"
+require_relative "resolver/requirement_list"
+require_relative "resolver/stats"
+
+require_relative "resolver/set"
+require_relative "resolver/api_set"
+require_relative "resolver/composed_set"
+require_relative "resolver/best_set"
+require_relative "resolver/current_set"
+require_relative "resolver/git_set"
+require_relative "resolver/index_set"
+require_relative "resolver/installer_set"
+require_relative "resolver/lock_set"
+require_relative "resolver/vendor_set"
+require_relative "resolver/source_set"
+
+require_relative "resolver/specification"
+require_relative "resolver/spec_specification"
+require_relative "resolver/api_specification"
+require_relative "resolver/git_specification"
+require_relative "resolver/index_specification"
+require_relative "resolver/installed_specification"
+require_relative "resolver/local_specification"
+require_relative "resolver/lock_specification"
+require_relative "resolver/vendor_specification"
diff --git a/lib/rubygems/resolver/activation_request.rb b/lib/rubygems/resolver/activation_request.rb
index ae35681db9..fc9ff58f57 100644
--- a/lib/rubygems/resolver/activation_request.rb
+++ b/lib/rubygems/resolver/activation_request.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Specifies a Specification object that should be activated. Also contains a
# dependency that was used to introduce this activation.
@@ -58,10 +59,8 @@ class Gem::Resolver::ActivationRequest
if @spec.respond_to? :sources
exception = nil
path = @spec.sources.find do |source|
- begin
- source.download full_spec, path
- rescue exception
- end
+ source.download full_spec, path
+ rescue exception
end
return path if path
raise exception if exception
@@ -93,9 +92,7 @@ class Gem::Resolver::ActivationRequest
end
def inspect # :nodoc:
- '#<%s for %p from %s>' % [
- self.class, @spec, @request
- ]
+ format("#<%s for %p from %s>", self.class, @spec, @request)
end
##
@@ -130,12 +127,12 @@ class Gem::Resolver::ActivationRequest
end
def pretty_print(q) # :nodoc:
- q.group 2, '[Activation request', ']' do
+ q.group 2, "[Activation request", "]" do
q.breakable
q.pp @spec
q.breakable
- q.text ' for '
+ q.text " for "
q.pp @request
end
end
diff --git a/lib/rubygems/resolver/api_set.rb b/lib/rubygems/resolver/api_set.rb
index 21c9b8920c..3e4dadc40f 100644
--- a/lib/rubygems/resolver/api_set.rb
+++ b/lib/rubygems/resolver/api_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The global rubygems pool, available via the rubygems.org API.
# Returns instances of APISpecification.
@@ -26,13 +27,13 @@ class Gem::Resolver::APISet < Gem::Resolver::Set
# API URL +dep_uri+ which is described at
# https://guides.rubygems.org/rubygems-org-api
- def initialize(dep_uri = 'https://index.rubygems.org/info/')
+ def initialize(dep_uri = "https://index.rubygems.org/info/")
super()
- dep_uri = URI dep_uri unless URI === dep_uri
+ dep_uri = Gem::URI dep_uri unless Gem::URI === dep_uri
@dep_uri = dep_uri
- @uri = dep_uri + '..'
+ @uri = dep_uri + ".."
@data = Hash.new {|h,k| h[k] = [] }
@source = Gem::Source.new @uri
@@ -75,7 +76,8 @@ class Gem::Resolver::APISet < Gem::Resolver::Set
end
def prefetch_now # :nodoc:
- needed, @to_fetch = @to_fetch, []
+ needed = @to_fetch
+ @to_fetch = []
needed.sort.each do |name|
versions(name)
@@ -83,12 +85,12 @@ class Gem::Resolver::APISet < Gem::Resolver::Set
end
def pretty_print(q) # :nodoc:
- q.group 2, '[APISet', ']' do
+ q.group 2, "[APISet", "]" do
q.breakable
q.text "URI: #{@dep_uri}"
q.breakable
- q.text 'gem names:'
+ q.text "gem names:"
q.pp @data.keys
end
end
diff --git a/lib/rubygems/resolver/api_set/gem_parser.rb b/lib/rubygems/resolver/api_set/gem_parser.rb
index 685c39558d..643b857107 100644
--- a/lib/rubygems/resolver/api_set/gem_parser.rb
+++ b/lib/rubygems/resolver/api_set/gem_parser.rb
@@ -1,12 +1,15 @@
# frozen_string_literal: true
class Gem::Resolver::APISet::GemParser
+ EMPTY_ARRAY = [].freeze
+ private_constant :EMPTY_ARRAY
+
def parse(line)
version_and_platform, rest = line.split(" ", 2)
version, platform = version_and_platform.split("-", 2)
- dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
- dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
- requirements = requirements ? requirements.map {|d| parse_dependency(d) } : []
+ dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest
+ dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : EMPTY_ARRAY
+ requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : EMPTY_ARRAY
[version, platform, dependencies, requirements]
end
@@ -15,6 +18,7 @@ class Gem::Resolver::APISet::GemParser
def parse_dependency(string)
dependency = string.split(":")
dependency[-1] = dependency[-1].split("&") if dependency.size > 1
+ dependency[0] = -dependency[0]
dependency
end
end
diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb
index b5aa0b71d4..a14bcbfeb1 100644
--- a/lib/rubygems/resolver/api_specification.rb
+++ b/lib/rubygems/resolver/api_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Represents a specification retrieved via the rubygems.org API.
#
@@ -21,7 +22,7 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification
# Creates an APISpecification for the given +set+ from the rubygems.org
# +api_data+.
#
- # See https://guides.rubygems.org/rubygems-org-api/#misc_methods for the
+ # See https://guides.rubygems.org/rubygems-org-api/#misc-methods for the
# format of the +api_data+.
def initialize(set, api_data)
@@ -40,10 +41,10 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification
end
def ==(other) # :nodoc:
- self.class === other and
- @set == other.set and
- @name == other.name and
- @version == other.version and
+ self.class === other &&
+ @set == other.set &&
+ @name == other.name &&
+ @version == other.version &&
@platform == other.platform
end
@@ -62,7 +63,7 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification
end
def pretty_print(q) # :nodoc:
- q.group 2, '[APISpecification', ']' do
+ q.group 2, "[APISpecification", "]" do
q.breakable
q.text "name: #{name}"
@@ -73,7 +74,7 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification
q.text "platform: #{platform}"
q.breakable
- q.text 'dependencies:'
+ q.text "dependencies:"
q.breakable
q.pp @dependencies
diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb
index 300ea8015c..a983f8c6b6 100644
--- a/lib/rubygems/resolver/best_set.rb
+++ b/lib/rubygems/resolver/best_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The BestSet chooses the best available method to query a remote index.
#
@@ -25,7 +26,7 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet
end
def find_all(req) # :nodoc:
- pick_sets if @remote and @sets.empty?
+ pick_sets if @remote && @sets.empty?
super
rescue Gem::RemoteFetcher::FetchError => e
@@ -35,15 +36,15 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet
end
def prefetch(reqs) # :nodoc:
- pick_sets if @remote and @sets.empty?
+ pick_sets if @remote && @sets.empty?
super
end
def pretty_print(q) # :nodoc:
- q.group 2, '[BestSet', ']' do
+ q.group 2, "[BestSet", "]" do
q.breakable
- q.text 'sets:'
+ q.text "sets:"
q.breakable
q.pp @sets
@@ -59,11 +60,11 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet
def replace_failed_api_set(error) # :nodoc:
uri = error.original_uri
- uri = URI uri unless URI === uri
- uri = uri + "."
+ uri = Gem::URI uri unless Gem::URI === uri
+ uri += "."
raise error unless api_set = @sets.find do |set|
- Gem::Resolver::APISet === set and set.dep_uri == uri
+ Gem::Resolver::APISet === set && set.dep_uri == uri
end
index_set = Gem::Resolver::IndexSet.new api_set.source
diff --git a/lib/rubygems/resolver/composed_set.rb b/lib/rubygems/resolver/composed_set.rb
index 226da1e1e0..8a714ad447 100644
--- a/lib/rubygems/resolver/composed_set.rb
+++ b/lib/rubygems/resolver/composed_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A ComposedSet allows multiple sets to be queried like a single set.
#
@@ -43,7 +44,7 @@ class Gem::Resolver::ComposedSet < Gem::Resolver::Set
end
def errors
- @errors + @sets.map {|set| set.errors }.flatten
+ @errors + @sets.map(&:errors).flatten
end
##
diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb
index 4c4588d7e8..367a36b43d 100644
--- a/lib/rubygems/resolver/conflict.rb
+++ b/lib/rubygems/resolver/conflict.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Used internally to indicate that a dependency conflicted
# with a spec that would be activated.
@@ -27,9 +28,9 @@ class Gem::Resolver::Conflict
end
def ==(other) # :nodoc:
- self.class === other and
- @dependency == other.dependency and
- @activated == other.activated and
+ self.class === other &&
+ @dependency == other.dependency &&
+ @activated == other.activated &&
@failed_dep == other.failed_dep
end
@@ -54,7 +55,7 @@ class Gem::Resolver::Conflict
activated = @activated.spec.full_name
dependency = @failed_dep.dependency
requirement = dependency.requirement
- alternates = dependency.matching_specs.map {|spec| spec.full_name }
+ alternates = dependency.matching_specs.map(&:full_name)
unless alternates.empty?
matching = <<-MATCHING.chomp
@@ -63,10 +64,7 @@ class Gem::Resolver::Conflict
%s
MATCHING
- matching = matching % [
- dependency,
- alternates.join(', '),
- ]
+ matching = format(matching, dependency, alternates.join(", "))
end
explanation = <<-EXPLANATION
@@ -81,12 +79,7 @@ class Gem::Resolver::Conflict
%s
EXPLANATION
- explanation % [
- activated, requirement,
- request_path(@activated).reverse.join(", depends on\n "),
- request_path(@failed_dep).reverse.join(", depends on\n "),
- matching
- ]
+ format(explanation, activated, requirement, request_path(@activated).reverse.join(", depends on\n "), request_path(@failed_dep).reverse.join(", depends on\n "), matching)
end
##
@@ -97,21 +90,21 @@ class Gem::Resolver::Conflict
end
def pretty_print(q) # :nodoc:
- q.group 2, '[Dependency conflict: ', ']' do
+ q.group 2, "[Dependency conflict: ", "]" do
q.breakable
- q.text 'activated '
+ q.text "activated "
q.pp @activated
q.breakable
- q.text ' dependency '
+ q.text " dependency "
q.pp @dependency
q.breakable
if @dependency == @failed_dep
- q.text ' failed'
+ q.text " failed"
else
- q.text ' failed dependency '
+ q.text " failed dependency "
q.pp @failed_dep
end
end
@@ -131,7 +124,7 @@ class Gem::Resolver::Conflict
current = current.parent
when Gem::Resolver::DependencyRequest then
- path << "#{current.dependency}"
+ path << current.dependency.to_s
current = current.requester
else
@@ -139,7 +132,7 @@ class Gem::Resolver::Conflict
end
end
- path = ['user request (gem command or Gemfile)'] if path.empty?
+ path = ["user request (gem command or Gemfile)"] if path.empty?
path
end
diff --git a/lib/rubygems/resolver/current_set.rb b/lib/rubygems/resolver/current_set.rb
index c3aa3a2c37..370e445089 100644
--- a/lib/rubygems/resolver/current_set.rb
+++ b/lib/rubygems/resolver/current_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A set which represents the installed gems. Respects
# all the normal settings that control where to look
diff --git a/lib/rubygems/resolver/dependency_request.rb b/lib/rubygems/resolver/dependency_request.rb
index 356aadb3b2..60b338277f 100644
--- a/lib/rubygems/resolver/dependency_request.rb
+++ b/lib/rubygems/resolver/dependency_request.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Used Internally. Wraps a Dependency object to also track which spec
# contained the Dependency.
@@ -95,12 +96,12 @@ class Gem::Resolver::DependencyRequest
end
def pretty_print(q) # :nodoc:
- q.group 2, '[Dependency request ', ']' do
+ q.group 2, "[Dependency request ", "]" do
q.breakable
q.text @dependency.to_s
q.breakable
- q.text ' requested by '
+ q.text " requested by "
q.pp @requester
end
end
diff --git a/lib/rubygems/resolver/git_set.rb b/lib/rubygems/resolver/git_set.rb
index eac51f15ad..89342ff80d 100644
--- a/lib/rubygems/resolver/git_set.rb
+++ b/lib/rubygems/resolver/git_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A GitSet represents gems that are sourced from git repositories.
#
@@ -35,7 +36,7 @@ class Gem::Resolver::GitSet < Gem::Resolver::Set
def initialize # :nodoc:
super()
- @git = ENV['git'] || 'git'
+ @git = ENV["git"] || "git"
@need_submodules = {}
@repositories = {}
@root_dir = Gem.dir
@@ -104,7 +105,7 @@ class Gem::Resolver::GitSet < Gem::Resolver::Set
end
def pretty_print(q) # :nodoc:
- q.group 2, '[GitSet', ']' do
+ q.group 2, "[GitSet", "]" do
next if @repositories.empty?
q.breakable
diff --git a/lib/rubygems/resolver/git_specification.rb b/lib/rubygems/resolver/git_specification.rb
index 555dcffc22..e587c17d2a 100644
--- a/lib/rubygems/resolver/git_specification.rb
+++ b/lib/rubygems/resolver/git_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A GitSpecification represents a gem that is sourced from a git repository
# and is being loaded through a gem dependencies file through the +git:+
@@ -6,9 +7,9 @@
class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification
def ==(other) # :nodoc:
- self.class === other and
- @set == other.set and
- @spec == other.spec and
+ self.class === other &&
+ @set == other.set &&
+ @spec == other.spec &&
@source == other.source
end
@@ -21,7 +22,7 @@ class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification
# the executables.
def install(options = {})
- require 'rubygems/installer'
+ require_relative "../installer"
installer = Gem::Installer.for_spec spec, options
@@ -35,7 +36,7 @@ class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification
end
def pretty_print(q) # :nodoc:
- q.group 2, '[GitSpecification', ']' do
+ q.group 2, "[GitSpecification", "]" do
q.breakable
q.text "name: #{name}"
@@ -43,7 +44,7 @@ class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification
q.text "version: #{version}"
q.breakable
- q.text 'dependencies:'
+ q.text "dependencies:"
q.breakable
q.pp dependencies
diff --git a/lib/rubygems/resolver/index_set.rb b/lib/rubygems/resolver/index_set.rb
index 9390e34255..0b4f376452 100644
--- a/lib/rubygems/resolver/index_set.rb
+++ b/lib/rubygems/resolver/index_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The global rubygems pool represented via the traditional
# source index.
@@ -43,24 +44,24 @@ class Gem::Resolver::IndexSet < Gem::Resolver::Set
name = req.dependency.name
@all[name].each do |uri, n|
- if req.match? n, @prerelease
- res << Gem::Resolver::IndexSpecification.new(
- self, n.name, n.version, uri, n.platform)
- end
+ next unless req.match? n, @prerelease
+ res << Gem::Resolver::IndexSpecification.new(
+ self, n.name, n.version, uri, n.platform
+ )
end
res
end
def pretty_print(q) # :nodoc:
- q.group 2, '[IndexSet', ']' do
+ q.group 2, "[IndexSet", "]" do
q.breakable
- q.text 'sources:'
+ q.text "sources:"
q.breakable
q.pp @f.sources
q.breakable
- q.text 'specs:'
+ q.text "specs:"
q.breakable
diff --git a/lib/rubygems/resolver/index_specification.rb b/lib/rubygems/resolver/index_specification.rb
index 9ea76f40ba..7b95608071 100644
--- a/lib/rubygems/resolver/index_specification.rb
+++ b/lib/rubygems/resolver/index_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Represents a possible Specification object returned from IndexSet. Used to
# delay needed to download full Specification objects when only the +name+
@@ -21,7 +22,8 @@ class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification
@name = name
@version = version
@source = source
- @platform = platform.to_s
+ @platform = Gem::Platform.new(platform.to_s)
+ @original_platform = platform.to_s
@spec = nil
end
@@ -66,21 +68,21 @@ class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification
end
def inspect # :nodoc:
- '#<%s %s source %s>' % [self.class, full_name, @source]
+ format("#<%s %s source %s>", self.class, full_name, @source)
end
def pretty_print(q) # :nodoc:
- q.group 2, '[Index specification', ']' do
+ q.group 2, "[Index specification", "]" do
q.breakable
q.text full_name
- unless Gem::Platform::RUBY == @platform
+ unless @platform == Gem::Platform::RUBY
q.breakable
q.text @platform.to_s
end
q.breakable
- q.text 'source '
+ q.text "source "
q.pp @source
end
end
@@ -91,7 +93,7 @@ class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification
def spec # :nodoc:
@spec ||=
begin
- tuple = Gem::NameTuple.new @name, @version, @platform
+ tuple = Gem::NameTuple.new @name, @version, @original_platform
@source.fetch_spec tuple
end
diff --git a/lib/rubygems/resolver/installed_specification.rb b/lib/rubygems/resolver/installed_specification.rb
index 167ba1439e..8280ae4672 100644
--- a/lib/rubygems/resolver/installed_specification.rb
+++ b/lib/rubygems/resolver/installed_specification.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
+
##
# An InstalledSpecification represents a gem that is already installed
# locally.
class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification
def ==(other) # :nodoc:
- self.class === other and
- @set == other.set and
+ self.class === other &&
+ @set == other.set &&
@spec == other.spec
end
@@ -24,13 +25,13 @@ class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification
def installable_platform?
# BACKCOMPAT If the file is coming out of a specified file, then we
# ignore the platform. This code can be removed in RG 3.0.
- return true if @source.kind_of? Gem::Source::SpecificFile
+ return true if @source.is_a? Gem::Source::SpecificFile
super
end
def pretty_print(q) # :nodoc:
- q.group 2, '[InstalledSpecification', ']' do
+ q.group 2, "[InstalledSpecification", "]" do
q.breakable
q.text "name: #{name}"
@@ -41,7 +42,7 @@ class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification
q.text "platform: #{platform}"
q.breakable
- q.text 'dependencies:'
+ q.text "dependencies:"
q.breakable
q.pp spec.dependencies
end
diff --git a/lib/rubygems/resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb
index 60181315b0..d9fe36c589 100644
--- a/lib/rubygems/resolver/installer_set.rb
+++ b/lib/rubygems/resolver/installer_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A set of gems for installation sourced from remote sources and local .gem
# files
@@ -61,38 +62,37 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
found = find_all request
found.delete_if do |s|
- s.version.prerelease? and not s.local?
+ s.version.prerelease? && !s.local?
end unless dependency.prerelease?
found = found.select do |s|
- Gem::Source::SpecificFile === s.source or
- Gem::Platform::RUBY == s.platform or
- Gem::Platform.local === s.platform
+ Gem::Source::SpecificFile === s.source ||
+ Gem::Platform.match_spec?(s)
end
found = found.sort_by do |s|
- [s.version, s.platform == Gem::Platform::RUBY ? -1 : 1]
+ [s.version, Gem::Platform.sort_priority(s.platform)]
end
newest = found.last
+ unless newest
+ exc = Gem::UnsatisfiableDependencyError.new request
+ exc.errors = errors
+
+ raise exc
+ end
+
unless @force
- found_matching_metadata = found.select do |spec|
+ found_matching_metadata = found.reverse.find do |spec|
metadata_satisfied?(spec)
end
- if found_matching_metadata.empty?
- if newest
- ensure_required_ruby_version_met(newest.spec)
- ensure_required_rubygems_version_met(newest.spec)
- else
- exc = Gem::UnsatisfiableDependencyError.new request
- exc.errors = errors
-
- raise exc
- end
+ if found_matching_metadata.nil?
+ ensure_required_ruby_version_met(newest.spec)
+ ensure_required_rubygems_version_met(newest.spec)
else
- newest = found_matching_metadata.last
+ newest = found_matching_metadata
end
end
@@ -111,14 +111,14 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
# Should local gems should be considered?
def consider_local? # :nodoc:
- @domain == :both or @domain == :local
+ @domain == :both || @domain == :local
end
##
# Should remote gems should be considered?
def consider_remote? # :nodoc:
- @domain == :both or @domain == :remote
+ @domain == :both || @domain == :remote
end
##
@@ -137,8 +137,8 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
dep = req.dependency
- return res if @ignore_dependencies and
- @always_install.none? {|spec| dep.match? spec }
+ return res if @ignore_dependencies &&
+ @always_install.none? {|spec| dep.match? spec }
name = dep.name
@@ -148,6 +148,8 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
res << Gem::Resolver::InstalledSpecification.new(self, gemspec)
end unless @ignore_installed
+ matching_local = []
+
if consider_local?
matching_local = @local.values.select do |spec, _|
req.match? spec
@@ -161,18 +163,15 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
if local_spec = @local_source.find_gem(name, dep.requirement)
res << Gem::Resolver::IndexSpecification.new(
self, local_spec.name, local_spec.version,
- @local_source, local_spec.platform)
+ @local_source, local_spec.platform
+ )
end
rescue Gem::Package::FormatError
# ignore
end
end
- res.delete_if do |spec|
- spec.version.prerelease? and not dep.prerelease?
- end
-
- res.concat @remote_set.find_all req if consider_remote?
+ res.concat @remote_set.find_all req if consider_remote? && matching_local.empty?
res
end
@@ -188,11 +187,9 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
end
def inspect # :nodoc:
- always_install = @always_install.map {|s| s.full_name }
+ always_install = @always_install.map(&:full_name)
- '#<%s domain: %s specs: %p always install: %p>' % [
- self.class, @domain, @specs.keys, always_install
- ]
+ format("#<%s domain: %s specs: %p always install: %p>", self.class, @domain, @specs.keys, always_install)
end
##
@@ -219,16 +216,16 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
end
def pretty_print(q) # :nodoc:
- q.group 2, '[InstallerSet', ']' do
+ q.group 2, "[InstallerSet", "]" do
q.breakable
q.text "domain: #{@domain}"
q.breakable
- q.text 'specs: '
+ q.text "specs: "
q.pp @specs.keys
q.breakable
- q.text 'always install: '
+ q.text "always install: "
q.pp @always_install
end
end
@@ -266,7 +263,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
unless rrgv.satisfied_by? Gem.rubygems_version
rg_version = Gem::VERSION
raise Gem::RuntimeRequirementNotMetError,
- "#{spec.full_name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " +
+ "#{spec.full_name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " \
"Try 'gem update --system' to update RubyGems itself."
end
end
diff --git a/lib/rubygems/resolver/local_specification.rb b/lib/rubygems/resolver/local_specification.rb
index 9c69c4ab74..b57d40e795 100644
--- a/lib/rubygems/resolver/local_specification.rb
+++ b/lib/rubygems/resolver/local_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A LocalSpecification comes from a .gem file on the local filesystem.
@@ -7,7 +8,7 @@ class Gem::Resolver::LocalSpecification < Gem::Resolver::SpecSpecification
# Returns +true+ if this gem is installable for the current platform.
def installable_platform?
- return true if @source.kind_of? Gem::Source::SpecificFile
+ return true if @source.is_a? Gem::Source::SpecificFile
super
end
@@ -17,7 +18,7 @@ class Gem::Resolver::LocalSpecification < Gem::Resolver::SpecSpecification
end
def pretty_print(q) # :nodoc:
- q.group 2, '[LocalSpecification', ']' do
+ q.group 2, "[LocalSpecification", "]" do
q.breakable
q.text "name: #{name}"
@@ -28,7 +29,7 @@ class Gem::Resolver::LocalSpecification < Gem::Resolver::SpecSpecification
q.text "platform: #{platform}"
q.breakable
- q.text 'dependencies:'
+ q.text "dependencies:"
q.breakable
q.pp dependencies
diff --git a/lib/rubygems/resolver/lock_set.rb b/lib/rubygems/resolver/lock_set.rb
index eabf217aba..e5ee32a9a6 100644
--- a/lib/rubygems/resolver/lock_set.rb
+++ b/lib/rubygems/resolver/lock_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A set of gems from a gem dependencies lockfile.
@@ -54,7 +55,7 @@ class Gem::Resolver::LockSet < Gem::Resolver::Set
dep = Gem::Dependency.new name, version
found = @specs.find do |spec|
- dep.matches_spec? spec and spec.platform == platform
+ dep.matches_spec?(spec) && spec.platform == platform
end
tuple = Gem::NameTuple.new found.name, found.version, found.platform
@@ -63,18 +64,18 @@ class Gem::Resolver::LockSet < Gem::Resolver::Set
end
def pretty_print(q) # :nodoc:
- q.group 2, '[LockSet', ']' do
+ q.group 2, "[LockSet", "]" do
q.breakable
- q.text 'source:'
+ q.text "source:"
q.breakable
q.pp @source
q.breakable
- q.text 'specs:'
+ q.text "specs:"
q.breakable
- q.pp @specs.map {|spec| spec.full_name }
+ q.pp @specs.map(&:full_name)
end
end
end
diff --git a/lib/rubygems/resolver/lock_specification.rb b/lib/rubygems/resolver/lock_specification.rb
index cdb8e4e425..06f912dd85 100644
--- a/lib/rubygems/resolver/lock_specification.rb
+++ b/lib/rubygems/resolver/lock_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The LockSpecification comes from a lockfile (Gem::RequestSet::Lockfile).
#
@@ -29,7 +30,7 @@ class Gem::Resolver::LockSpecification < Gem::Resolver::Specification
def install(options = {})
destination = options[:install_dir] || Gem.dir
- if File.exist? File.join(destination, 'specifications', spec.spec_name)
+ if File.exist? File.join(destination, "specifications", spec.spec_name)
yield nil
return
end
@@ -45,7 +46,7 @@ class Gem::Resolver::LockSpecification < Gem::Resolver::Specification
end
def pretty_print(q) # :nodoc:
- q.group 2, '[LockSpecification', ']' do
+ q.group 2, "[LockSpecification", "]" do
q.breakable
q.text "name: #{@name}"
@@ -59,7 +60,7 @@ class Gem::Resolver::LockSpecification < Gem::Resolver::Specification
unless @dependencies.empty?
q.breakable
- q.text 'dependencies:'
+ q.text "dependencies:"
q.breakable
q.pp @dependencies
end
@@ -71,7 +72,7 @@ class Gem::Resolver::LockSpecification < Gem::Resolver::Specification
def spec
@spec ||= Gem::Specification.find do |spec|
- spec.name == @name and spec.version == @version
+ spec.name == @name && spec.version == @version
end
@spec ||= Gem::Specification.new do |s|
diff --git a/lib/rubygems/resolver/molinillo.rb b/lib/rubygems/resolver/molinillo.rb
deleted file mode 100644
index 2357f41bee..0000000000
--- a/lib/rubygems/resolver/molinillo.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-# frozen_string_literal: true
-require 'rubygems/resolver/molinillo/lib/molinillo'
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb
deleted file mode 100644
index d540d3baff..0000000000
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-module Gem::Resolver::Molinillo
- # @!visibility private
- module Delegates
- # Delegates all {Gem::Resolver::Molinillo::ResolutionState} methods to a `#state` property.
- module ResolutionState
- # (see Gem::Resolver::Molinillo::ResolutionState#name)
- def name
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.name
- end
-
- # (see Gem::Resolver::Molinillo::ResolutionState#requirements)
- def requirements
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.requirements
- end
-
- # (see Gem::Resolver::Molinillo::ResolutionState#activated)
- def activated
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.activated
- end
-
- # (see Gem::Resolver::Molinillo::ResolutionState#requirement)
- def requirement
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.requirement
- end
-
- # (see Gem::Resolver::Molinillo::ResolutionState#possibilities)
- def possibilities
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.possibilities
- end
-
- # (see Gem::Resolver::Molinillo::ResolutionState#depth)
- def depth
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.depth
- end
-
- # (see Gem::Resolver::Molinillo::ResolutionState#conflicts)
- def conflicts
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.conflicts
- end
-
- # (see Gem::Resolver::Molinillo::ResolutionState#unused_unwind_options)
- def unused_unwind_options
- current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty
- current_state.unused_unwind_options
- end
- end
- end
-end
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb
deleted file mode 100644
index 6b5ada7ade..0000000000
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-module Gem::Resolver::Molinillo
- # The version of Gem::Resolver::Molinillo.
- VERSION = '0.7.0'.freeze
-end
diff --git a/lib/rubygems/resolver/requirement_list.rb b/lib/rubygems/resolver/requirement_list.rb
index 5b51493c9a..6f86f0f412 100644
--- a/lib/rubygems/resolver/requirement_list.rb
+++ b/lib/rubygems/resolver/requirement_list.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The RequirementList is used to hold the requirements being considered
# while resolving a set of gems.
diff --git a/lib/rubygems/resolver/set.rb b/lib/rubygems/resolver/set.rb
index 8046e18ea1..243fee5fd5 100644
--- a/lib/rubygems/resolver/set.rb
+++ b/lib/rubygems/resolver/set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Resolver sets are used to look up specifications (and their
# dependencies) used in resolution. This set is abstract.
@@ -20,7 +21,6 @@ class Gem::Resolver::Set
attr_accessor :prerelease
def initialize # :nodoc:
- require 'uri'
@prerelease = false
@remote = true
@errors = []
diff --git a/lib/rubygems/resolver/source_set.rb b/lib/rubygems/resolver/source_set.rb
index bf8c23184e..296cf41078 100644
--- a/lib/rubygems/resolver/source_set.rb
+++ b/lib/rubygems/resolver/source_set.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# The SourceSet chooses the best available method to query a remote index.
#
diff --git a/lib/rubygems/resolver/spec_specification.rb b/lib/rubygems/resolver/spec_specification.rb
index 7b665fe876..00ef9fdba0 100644
--- a/lib/rubygems/resolver/spec_specification.rb
+++ b/lib/rubygems/resolver/spec_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The Resolver::SpecSpecification contains common functionality for
# Resolver specifications that are backed by a Gem::Specification.
@@ -65,4 +66,11 @@ class Gem::Resolver::SpecSpecification < Gem::Resolver::Specification
def version
spec.version
end
+
+ ##
+ # The hash value for this specification.
+
+ def hash
+ spec.hash
+ end
end
diff --git a/lib/rubygems/resolver/specification.rb b/lib/rubygems/resolver/specification.rb
index 8c6fc9afcf..d2098ef0e2 100644
--- a/lib/rubygems/resolver/specification.rb
+++ b/lib/rubygems/resolver/specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A Resolver::Specification contains a subset of the information
# contained in a Gem::Specification. Only the information necessary for
@@ -93,7 +94,7 @@ class Gem::Resolver::Specification
# specification.
def install(options = {})
- require 'rubygems/installer'
+ require_relative "../installer"
gem = download options
diff --git a/lib/rubygems/resolver/stats.rb b/lib/rubygems/resolver/stats.rb
index 64b458f504..9920976b2a 100644
--- a/lib/rubygems/resolver/stats.rb
+++ b/lib/rubygems/resolver/stats.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
class Gem::Resolver::Stats
def initialize
@max_depth = 0
@@ -32,7 +33,7 @@ class Gem::Resolver::Stats
@iterations += 1
end
- PATTERN = "%20s: %d\n".freeze
+ PATTERN = "%20s: %d\n"
def display
$stdout.puts "=== Resolver Statistics ==="
diff --git a/lib/rubygems/resolver/vendor_set.rb b/lib/rubygems/resolver/vendor_set.rb
index 48c640d8c9..293a1e3331 100644
--- a/lib/rubygems/resolver/vendor_set.rb
+++ b/lib/rubygems/resolver/vendor_set.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A VendorSet represents gems that have been unpacked into a specific
# directory that contains a gemspec.
@@ -69,7 +70,7 @@ class Gem::Resolver::VendorSet < Gem::Resolver::Set
end
def pretty_print(q) # :nodoc:
- q.group 2, '[VendorSet', ']' do
+ q.group 2, "[VendorSet", "]" do
next if @directories.empty?
q.breakable
diff --git a/lib/rubygems/resolver/vendor_specification.rb b/lib/rubygems/resolver/vendor_specification.rb
index 8dfe5940f2..ac78f54558 100644
--- a/lib/rubygems/resolver/vendor_specification.rb
+++ b/lib/rubygems/resolver/vendor_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A VendorSpecification represents a gem that has been unpacked into a project
# and is being loaded through a gem dependencies file through the +path:+
@@ -6,9 +7,9 @@
class Gem::Resolver::VendorSpecification < Gem::Resolver::SpecSpecification
def ==(other) # :nodoc:
- self.class === other and
- @set == other.set and
- @spec == other.spec and
+ self.class === other &&
+ @set == other.set &&
+ @spec == other.spec &&
@source == other.source
end
diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb
index f1f9229ca5..7c95a9d4f5 100644
--- a/lib/rubygems/s3_uri_signer.rb
+++ b/lib/rubygems/s3_uri_signer.rb
@@ -1,6 +1,6 @@
-require 'base64'
-require 'digest'
-require 'rubygems/openssl'
+# frozen_string_literal: true
+
+require_relative "openssl"
##
# S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems
@@ -12,7 +12,7 @@ class Gem::S3URISigner
end
def to_s # :nodoc:
- "#{super}"
+ super.to_s
end
end
@@ -22,7 +22,7 @@ class Gem::S3URISigner
end
def to_s # :nodoc:
- "#{super}"
+ super.to_s
end
end
@@ -34,7 +34,7 @@ class Gem::S3URISigner
##
# Signs S3 URI using query-params according to the reference: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
- def sign(expiration = 86400)
+ def sign(expiration = 86_400)
s3_config = fetch_s3_config
current_time = Time.now.utc
@@ -49,7 +49,7 @@ class Gem::S3URISigner
string_to_sign = generate_string_to_sign(date_time, credential_info, canonical_request)
signature = generate_signature(s3_config, date, string_to_sign)
- URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}")
+ Gem::URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}")
end
private
@@ -88,7 +88,7 @@ class Gem::S3URISigner
"AWS4-HMAC-SHA256",
date_time,
credential_info,
- Digest::SHA256.hexdigest(canonical_request),
+ OpenSSL::Digest::SHA256.hexdigest(canonical_request),
].join("\n")
end
@@ -136,29 +136,29 @@ class Gem::S3URISigner
end
def base64_uri_escape(str)
- str.gsub(/[\+\/=\n]/, BASE64_URI_TRANSLATE)
+ str.gsub(%r{[\+/=\n]}, BASE64_URI_TRANSLATE)
end
def ec2_metadata_credentials_json
- require 'net/http'
- require 'rubygems/request'
- require 'rubygems/request/connection_pools'
- require 'json'
+ require_relative "vendored_net_http"
+ require_relative "request"
+ require_relative "request/connection_pools"
+ require "json"
iam_info = ec2_metadata_request(EC2_IAM_INFO)
# Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
- role_name = iam_info['InstanceProfileArn'].split('/').last
+ role_name = iam_info["InstanceProfileArn"].split("/").last
ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name)
end
def ec2_metadata_request(url)
- uri = URI(url)
+ uri = Gem::URI(url)
@request_pool ||= create_request_pool(uri)
- request = Gem::Request.new(uri, Net::HTTP::Get, nil, @request_pool)
+ request = Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, @request_pool)
response = request.fetch
case response
- when Net::HTTPOK then
+ when Gem::Net::HTTPOK then
JSON.parse(response.body)
else
raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}")
@@ -172,6 +172,6 @@ class Gem::S3URISigner
end
BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze
- EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info".freeze
- EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/".freeze
+ EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info"
+ EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
end
diff --git a/lib/rubygems/safe_marshal.rb b/lib/rubygems/safe_marshal.rb
new file mode 100644
index 0000000000..b81d1a0a47
--- /dev/null
+++ b/lib/rubygems/safe_marshal.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require "stringio"
+
+require_relative "safe_marshal/reader"
+require_relative "safe_marshal/visitors/to_ruby"
+
+module Gem
+ ###
+ # This module is used for safely loading Marshal specs from a gem. The
+ # `safe_load` method defined on this module is specifically designed for
+ # loading Gem specifications.
+
+ module SafeMarshal
+ PERMITTED_CLASSES = %w[
+ Date
+ Time
+ Rational
+
+ Gem::Dependency
+ Gem::NameTuple
+ Gem::Platform
+ Gem::Requirement
+ Gem::Specification
+ Gem::Version
+ Gem::Version::Requirement
+
+ YAML::Syck::DefaultKey
+ YAML::PrivateType
+ ].freeze
+ private_constant :PERMITTED_CLASSES
+
+ PERMITTED_SYMBOLS = %w[
+ development
+ runtime
+
+ name
+ number
+ platform
+ dependencies
+ ].freeze
+ private_constant :PERMITTED_SYMBOLS
+
+ PERMITTED_IVARS = {
+ "String" => %w[E encoding @taguri @debug_created_info],
+ "Time" => %w[
+ offset zone nano_num nano_den submicro
+ @_zone @marshal_with_utc_coercion
+ ],
+ "Gem::Dependency" => %w[
+ @name @requirement @prerelease @version_requirement @version_requirements @type
+ @force_ruby_platform
+ ],
+ "Gem::NameTuple" => %w[@name @version @platform],
+ "Gem::Platform" => %w[@os @cpu @version],
+ "Psych::PrivateType" => %w[@value @type_id],
+ }.freeze
+ private_constant :PERMITTED_IVARS
+
+ def self.safe_load(input)
+ load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, permitted_ivars: PERMITTED_IVARS)
+ end
+
+ def self.load(input, permitted_classes: [::Symbol], permitted_symbols: [], permitted_ivars: {})
+ root = Reader.new(StringIO.new(input, "r").binmode).read!
+
+ Visitors::ToRuby.new(
+ permitted_classes: permitted_classes,
+ permitted_symbols: permitted_symbols,
+ permitted_ivars: permitted_ivars,
+ ).visit(root)
+ end
+ end
+end
diff --git a/lib/rubygems/safe_marshal/elements.rb b/lib/rubygems/safe_marshal/elements.rb
new file mode 100644
index 0000000000..f8874b1b2f
--- /dev/null
+++ b/lib/rubygems/safe_marshal/elements.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+module Gem
+ module SafeMarshal
+ module Elements
+ class Element
+ end
+
+ class Symbol < Element
+ def initialize(name)
+ @name = name
+ end
+ attr_reader :name
+ end
+
+ class UserDefined < Element
+ def initialize(name, binary_string)
+ @name = name
+ @binary_string = binary_string
+ end
+
+ attr_reader :name, :binary_string
+ end
+
+ class UserMarshal < Element
+ def initialize(name, data)
+ @name = name
+ @data = data
+ end
+
+ attr_reader :name, :data
+ end
+
+ class String < Element
+ def initialize(str)
+ @str = str
+ end
+
+ attr_reader :str
+ end
+
+ class Hash < Element
+ def initialize(pairs)
+ @pairs = pairs
+ end
+
+ attr_reader :pairs
+ end
+
+ class HashWithDefaultValue < Hash
+ def initialize(pairs, default)
+ super(pairs)
+ @default = default
+ end
+
+ attr_reader :default
+ end
+
+ class Array < Element
+ def initialize(elements)
+ @elements = elements
+ end
+
+ attr_reader :elements
+ end
+
+ class Integer < Element
+ def initialize(int)
+ @int = int
+ end
+
+ attr_reader :int
+ end
+
+ class True < Element
+ def initialize
+ end
+ TRUE = new.freeze
+ end
+
+ class False < Element
+ def initialize
+ end
+
+ FALSE = new.freeze
+ end
+
+ class WithIvars < Element
+ def initialize(object, ivars)
+ @object = object
+ @ivars = ivars
+ end
+
+ attr_reader :object, :ivars
+ end
+
+ class Object < Element
+ def initialize(name)
+ @name = name
+ end
+ attr_reader :name
+ end
+
+ class Nil < Element
+ NIL = new.freeze
+ end
+
+ class ObjectLink < Element
+ def initialize(offset)
+ @offset = offset
+ end
+ attr_reader :offset
+ end
+
+ class SymbolLink < Element
+ def initialize(offset)
+ @offset = offset
+ end
+ attr_reader :offset
+ end
+
+ class Float < Element
+ def initialize(string)
+ @string = string
+ end
+ attr_reader :string
+ end
+
+ class Bignum < Element # rubocop:disable Lint/UnifiedInteger
+ def initialize(sign, data)
+ @sign = sign
+ @data = data
+ end
+ attr_reader :sign, :data
+ end
+
+ class UserClass < Element
+ def initialize(name, wrapped_object)
+ @name = name
+ @wrapped_object = wrapped_object
+ end
+ attr_reader :name, :wrapped_object
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/safe_marshal/reader.rb b/lib/rubygems/safe_marshal/reader.rb
new file mode 100644
index 0000000000..740be113e5
--- /dev/null
+++ b/lib/rubygems/safe_marshal/reader.rb
@@ -0,0 +1,308 @@
+# frozen_string_literal: true
+
+require_relative "elements"
+
+module Gem
+ module SafeMarshal
+ class Reader
+ class Error < StandardError
+ end
+
+ class UnsupportedVersionError < Error
+ end
+
+ class UnconsumedBytesError < Error
+ end
+
+ class NotImplementedError < Error
+ end
+
+ class EOFError < Error
+ end
+
+ def initialize(io)
+ @io = io
+ end
+
+ def read!
+ read_header
+ root = read_element
+ raise UnconsumedBytesError unless @io.eof?
+ root
+ end
+
+ private
+
+ MARSHAL_VERSION = [Marshal::MAJOR_VERSION, Marshal::MINOR_VERSION].map(&:chr).join.freeze
+ private_constant :MARSHAL_VERSION
+
+ def read_header
+ v = @io.read(2)
+ raise UnsupportedVersionError, "Unsupported marshal version #{v.bytes.map(&:ord).join(".")}, expected #{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" unless v == MARSHAL_VERSION
+ end
+
+ def read_byte
+ @io.getbyte
+ end
+
+ def read_integer
+ b = read_byte
+
+ case b
+ when 0x00
+ 0
+ when 0x01
+ read_byte
+ when 0x02
+ read_byte | (read_byte << 8)
+ when 0x03
+ read_byte | (read_byte << 8) | (read_byte << 16)
+ when 0x04
+ read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24)
+ when 0xFC
+ read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24) | -0x100000000
+ when 0xFD
+ read_byte | (read_byte << 8) | (read_byte << 16) | -0x1000000
+ when 0xFE
+ read_byte | (read_byte << 8) | -0x10000
+ when 0xFF
+ read_byte | -0x100
+ when nil
+ raise EOFError, "Unexpected EOF"
+ else
+ signed = (b ^ 128) - 128
+ if b >= 128
+ signed + 5
+ else
+ signed - 5
+ end
+ end
+ end
+
+ def read_element
+ type = read_byte
+ case type
+ when 34 then read_string # ?"
+ when 48 then read_nil # ?0
+ when 58 then read_symbol # ?:
+ when 59 then read_symbol_link # ?;
+ when 64 then read_object_link # ?@
+ when 70 then read_false # ?F
+ when 73 then read_object_with_ivars # ?I
+ when 84 then read_true # ?T
+ when 85 then read_user_marshal # ?U
+ when 91 then read_array # ?[
+ when 102 then read_float # ?f
+ when 105 then Elements::Integer.new(read_integer) # ?i
+ when 108 then read_bignum # ?l
+ when 111 then read_object # ?o
+ when 117 then read_user_defined # ?u
+ when 123 then read_hash # ?{
+ when 125 then read_hash_with_default_value # ?}
+ when 101 then read_extended_object # ?e
+ when 99 then read_class # ?c
+ when 109 then read_module # ?m
+ when 77 then read_class_or_module # ?M
+ when 100 then read_data # ?d
+ when 47 then read_regexp # ?/
+ when 83 then read_struct # ?S
+ when 67 then read_user_class # ?C
+ when nil
+ raise EOFError, "Unexpected EOF"
+ else
+ raise Error, "Unknown marshal type discriminator #{type.chr.inspect} (#{type})"
+ end
+ end
+
+ STRING_E_SYMBOL = Elements::Symbol.new("E").freeze
+ private_constant :STRING_E_SYMBOL
+
+ def read_symbol
+ len = read_integer
+ if len == 1
+ byte = read_byte
+ if byte == 69 # ?E
+ STRING_E_SYMBOL
+ else
+ Elements::Symbol.new(byte.chr)
+ end
+ else
+ name = -@io.read(len)
+ Elements::Symbol.new(name)
+ end
+ end
+
+ EMPTY_STRING = Elements::String.new("".b.freeze).freeze
+ private_constant :EMPTY_STRING
+
+ def read_string
+ length = read_integer
+ return EMPTY_STRING if length == 0
+ str = @io.read(length)
+ Elements::String.new(str)
+ end
+
+ def read_true
+ Elements::True::TRUE
+ end
+
+ def read_false
+ Elements::False::FALSE
+ end
+
+ def read_user_defined
+ name = read_element
+ binary_string = @io.read(read_integer)
+ Elements::UserDefined.new(name, binary_string)
+ end
+
+ EMPTY_ARRAY = Elements::Array.new([].freeze).freeze
+ private_constant :EMPTY_ARRAY
+
+ def read_array
+ length = read_integer
+ return EMPTY_ARRAY if length == 0
+ elements = Array.new(length) do
+ read_element
+ end
+ Elements::Array.new(elements)
+ end
+
+ def read_object_with_ivars
+ object = read_element
+ ivars = Array.new(read_integer) do
+ [read_element, read_element]
+ end
+ Elements::WithIvars.new(object, ivars)
+ end
+
+ def read_symbol_link
+ offset = read_integer
+ Elements::SymbolLink.new(offset)
+ end
+
+ def read_user_marshal
+ name = read_element
+ data = read_element
+ Elements::UserMarshal.new(name, data)
+ end
+
+ # profiling bundle install --full-index shows that
+ # offset 6 is by far the most common object link,
+ # so we special case it to avoid allocating a new
+ # object a third of the time.
+ # the following are all the object links that
+ # appear more than 10000 times in my profiling
+
+ OBJECT_LINKS = {
+ 6 => Elements::ObjectLink.new(6).freeze,
+ 30 => Elements::ObjectLink.new(30).freeze,
+ 81 => Elements::ObjectLink.new(81).freeze,
+ 34 => Elements::ObjectLink.new(34).freeze,
+ 38 => Elements::ObjectLink.new(38).freeze,
+ 50 => Elements::ObjectLink.new(50).freeze,
+ 91 => Elements::ObjectLink.new(91).freeze,
+ 42 => Elements::ObjectLink.new(42).freeze,
+ 46 => Elements::ObjectLink.new(46).freeze,
+ 150 => Elements::ObjectLink.new(150).freeze,
+ 100 => Elements::ObjectLink.new(100).freeze,
+ 104 => Elements::ObjectLink.new(104).freeze,
+ 108 => Elements::ObjectLink.new(108).freeze,
+ 242 => Elements::ObjectLink.new(242).freeze,
+ 246 => Elements::ObjectLink.new(246).freeze,
+ 139 => Elements::ObjectLink.new(139).freeze,
+ 143 => Elements::ObjectLink.new(143).freeze,
+ 114 => Elements::ObjectLink.new(114).freeze,
+ 308 => Elements::ObjectLink.new(308).freeze,
+ 200 => Elements::ObjectLink.new(200).freeze,
+ 54 => Elements::ObjectLink.new(54).freeze,
+ 62 => Elements::ObjectLink.new(62).freeze,
+ 1_286_245 => Elements::ObjectLink.new(1_286_245).freeze,
+ }.freeze
+ private_constant :OBJECT_LINKS
+
+ def read_object_link
+ offset = read_integer
+ OBJECT_LINKS[offset] || Elements::ObjectLink.new(offset)
+ end
+
+ EMPTY_HASH = Elements::Hash.new([].freeze).freeze
+ private_constant :EMPTY_HASH
+
+ def read_hash
+ length = read_integer
+ return EMPTY_HASH if length == 0
+ pairs = Array.new(length) do
+ [read_element, read_element]
+ end
+ Elements::Hash.new(pairs)
+ end
+
+ def read_hash_with_default_value
+ pairs = Array.new(read_integer) do
+ [read_element, read_element]
+ end
+ default = read_element
+ Elements::HashWithDefaultValue.new(pairs, default)
+ end
+
+ def read_object
+ name = read_element
+ object = Elements::Object.new(name)
+ ivars = Array.new(read_integer) do
+ [read_element, read_element]
+ end
+ Elements::WithIvars.new(object, ivars)
+ end
+
+ def read_nil
+ Elements::Nil::NIL
+ end
+
+ def read_float
+ string = @io.read(read_integer)
+ Elements::Float.new(string)
+ end
+
+ def read_bignum
+ sign = read_byte
+ data = @io.read(read_integer * 2)
+ Elements::Bignum.new(sign, data)
+ end
+
+ def read_extended_object
+ raise NotImplementedError, "Reading Marshal objects of type extended_object is not implemented"
+ end
+
+ def read_class
+ raise NotImplementedError, "Reading Marshal objects of type class is not implemented"
+ end
+
+ def read_module
+ raise NotImplementedError, "Reading Marshal objects of type module is not implemented"
+ end
+
+ def read_class_or_module
+ raise NotImplementedError, "Reading Marshal objects of type class_or_module is not implemented"
+ end
+
+ def read_data
+ raise NotImplementedError, "Reading Marshal objects of type data is not implemented"
+ end
+
+ def read_regexp
+ raise NotImplementedError, "Reading Marshal objects of type regexp is not implemented"
+ end
+
+ def read_struct
+ raise NotImplementedError, "Reading Marshal objects of type struct is not implemented"
+ end
+
+ def read_user_class
+ name = read_element
+ wrapped_object = read_element
+ Elements::UserClass.new(name, wrapped_object)
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/safe_marshal/visitors/stream_printer.rb b/lib/rubygems/safe_marshal/visitors/stream_printer.rb
new file mode 100644
index 0000000000..162b36ad05
--- /dev/null
+++ b/lib/rubygems/safe_marshal/visitors/stream_printer.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require_relative "visitor"
+
+module Gem::SafeMarshal
+ module Visitors
+ class StreamPrinter < Visitor
+ def initialize(io, indent: "")
+ @io = io
+ @indent = indent
+ @level = 0
+ end
+
+ def visit(target)
+ @io.write("#{@indent * @level}#{target.class}")
+ target.instance_variables.each do |ivar|
+ value = target.instance_variable_get(ivar)
+ next if Elements::Element === value || Array === value
+ @io.write(" #{ivar}=#{value.inspect}")
+ end
+ @io.write("\n")
+ begin
+ @level += 1
+ super
+ ensure
+ @level -= 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/safe_marshal/visitors/to_ruby.rb b/lib/rubygems/safe_marshal/visitors/to_ruby.rb
new file mode 100644
index 0000000000..a9f1d048d4
--- /dev/null
+++ b/lib/rubygems/safe_marshal/visitors/to_ruby.rb
@@ -0,0 +1,415 @@
+# frozen_string_literal: true
+
+require_relative "visitor"
+
+module Gem::SafeMarshal
+ module Visitors
+ class ToRuby < Visitor
+ def initialize(permitted_classes:, permitted_symbols:, permitted_ivars:)
+ @permitted_classes = permitted_classes
+ @permitted_symbols = ["E"].concat(permitted_symbols).concat(permitted_classes)
+ @permitted_ivars = permitted_ivars
+
+ @objects = []
+ @symbols = []
+ @class_cache = {}
+
+ @stack = ["root"]
+ @stack_idx = 1
+ end
+
+ def inspect # :nodoc:
+ format("#<%s permitted_classes: %p permitted_symbols: %p permitted_ivars: %p>",
+ self.class, @permitted_classes, @permitted_symbols, @permitted_ivars)
+ end
+
+ def visit(target)
+ stack_idx = @stack_idx
+ super
+ ensure
+ @stack_idx = stack_idx - 1
+ end
+
+ private
+
+ def push_stack(element)
+ @stack[@stack_idx] = element
+ @stack_idx += 1
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Array(a)
+ array = register_object([])
+
+ elements = a.elements
+ size = elements.size
+ idx = 0
+ # not idiomatic, but there's a huge number of IMEMOs allocated here, so we avoid the block
+ # because this is such a hot path when doing a bundle install with the full index
+ until idx == size
+ push_stack idx
+ array << visit(elements[idx])
+ idx += 1
+ end
+
+ array
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Symbol(s)
+ name = s.name
+ raise UnpermittedSymbolError.new(symbol: name, stack: formatted_stack) unless @permitted_symbols.include?(name)
+ visit_symbol_type(s)
+ end
+
+ def map_ivars(klass, ivars)
+ stack_idx = @stack_idx
+ ivars.map.with_index do |(k, v), i|
+ @stack_idx = stack_idx
+
+ push_stack "ivar_"
+ push_stack i
+ k = resolve_ivar(klass, k)
+
+ @stack_idx = stack_idx
+ push_stack k
+
+ next k, visit(v)
+ end
+ end
+
+ def visit_Gem_SafeMarshal_Elements_WithIvars(e)
+ object_offset = @objects.size
+ push_stack "object"
+ object = visit(e.object)
+ ivars = map_ivars(object.class, e.ivars)
+
+ case e.object
+ when Elements::UserDefined
+ if object.class == ::Time
+ internal = []
+
+ ivars.reject! do |k, v|
+ case k
+ when :offset, :zone, :nano_num, :nano_den, :submicro
+ internal << [k, v]
+ true
+ else
+ false
+ end
+ end
+
+ s = e.object.binary_string
+
+ marshal_string = "\x04\bIu:\tTime".b
+ marshal_string.concat(s.size + 5)
+ marshal_string << s
+ marshal_string.concat(internal.size + 5)
+
+ internal.each do |k, v|
+ marshal_string.concat(":")
+ marshal_string.concat(k.size + 5)
+ marshal_string.concat(k.to_s)
+ dumped = Marshal.dump(v)
+ dumped[0, 2] = ""
+ marshal_string.concat(dumped)
+ end
+
+ object = @objects[object_offset] = Marshal.load(marshal_string)
+ end
+ when Elements::String
+ enc = nil
+
+ ivars.reject! do |k, v|
+ case k
+ when :E
+ case v
+ when TrueClass
+ enc = "UTF-8"
+ when FalseClass
+ enc = "US-ASCII"
+ else
+ raise FormatError, "Unexpected value for String :E #{v.inspect}"
+ end
+ when :encoding
+ enc = v
+ else
+ next false
+ end
+ true
+ end
+
+ object.force_encoding(enc) if enc
+ end
+
+ ivars.each do |k, v|
+ object.instance_variable_set k, v
+ end
+ object
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Hash(o)
+ hash = register_object({})
+
+ o.pairs.each_with_index do |(k, v), i|
+ push_stack i
+ k = visit(k)
+ push_stack k
+ hash[k] = visit(v)
+ end
+
+ hash
+ end
+
+ def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(o)
+ hash = visit_Gem_SafeMarshal_Elements_Hash(o)
+ push_stack :default
+ hash.default = visit(o.default)
+ hash
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Object(o)
+ register_object(resolve_class(o.name).allocate)
+ end
+
+ def visit_Gem_SafeMarshal_Elements_ObjectLink(o)
+ @objects[o.offset]
+ end
+
+ def visit_Gem_SafeMarshal_Elements_SymbolLink(o)
+ @symbols[o.offset]
+ end
+
+ def visit_Gem_SafeMarshal_Elements_UserDefined(o)
+ register_object(call_method(resolve_class(o.name), :_load, o.binary_string))
+ end
+
+ def visit_Gem_SafeMarshal_Elements_UserMarshal(o)
+ klass = resolve_class(o.name)
+ compat = COMPAT_CLASSES.fetch(klass, nil)
+ idx = @objects.size
+ object = register_object(call_method(compat || klass, :allocate))
+
+ push_stack :data
+ ret = call_method(object, :marshal_load, visit(o.data))
+
+ if compat
+ object = @objects[idx] = ret
+ end
+
+ object
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Integer(i)
+ i.int
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Nil(_)
+ nil
+ end
+
+ def visit_Gem_SafeMarshal_Elements_True(_)
+ true
+ end
+
+ def visit_Gem_SafeMarshal_Elements_False(_)
+ false
+ end
+
+ def visit_Gem_SafeMarshal_Elements_String(s)
+ register_object(+s.str)
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Float(f)
+ case f.string
+ when "inf"
+ ::Float::INFINITY
+ when "-inf"
+ -::Float::INFINITY
+ when "nan"
+ ::Float::NAN
+ else
+ f.string.to_f
+ end
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Bignum(b)
+ result = 0
+ b.data.each_byte.with_index do |byte, exp|
+ result += (byte * 2**(exp * 8))
+ end
+
+ case b.sign
+ when 43 # ?+
+ result
+ when 45 # ?-
+ -result
+ else
+ raise FormatError, "Unexpected sign for Bignum #{b.sign.chr.inspect} (#{b.sign})"
+ end
+ end
+
+ def visit_Gem_SafeMarshal_Elements_UserClass(r)
+ if resolve_class(r.name) == ::Hash && r.wrapped_object.is_a?(Elements::Hash)
+
+ hash = register_object({}.compare_by_identity)
+
+ o = r.wrapped_object
+ o.pairs.each_with_index do |(k, v), i|
+ push_stack i
+ k = visit(k)
+ push_stack k
+ hash[k] = visit(v)
+ end
+
+ if o.is_a?(Elements::HashWithDefaultValue)
+ push_stack :default
+ hash.default = visit(o.default)
+ end
+
+ hash
+ else
+ raise UnsupportedError.new("Unsupported user class #{resolve_class(r.name)} in marshal stream", stack: formatted_stack)
+ end
+ end
+
+ def resolve_class(n)
+ @class_cache[n] ||= begin
+ to_s = resolve_symbol_name(n)
+ raise UnpermittedClassError.new(name: to_s, stack: formatted_stack) unless @permitted_classes.include?(to_s)
+ visit_symbol_type(n)
+ begin
+ ::Object.const_get(to_s)
+ rescue NameError
+ raise ArgumentError, "Undefined class #{to_s.inspect}"
+ end
+ end
+ end
+
+ class RationalCompat
+ def marshal_load(s)
+ num, den = s
+ raise ArgumentError, "Expected 2 ints" unless s.size == 2 && num.is_a?(Integer) && den.is_a?(Integer)
+ Rational(num, den)
+ end
+ end
+ private_constant :RationalCompat
+
+ COMPAT_CLASSES = {}.tap do |h|
+ h[Rational] = RationalCompat
+ end.compare_by_identity.freeze
+ private_constant :COMPAT_CLASSES
+
+ def resolve_ivar(klass, name)
+ to_s = resolve_symbol_name(name)
+
+ raise UnpermittedIvarError.new(symbol: to_s, klass: klass, stack: formatted_stack) unless @permitted_ivars.fetch(klass.name, [].freeze).include?(to_s)
+
+ visit_symbol_type(name)
+ end
+
+ def visit_symbol_type(element)
+ case element
+ when Elements::Symbol
+ sym = element.name.to_sym
+ @symbols << sym
+ sym
+ when Elements::SymbolLink
+ visit_Gem_SafeMarshal_Elements_SymbolLink(element)
+ end
+ end
+
+ # This is a hot method, so avoid respond_to? checks on every invocation
+ if :read.respond_to?(:name)
+ def resolve_symbol_name(element)
+ case element
+ when Elements::Symbol
+ element.name
+ when Elements::SymbolLink
+ visit_Gem_SafeMarshal_Elements_SymbolLink(element).name
+ else
+ raise FormatError, "Expected symbol or symbol link, got #{element.inspect} @ #{formatted_stack.join(".")}"
+ end
+ end
+ else
+ def resolve_symbol_name(element)
+ case element
+ when Elements::Symbol
+ element.name
+ when Elements::SymbolLink
+ visit_Gem_SafeMarshal_Elements_SymbolLink(element).to_s
+ else
+ raise FormatError, "Expected symbol or symbol link, got #{element.inspect} @ #{formatted_stack.join(".")}"
+ end
+ end
+ end
+
+ def register_object(o)
+ @objects << o
+ o
+ end
+
+ def call_method(receiver, method, *args)
+ receiver.__send__(method, *args)
+ rescue NoMethodError => e
+ raise unless e.receiver == receiver
+
+ raise MethodCallError, "Unable to call #{method.inspect} on #{receiver.inspect}, perhaps it is a class using marshal compat, which is not visible in ruby? #{e}"
+ end
+
+ def formatted_stack
+ formatted = []
+ @stack[0, @stack_idx].each do |e|
+ if e.is_a?(Integer)
+ if formatted.last == "ivar_"
+ formatted[-1] = "ivar_#{e}"
+ else
+ formatted << "[#{e}]"
+ end
+ else
+ formatted << e
+ end
+ end
+ formatted
+ end
+
+ class Error < StandardError
+ end
+
+ class UnpermittedSymbolError < Error
+ def initialize(symbol:, stack:)
+ @symbol = symbol
+ @stack = stack
+ super "Attempting to load unpermitted symbol #{symbol.inspect} @ #{stack.join "."}"
+ end
+ end
+
+ class UnpermittedIvarError < Error
+ def initialize(symbol:, klass:, stack:)
+ @symbol = symbol
+ @klass = klass
+ @stack = stack
+ super "Attempting to set unpermitted ivar #{symbol.inspect} on object of class #{klass} @ #{stack.join "."}"
+ end
+ end
+
+ class UnpermittedClassError < Error
+ def initialize(name:, stack:)
+ @name = name
+ @stack = stack
+ super "Attempting to load unpermitted class #{name.inspect} @ #{stack.join "."}"
+ end
+ end
+
+ class UnsupportedError < Error
+ def initialize(message, stack:)
+ super "#{message} @ #{stack.join "."}"
+ end
+ end
+
+ class FormatError < Error
+ end
+
+ class MethodCallError < Error
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/safe_marshal/visitors/visitor.rb b/lib/rubygems/safe_marshal/visitors/visitor.rb
new file mode 100644
index 0000000000..c9a079dc0e
--- /dev/null
+++ b/lib/rubygems/safe_marshal/visitors/visitor.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+module Gem::SafeMarshal::Visitors
+ class Visitor
+ def visit(target)
+ send DISPATCH.fetch(target.class), target
+ end
+
+ private
+
+ DISPATCH = Gem::SafeMarshal::Elements.constants.each_with_object({}) do |c, h|
+ next if c == :Element
+
+ klass = Gem::SafeMarshal::Elements.const_get(c)
+ h[klass] = :"visit_#{klass.name.gsub("::", "_")}"
+ h.default = :visit_unknown_element
+ end.compare_by_identity.freeze
+ private_constant :DISPATCH
+
+ def visit_unknown_element(e)
+ raise ArgumentError, "Attempting to visit unknown element #{e.inspect}"
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Array(target)
+ target.elements.each {|e| visit(e) }
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Bignum(target); end
+ def visit_Gem_SafeMarshal_Elements_False(target); end
+ def visit_Gem_SafeMarshal_Elements_Float(target); end
+
+ def visit_Gem_SafeMarshal_Elements_Hash(target)
+ target.pairs.each do |k, v|
+ visit(k)
+ visit(v)
+ end
+ end
+
+ def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(target)
+ visit_Gem_SafeMarshal_Elements_Hash(target)
+ visit(target.default)
+ end
+
+ def visit_Gem_SafeMarshal_Elements_Integer(target); end
+ def visit_Gem_SafeMarshal_Elements_Nil(target); end
+
+ def visit_Gem_SafeMarshal_Elements_Object(target)
+ visit(target.name)
+ end
+
+ def visit_Gem_SafeMarshal_Elements_ObjectLink(target); end
+ def visit_Gem_SafeMarshal_Elements_String(target); end
+ def visit_Gem_SafeMarshal_Elements_Symbol(target); end
+ def visit_Gem_SafeMarshal_Elements_SymbolLink(target); end
+ def visit_Gem_SafeMarshal_Elements_True(target); end
+
+ def visit_Gem_SafeMarshal_Elements_UserDefined(target)
+ visit(target.name)
+ end
+
+ def visit_Gem_SafeMarshal_Elements_UserMarshal(target)
+ visit(target.name)
+ visit(target.data)
+ end
+
+ def visit_Gem_SafeMarshal_Elements_WithIvars(target)
+ visit(target.object)
+ target.ivars.each do |k, v|
+ visit(k)
+ visit(v)
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb
index 29312ad5a1..6a02a48230 100644
--- a/lib/rubygems/safe_yaml.rb
+++ b/lib/rubygems/safe_yaml.rb
@@ -1,5 +1,6 @@
-module Gem
+# frozen_string_literal: true
+module Gem
###
# This module is used for safely loading YAML specs from a gem. The
# `safe_load` method defined on this module is specifically designed for
@@ -17,8 +18,6 @@ module Gem
Gem::Specification
Gem::Version
Gem::Version::Requirement
- YAML::Syck::DefaultKey
- Syck::DefaultKey
].freeze
PERMITTED_SYMBOLS = %w[
@@ -26,34 +25,21 @@ module Gem
runtime
].freeze
- if ::YAML.respond_to? :safe_load
- def self.safe_load(input)
- if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1')
- ::YAML.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: true)
- else
- ::YAML.safe_load(input, PERMITTED_CLASSES, PERMITTED_SYMBOLS, true)
- end
- end
+ @aliases_enabled = true
+ def self.aliases_enabled=(value) # :nodoc:
+ @aliases_enabled = !!value
+ end
- def self.load(input)
- if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1')
- ::YAML.safe_load(input, permitted_classes: [::Symbol])
- else
- ::YAML.safe_load(input, [::Symbol])
- end
- end
- else
- unless Gem::Deprecate.skip
- warn "YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0)."
- end
+ def self.aliases_enabled? # :nodoc:
+ @aliases_enabled
+ end
- def self.safe_load(input, *args)
- ::YAML.load input
- end
+ def self.safe_load(input)
+ ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled)
+ end
- def self.load(input)
- ::YAML.load input
- end
+ def self.load(input)
+ ::Psych.safe_load(input, permitted_classes: [::Symbol])
end
end
end
diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb
index c80639af6d..69ba87b07f 100644
--- a/lib/rubygems/security.rb
+++ b/lib/rubygems/security.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/exceptions'
-require_relative 'openssl'
+require_relative "exceptions"
+require_relative "openssl"
##
# = Signing gems
@@ -152,6 +153,7 @@ require_relative 'openssl'
# certificate for EMAIL_ADDR
# -C, --certificate CERT Signing certificate for --sign
# -K, --private-key KEY Key for --sign or --build
+# -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA.
# -s, --sign CERT Signs CERT with the key from -K
# and the certificate from -C
# -d, --days NUMBER_OF_DAYS Days before the certificate expires
@@ -260,7 +262,7 @@ require_relative 'openssl'
# 2. Grab the public key from the gemspec
#
# gem spec some_signed_gem-1.0.gem cert_chain | \
-# ruby -ryaml -e 'puts YAML.load($stdin)' > public_key.crt
+# ruby -rpsych -e 'puts Psych.load($stdin)' > public_key.crt
#
# 3. Generate a SHA1 hash of the data.tar.gz
#
@@ -317,15 +319,13 @@ require_relative 'openssl'
# * Honor extension restrictions
# * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
# file, instead of an array embedded in the metadata.
-# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
#
# == Original author
#
# Paul Duncan <pabs@pablotron.org>
-# http://pablotron.org/
+# https://pablotron.org/
module Gem::Security
-
##
# Gem::Security default exception type
@@ -334,31 +334,33 @@ module Gem::Security
##
# Used internally to select the signing digest from all computed digests
- DIGEST_NAME = 'SHA256' # :nodoc:
+ DIGEST_NAME = "SHA256" # :nodoc:
##
- # Algorithm for creating the key pair used to sign gems
+ # Length of keys created by RSA and DSA keys
- KEY_ALGORITHM =
- if defined?(OpenSSL::PKey::RSA)
- OpenSSL::PKey::RSA
- end
+ RSA_DSA_KEY_LENGTH = 3072
+
+ ##
+ # Default algorithm to use when building a key pair
+
+ DEFAULT_KEY_ALGORITHM = "RSA"
##
- # Length of keys created by KEY_ALGORITHM
+ # Named curve used for Elliptic Curve
- KEY_LENGTH = 3072
+ EC_NAME = "secp384r1"
##
# Cipher used to encrypt the key pair used to sign gems.
# Must be in the list returned by OpenSSL::Cipher.ciphers
- KEY_CIPHER = OpenSSL::Cipher.new('AES-256-CBC') if defined?(OpenSSL::Cipher)
+ KEY_CIPHER = OpenSSL::Cipher.new("AES-256-CBC") if defined?(OpenSSL::Cipher)
##
# One day in seconds
- ONE_DAY = 86400
+ ONE_DAY = 86_400
##
# One year in seconds
@@ -374,10 +376,10 @@ module Gem::Security
# * The certificate contains a subject key identifier
EXTENSIONS = {
- 'basicConstraints' => 'CA:FALSE',
- 'keyUsage' =>
- 'keyEncipherment,dataEncipherment,digitalSignature',
- 'subjectKeyIdentifier' => 'hash',
+ "basicConstraints" => "CA:FALSE",
+ "keyUsage" =>
+ "keyEncipherment,dataEncipherment,digitalSignature",
+ "subjectKeyIdentifier" => "hash",
}.freeze
def self.alt_name_or_x509_entry(certificate, x509_entry)
@@ -396,11 +398,10 @@ module Gem::Security
#
# The +extensions+ restrict the key to the indicated uses.
- def self.create_cert(subject, key, age = ONE_YEAR, extensions = EXTENSIONS,
- serial = 1)
+ def self.create_cert(subject, key, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1)
cert = OpenSSL::X509::Certificate.new
- cert.public_key = key.public_key
+ cert.public_key = get_public_key(key)
cert.version = 2
cert.serial = serial
@@ -419,6 +420,19 @@ module Gem::Security
end
##
+ # Gets the right public key from a PKey instance
+
+ def self.get_public_key(key)
+ # Ruby 3.0 (Ruby/OpenSSL 2.2) or later
+ return OpenSSL::PKey.read(key.public_to_der) if key.respond_to?(:public_to_der)
+ return key.public_key unless key.is_a?(OpenSSL::PKey::EC)
+
+ ec_key = OpenSSL::PKey::EC.new(key.group.curve_name)
+ ec_key.public_key = key.public_key
+ ec_key
+ end
+
+ ##
# Creates a self-signed certificate with an issuer and subject from +email+,
# a subject alternative name of +email+ and the given +extensions+ for the
# +key+.
@@ -435,8 +449,7 @@ module Gem::Security
# Creates a self-signed certificate with an issuer and subject of +subject+
# and the given +extensions+ for the +key+.
- def self.create_cert_self_signed(subject, key, age = ONE_YEAR,
- extensions = EXTENSIONS, serial = 1)
+ def self.create_cert_self_signed(subject, key, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1)
certificate = create_cert subject, key, age, extensions
sign certificate, key, certificate, age, extensions, serial
@@ -446,39 +459,44 @@ module Gem::Security
# Creates a new digest instance using the specified +algorithm+. The default
# is SHA256.
- if defined?(OpenSSL::Digest)
- def self.create_digest(algorithm = DIGEST_NAME)
- OpenSSL::Digest.new(algorithm)
- end
- else
- require 'digest'
-
- def self.create_digest(algorithm = DIGEST_NAME)
- Digest.const_get(algorithm).new
- end
+ def self.create_digest(algorithm = DIGEST_NAME)
+ OpenSSL::Digest.new(algorithm)
end
##
- # Creates a new key pair of the specified +length+ and +algorithm+. The
- # default is a 3072 bit RSA key.
-
- def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM)
- algorithm.new length
+ # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC
+ # are supported.
+
+ def self.create_key(algorithm)
+ if defined?(OpenSSL::PKey)
+ case algorithm.downcase
+ when "dsa"
+ OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH)
+ when "rsa"
+ OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH)
+ when "ec"
+ OpenSSL::PKey::EC.generate(EC_NAME)
+ else
+ raise Gem::Security::Exception,
+ "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported."
+ end
+ end
end
##
# Turns +email_address+ into an OpenSSL::X509::Name
def self.email_to_name(email_address)
- email_address = email_address.gsub(/[^\w@.-]+/i, '_')
+ email_address = email_address.gsub(/[^\w@.-]+/i, "_")
- cn, dcs = email_address.split '@'
+ cn, dcs = email_address.split "@"
- dcs = dcs.split '.'
+ dcs = dcs.split "."
- name = "CN=#{cn}/#{dcs.map {|dc| "DC=#{dc}" }.join '/'}"
-
- OpenSSL::X509::Name.parse name
+ OpenSSL::X509::Name.new([
+ ["CN", cn],
+ *dcs.map {|dc| ["DC", dc] },
+ ])
end
##
@@ -487,12 +505,11 @@ module Gem::Security
#--
# TODO increment serial
- def self.re_sign(expired_certificate, private_key, age = ONE_YEAR,
- extensions = EXTENSIONS)
+ def self.re_sign(expired_certificate, private_key, age = ONE_YEAR, extensions = EXTENSIONS)
raise Gem::Security::Exception,
"incorrect signing key for re-signing " +
- "#{expired_certificate.subject}" unless
- expired_certificate.public_key.to_pem == private_key.public_key.to_pem
+ expired_certificate.subject.to_s unless
+ expired_certificate.check_private_key(private_key)
unless expired_certificate.subject.to_s ==
expired_certificate.issuer.to_s
@@ -500,7 +517,7 @@ module Gem::Security
issuer = alt_name_or_x509_entry expired_certificate, :issuer
raise Gem::Security::Exception,
- "#{subject} is not self-signed, contact #{issuer} " +
+ "#{subject} is not self-signed, contact #{issuer} " \
"to obtain a valid certificate"
end
@@ -524,23 +541,22 @@ module Gem::Security
#
# Returns the newly signed certificate.
- def self.sign(certificate, signing_key, signing_cert,
- age = ONE_YEAR, extensions = EXTENSIONS, serial = 1)
+ def self.sign(certificate, signing_key, signing_cert, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1)
signee_subject = certificate.subject
signee_key = certificate.public_key
alt_name = certificate.extensions.find do |extension|
- extension.oid == 'subjectAltName'
+ extension.oid == "subjectAltName"
end
- extensions = extensions.merge 'subjectAltName' => alt_name.value if
+ extensions = extensions.merge "subjectAltName" => alt_name.value if
alt_name
issuer_alt_name = signing_cert.extensions.find do |extension|
- extension.oid == 'subjectAltName'
+ extension.oid == "subjectAltName"
end
- extensions = extensions.merge 'issuerAltName' => issuer_alt_name.value if
+ extensions = extensions.merge "issuerAltName" => issuer_alt_name.value if
issuer_alt_name
signed = create_cert signee_subject, signee_key, age, extensions, serial
@@ -556,7 +572,7 @@ module Gem::Security
def self.trust_dir
return @trust_dir if @trust_dir
- dir = File.join Gem.user_home, '.gem', 'trust'
+ dir = File.join Gem.user_home, ".gem", "trust"
@trust_dir ||= Gem::Security::TrustDir.new dir
end
@@ -573,11 +589,11 @@ module Gem::Security
# +permissions+. If passed +cipher+ and +passphrase+ those arguments will be
# passed to +to_pem+.
- def self.write(pemmable, path, permissions = 0600, passphrase = nil, cipher = KEY_CIPHER)
+ def self.write(pemmable, path, permissions = 0o600, passphrase = nil, cipher = KEY_CIPHER)
path = File.expand_path path
- File.open path, 'wb', permissions do |io|
- if passphrase and cipher
+ File.open path, "wb", permissions do |io|
+ if passphrase && cipher
io.write pemmable.to_pem cipher, passphrase
else
io.write pemmable.to_pem
@@ -588,13 +604,12 @@ module Gem::Security
end
reset
-
end
if Gem::HAVE_OPENSSL
- require 'rubygems/security/policy'
- require 'rubygems/security/policies'
- require 'rubygems/security/trust_dir'
+ require_relative "security/policy"
+ require_relative "security/policies"
+ require_relative "security/trust_dir"
end
-require 'rubygems/security/signer'
+require_relative "security/signer"
diff --git a/lib/rubygems/security/policies.rb b/lib/rubygems/security/policies.rb
index 8f6ad99316..41f66043ad 100644
--- a/lib/rubygems/security/policies.rb
+++ b/lib/rubygems/security/policies.rb
@@ -1,17 +1,17 @@
# frozen_string_literal: true
-module Gem::Security
+module Gem::Security
##
# No security policy: all package signature checks are disabled.
NoSecurity = Policy.new(
- 'No Security',
- :verify_data => false,
- :verify_signer => false,
- :verify_chain => false,
- :verify_root => false,
- :only_trusted => false,
- :only_signed => false
+ "No Security",
+ verify_data: false,
+ verify_signer: false,
+ verify_chain: false,
+ verify_root: false,
+ only_trusted: false,
+ only_signed: false
)
##
@@ -23,13 +23,13 @@ module Gem::Security
# easily spoofed, and is not recommended.
AlmostNoSecurity = Policy.new(
- 'Almost No Security',
- :verify_data => true,
- :verify_signer => false,
- :verify_chain => false,
- :verify_root => false,
- :only_trusted => false,
- :only_signed => false
+ "Almost No Security",
+ verify_data: true,
+ verify_signer: false,
+ verify_chain: false,
+ verify_root: false,
+ only_trusted: false,
+ only_signed: false
)
##
@@ -40,13 +40,13 @@ module Gem::Security
# is not recommended.
LowSecurity = Policy.new(
- 'Low Security',
- :verify_data => true,
- :verify_signer => true,
- :verify_chain => false,
- :verify_root => false,
- :only_trusted => false,
- :only_signed => false
+ "Low Security",
+ verify_data: true,
+ verify_signer: true,
+ verify_chain: false,
+ verify_root: false,
+ only_trusted: false,
+ only_signed: false
)
##
@@ -59,13 +59,13 @@ module Gem::Security
# gem off as unsigned.
MediumSecurity = Policy.new(
- 'Medium Security',
- :verify_data => true,
- :verify_signer => true,
- :verify_chain => true,
- :verify_root => true,
- :only_trusted => true,
- :only_signed => false
+ "Medium Security",
+ verify_data: true,
+ verify_signer: true,
+ verify_chain: true,
+ verify_root: true,
+ only_trusted: true,
+ only_signed: false
)
##
@@ -78,38 +78,37 @@ module Gem::Security
# a reasonable guarantee that the contents of the gem have not been altered.
HighSecurity = Policy.new(
- 'High Security',
- :verify_data => true,
- :verify_signer => true,
- :verify_chain => true,
- :verify_root => true,
- :only_trusted => true,
- :only_signed => true
+ "High Security",
+ verify_data: true,
+ verify_signer: true,
+ verify_chain: true,
+ verify_root: true,
+ only_trusted: true,
+ only_signed: true
)
##
# Policy used to verify a certificate and key when signing a gem
SigningPolicy = Policy.new(
- 'Signing Policy',
- :verify_data => false,
- :verify_signer => true,
- :verify_chain => true,
- :verify_root => true,
- :only_trusted => false,
- :only_signed => false
+ "Signing Policy",
+ verify_data: false,
+ verify_signer: true,
+ verify_chain: true,
+ verify_root: true,
+ only_trusted: false,
+ only_signed: false
)
##
# Hash of configured security policies
Policies = {
- 'NoSecurity' => NoSecurity,
- 'AlmostNoSecurity' => AlmostNoSecurity,
- 'LowSecurity' => LowSecurity,
- 'MediumSecurity' => MediumSecurity,
- 'HighSecurity' => HighSecurity,
+ "NoSecurity" => NoSecurity,
+ "AlmostNoSecurity" => AlmostNoSecurity,
+ "LowSecurity" => LowSecurity,
+ "MediumSecurity" => MediumSecurity,
+ "HighSecurity" => HighSecurity,
# SigningPolicy is not intended for use by `gem -P` so do not list it
}.freeze
-
end
diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb
index 7629d64796..7b86ac5763 100644
--- a/lib/rubygems/security/policy.rb
+++ b/lib/rubygems/security/policy.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
-require 'rubygems/user_interaction'
+
+require_relative "../user_interaction"
##
# A Gem::Security::Policy object encapsulates the settings for verifying
@@ -53,8 +54,8 @@ class Gem::Security::Policy
# and is valid for the given +time+.
def check_chain(chain, time)
- raise Gem::Security::Exception, 'missing signing chain' unless chain
- raise Gem::Security::Exception, 'empty signing chain' if chain.empty?
+ raise Gem::Security::Exception, "missing signing chain" unless chain
+ raise Gem::Security::Exception, "empty signing chain" if chain.empty?
begin
chain.each_cons 2 do |issuer, cert|
@@ -83,21 +84,21 @@ class Gem::Security::Policy
# If the +issuer+ is +nil+ no verification is performed.
def check_cert(signer, issuer, time)
- raise Gem::Security::Exception, 'missing signing certificate' unless
+ raise Gem::Security::Exception, "missing signing certificate" unless
signer
message = "certificate #{signer.subject}"
- if not_before = signer.not_before and not_before > time
+ if (not_before = signer.not_before) && not_before > time
raise Gem::Security::Exception,
"#{message} not valid before #{not_before}"
end
- if not_after = signer.not_after and not_after < time
+ if (not_after = signer.not_after) && not_after < time
raise Gem::Security::Exception, "#{message} not valid after #{not_after}"
end
- if issuer and not signer.verify issuer.public_key
+ if issuer && !signer.verify(issuer.public_key)
raise Gem::Security::Exception,
"#{message} was not issued by #{issuer.subject}"
end
@@ -109,15 +110,15 @@ class Gem::Security::Policy
# Ensures the public key of +key+ matches the public key in +signer+
def check_key(signer, key)
- unless signer and key
+ unless signer && key
return true unless @only_signed
- raise Gem::Security::Exception, 'missing key or signature'
+ raise Gem::Security::Exception, "missing key or signature"
end
raise Gem::Security::Exception,
"certificate #{signer.subject} does not match the signing key" unless
- signer.public_key.to_pem == key.public_key.to_pem
+ signer.check_private_key(key)
true
end
@@ -127,14 +128,14 @@ class Gem::Security::Policy
# +time+.
def check_root(chain, time)
- raise Gem::Security::Exception, 'missing signing chain' unless chain
+ raise Gem::Security::Exception, "missing signing chain" unless chain
root = chain.first
- raise Gem::Security::Exception, 'missing root certificate' unless root
+ raise Gem::Security::Exception, "missing root certificate" unless root
raise Gem::Security::Exception,
- "root certificate #{root.subject} is not self-signed " +
+ "root certificate #{root.subject} is not self-signed " \
"(issuer #{root.issuer})" if
root.issuer != root.subject
@@ -146,11 +147,11 @@ class Gem::Security::Policy
# the digests of the two certificates match according to +digester+
def check_trust(chain, digester, trust_dir)
- raise Gem::Security::Exception, 'missing signing chain' unless chain
+ raise Gem::Security::Exception, "missing signing chain" unless chain
root = chain.first
- raise Gem::Security::Exception, 'missing root certificate' unless root
+ raise Gem::Security::Exception, "missing root certificate" unless root
path = Gem::Security.trust_dir.cert_path root
@@ -164,13 +165,13 @@ class Gem::Security::Policy
end
save_cert = OpenSSL::X509::Certificate.new File.read path
- save_dgst = digester.digest save_cert.public_key.to_s
+ save_dgst = digester.digest save_cert.public_key.to_pem
- pkey_str = root.public_key.to_s
+ pkey_str = root.public_key.to_pem
cert_dgst = digester.digest pkey_str
raise Gem::Security::Exception,
- "trusted root certificate #{root.subject} checksum " +
+ "trusted root certificate #{root.subject} checksum " \
"does not match signing root certificate checksum" unless
save_dgst == cert_dgst
@@ -182,7 +183,7 @@ class Gem::Security::Policy
def subject(certificate) # :nodoc:
certificate.extensions.each do |extension|
- next unless extension.oid == 'subjectAltName'
+ next unless extension.oid == "subjectAltName"
return extension.value
end
@@ -191,11 +192,8 @@ class Gem::Security::Policy
end
def inspect # :nodoc:
- ("[Policy: %s - data: %p signer: %p chain: %p root: %p " +
- "signed-only: %p trusted-only: %p]") % [
- @name, @verify_chain, @verify_data, @verify_root, @verify_signer,
- @only_signed, @only_trusted
- ]
+ format("[Policy: %s - data: %p signer: %p chain: %p root: %p " \
+ "signed-only: %p trusted-only: %p]", @name, @verify_chain, @verify_data, @verify_root, @verify_signer, @only_signed, @only_trusted)
end
##
@@ -205,8 +203,7 @@ class Gem::Security::Policy
#
# If +key+ is given it is used to validate the signing certificate.
- def verify(chain, key = nil, digests = {}, signatures = {},
- full_name = '(unknown)')
+ def verify(chain, key = nil, digests = {}, signatures = {}, full_name = "(unknown)")
if signatures.empty?
if @only_signed
raise Gem::Security::Exception,
@@ -225,13 +222,13 @@ class Gem::Security::Policy
trust_dir = opt[:trust_dir]
time = Time.now
- _, signer_digests = digests.find do |algorithm, file_digests|
+ _, signer_digests = digests.find do |_algorithm, file_digests|
file_digests.values.first.name == Gem::Security::DIGEST_NAME
end
if @verify_data
- raise Gem::Security::Exception, 'no digests provided (probable bug)' if
- signer_digests.nil? or signer_digests.empty?
+ raise Gem::Security::Exception, "no digests provided (probable bug)" if
+ signer_digests.nil? || signer_digests.empty?
else
signer_digests = {}
end
@@ -248,7 +245,7 @@ class Gem::Security::Policy
if @only_trusted
check_trust chain, digester, trust_dir
- elsif signatures.empty? and digests.empty?
+ elsif signatures.empty? && digests.empty?
# trust is irrelevant if there's no signatures to verify
else
alert_warning "#{subject signer} is not trusted for #{full_name}"
@@ -287,5 +284,5 @@ class Gem::Security::Policy
true
end
- alias to_s name # :nodoc:
+ alias_method :to_s, :name # :nodoc:
end
diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb
index 6c85ab08d2..5732fb57fd 100644
--- a/lib/rubygems/security/signer.rb
+++ b/lib/rubygems/security/signer.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
+
##
# Basic OpenSSL-based package signing class.
-require "rubygems/user_interaction"
+require_relative "../user_interaction"
class Gem::Security::Signer
include Gem::UserInteraction
@@ -42,7 +43,7 @@ class Gem::Security::Signer
def self.re_sign_cert(expired_cert, expired_cert_path, private_key)
return unless expired_cert.not_after < Time.now
- expiry = expired_cert.not_after.strftime('%Y%m%d%H%M%S')
+ expiry = expired_cert.not_after.strftime("%Y%m%d%H%M%S")
expired_cert_file = "#{File.basename(expired_cert_path)}.expired.#{expiry}"
new_expired_cert_path = File.join(Gem.user_home, ".gem", expired_cert_file)
@@ -83,8 +84,8 @@ class Gem::Security::Signer
@digest_name = Gem::Security::DIGEST_NAME
@digest_algorithm = Gem::Security.create_digest(@digest_name)
- if @key && !@key.is_a?(OpenSSL::PKey::RSA)
- @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase)
+ if @key && !@key.is_a?(OpenSSL::PKey::PKey)
+ @key = OpenSSL::PKey.read(File.read(@key), @passphrase)
end
if @cert_chain
@@ -105,7 +106,7 @@ class Gem::Security::Signer
# this value is preferred, otherwise the subject is used.
def extract_name(cert) # :nodoc:
- subject_alt_name = cert.extensions.find {|e| 'subjectAltName' == e.oid }
+ subject_alt_name = cert.extensions.find {|e| e.oid == "subjectAltName" }
if subject_alt_name
/\Aemail:/ =~ subject_alt_name.value # rubocop:disable Performance/StartWith
@@ -139,9 +140,9 @@ class Gem::Security::Signer
def sign(data)
return unless @key
- raise Gem::Security::Exception, 'no certs provided' if @cert_chain.empty?
+ raise Gem::Security::Exception, "no certs provided" if @cert_chain.empty?
- if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now
+ if @cert_chain.length == 1 && @cert_chain.last.not_after < Time.now
alert("Your certificate has expired, trying to re-sign it...")
re_sign_key(
@@ -174,16 +175,23 @@ class Gem::Security::Signer
old_cert = @cert_chain.last
disk_cert_path = File.join(Gem.default_cert_path)
- disk_cert = File.read(disk_cert_path) rescue nil
+ disk_cert = begin
+ File.read(disk_cert_path)
+ rescue StandardError
+ nil
+ end
disk_key_path = File.join(Gem.default_key_path)
- disk_key =
- OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil
+ disk_key = begin
+ OpenSSL::PKey.read(File.read(disk_key_path), @passphrase)
+ rescue StandardError
+ nil
+ end
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')
+ 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)
diff --git a/lib/rubygems/security/trust_dir.rb b/lib/rubygems/security/trust_dir.rb
index 456947274c..d23d161cfe 100644
--- a/lib/rubygems/security/trust_dir.rb
+++ b/lib/rubygems/security/trust_dir.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The TrustDir manages the trusted certificates for gem signature
# verification.
@@ -8,8 +9,8 @@ class Gem::Security::TrustDir
# Default permissions for the trust directory and its contents
DEFAULT_PERMISSIONS = {
- :trust_dir => 0700,
- :trusted_cert => 0600,
+ trust_dir: 0o700,
+ trusted_cert: 0o600,
}.freeze
##
@@ -41,16 +42,14 @@ class Gem::Security::TrustDir
def each_certificate
return enum_for __method__ unless block_given?
- glob = File.join @dir, '*.pem'
+ glob = File.join @dir, "*.pem"
Dir[glob].each do |certificate_file|
- begin
- certificate = load_certificate certificate_file
+ certificate = load_certificate certificate_file
- yield certificate, certificate_file
- rescue OpenSSL::X509::CertificateError
- next # HACK warn
- end
+ yield certificate, certificate_file
+ rescue OpenSSL::X509::CertificateError
+ next # HACK: warn
end
end
@@ -92,7 +91,7 @@ class Gem::Security::TrustDir
destination = cert_path certificate
- File.open destination, 'wb', 0600 do |io|
+ File.open destination, "wb", 0o600 do |io|
io.write certificate.to_pem
io.chmod(@permissions[:trusted_cert])
end
@@ -104,15 +103,15 @@ class Gem::Security::TrustDir
# permissions.
def verify
- require 'fileutils'
+ require "fileutils"
if File.exist? @dir
raise Gem::Security::Exception,
"trust directory #{@dir} is not a directory" unless
File.directory? @dir
- FileUtils.chmod 0700, @dir
+ FileUtils.chmod 0o700, @dir
else
- FileUtils.mkdir_p @dir, :mode => @permissions[:trust_dir]
+ FileUtils.mkdir_p @dir, mode: @permissions[:trust_dir]
end
end
end
diff --git a/lib/rubygems/security_option.rb b/lib/rubygems/security_option.rb
index 3403aaaf05..3a101fe9db 100644
--- a/lib/rubygems/security_option.rb
+++ b/lib/rubygems/security_option.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems'
+require_relative "../rubygems"
# forward-declare
@@ -19,23 +20,23 @@ end
module Gem::SecurityOption
def add_security_option
- OptionParser.accept Gem::Security::Policy do |value|
- require 'rubygems/security'
+ Gem::OptionParser.accept Gem::Security::Policy do |value|
+ require_relative "security"
- raise OptionParser::InvalidArgument, 'OpenSSL not installed' unless
+ raise Gem::OptionParser::InvalidArgument, "OpenSSL not installed" unless
defined?(Gem::Security::HighSecurity)
policy = Gem::Security::Policies[value]
unless policy
valid = Gem::Security::Policies.keys.sort
- raise OptionParser::InvalidArgument, "#{value} (#{valid.join ', '} are valid)"
+ raise Gem::OptionParser::InvalidArgument, "#{value} (#{valid.join ", "} are valid)"
end
policy
end
- add_option(:"Install/Update", '-P', '--trust-policy POLICY',
+ add_option(:"Install/Update", "-P", "--trust-policy POLICY",
Gem::Security::Policy,
- 'Specify gem trust policy') do |value, options|
+ "Specify gem trust policy") do |value, options|
options[:security_policy] = value
end
end
diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb
deleted file mode 100644
index 2c2805f31c..0000000000
--- a/lib/rubygems/server.rb
+++ /dev/null
@@ -1,882 +0,0 @@
-# frozen_string_literal: true
-require 'zlib'
-require 'erb'
-require 'uri'
-
-require 'rubygems'
-require 'rubygems/rdoc'
-
-##
-# Gem::Server and allows users to serve gems for consumption by
-# `gem --remote-install`.
-#
-# gem_server starts an HTTP server on the given port and serves the following:
-# * "/" - Browsing of gem spec files for installed gems
-# * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index
-# * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs
-# name/version/platform index
-# * "/quick/" - Individual gemspecs
-# * "/gems" - Direct access to download the installable gems
-# * "/rdoc?q=" - Search for installed rdoc documentation
-#
-# == Usage
-#
-# gem_server = Gem::Server.new Gem.dir, 8089, false
-# gem_server.run
-#
-#--
-# TODO Refactor into a real WEBrick servlet to remove code duplication.
-
-class Gem::Server
- attr_reader :spec_dirs
-
- include ERB::Util
- include Gem::UserInteraction
-
- SEARCH = <<-ERB.freeze
- <form class="headerSearch" name="headerSearchForm" method="get" action="/rdoc">
- <div id="search" style="float:right">
- <label for="q">Filter/Search</label>
- <input id="q" type="text" style="width:10em" name="q">
- <button type="submit" style="display:none"></button>
- </div>
- </form>
- ERB
-
- DOC_TEMPLATE = <<-'ERB'.freeze
- <?xml version="1.0" encoding="iso-8859-1"?>
- <!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>RubyGems Documentation Index</title>
- <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
- </head>
- <body>
- <div id="fileHeader">
-<%= SEARCH %>
- <h1>RubyGems Documentation Index</h1>
- </div>
- <!-- banner header -->
-
- <div id="bodyContent">
- <div id="contextContent">
- <div id="description">
- <h1>Summary</h1>
- <p>There are <%=values["gem_count"]%> gems installed:</p>
- <p>
- <%= values["specs"].map { |v| "<a href=\"##{u v["name"]}\">#{h v["name"]}</a>" }.join ', ' %>.
- <h1>Gems</h1>
-
- <dl>
- <% values["specs"].each do |spec| %>
- <dt>
- <% if spec["first_name_entry"] then %>
- <a name="<%=h spec["name"]%>"></a>
- <% end %>
-
- <b><%=h spec["name"]%> <%=h spec["version"]%></b>
-
- <% if spec["ri_installed"] || spec["rdoc_installed"] then %>
- <a href="<%=spec["doc_path"]%>">[rdoc]</a>
- <% else %>
- <span title="rdoc not installed">[rdoc]</span>
- <% end %>
-
- <% if spec["homepage"] then %>
- <a href="<%=uri_encode spec["homepage"]%>" title="<%=h spec["homepage"]%>">[www]</a>
- <% else %>
- <span title="no homepage available">[www]</span>
- <% end %>
-
- <% if spec["has_deps"] then %>
- - depends on
- <%= spec["dependencies"].map { |v| "<a href=\"##{u v["name"]}\">#{h v["name"]}</a>" }.join ', ' %>.
- <% end %>
- </dt>
- <dd>
- <%=spec["summary"]%>
- <% if spec["executables"] then %>
- <br/>
-
- <% if spec["only_one_executable"] then %>
- Executable is
- <% else %>
- Executables are
- <%end%>
-
- <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{h v["executable"]}</span>"}.join ', ' %>.
-
- <%end%>
- <br/>
- <br/>
- </dd>
- <% end %>
- </dl>
-
- </div>
- </div>
- </div>
- <div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
- </div>
- </body>
- </html>
- ERB
-
- # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
- RDOC_CSS = <<-CSS.freeze
-body {
- font-family: Verdana,Arial,Helvetica,sans-serif;
- font-size: 90%;
- margin: 0;
- margin-left: 40px;
- padding: 0;
- background: white;
-}
-
-h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
-h1 { font-size: 150%; }
-h2,h3,h4 { margin-top: 1em; }
-
-a { background: #eef; color: #039; text-decoration: none; }
-a:hover { background: #039; color: #eef; }
-
-/* Override the base stylesheets Anchor inside a table cell */
-td > a {
- background: transparent;
- color: #039;
- text-decoration: none;
-}
-
-/* and inside a section title */
-.section-title > a {
- background: transparent;
- color: #eee;
- text-decoration: none;
-}
-
-/* === Structural elements =================================== */
-
-div#index {
- margin: 0;
- margin-left: -40px;
- padding: 0;
- font-size: 90%;
-}
-
-
-div#index a {
- margin-left: 0.7em;
-}
-
-div#index .section-bar {
- margin-left: 0px;
- padding-left: 0.7em;
- background: #ccc;
- font-size: small;
-}
-
-
-div#classHeader, div#fileHeader {
- width: auto;
- color: white;
- padding: 0.5em 1.5em 0.5em 1.5em;
- margin: 0;
- margin-left: -40px;
- border-bottom: 3px solid #006;
-}
-
-div#classHeader a, div#fileHeader a {
- background: inherit;
- color: white;
-}
-
-div#classHeader td, div#fileHeader td {
- background: inherit;
- color: white;
-}
-
-
-div#fileHeader {
- background: #057;
-}
-
-div#classHeader {
- background: #048;
-}
-
-
-.class-name-in-header {
- font-size: 180%;
- font-weight: bold;
-}
-
-
-div#bodyContent {
- padding: 0 1.5em 0 1.5em;
-}
-
-div#description {
- padding: 0.5em 1.5em;
- background: #efefef;
- border: 1px dotted #999;
-}
-
-div#description h1,h2,h3,h4,h5,h6 {
- color: #125;;
- background: transparent;
-}
-
-div#validator-badges {
- text-align: center;
-}
-div#validator-badges img { border: 0; }
-
-div#copyright {
- color: #333;
- background: #efefef;
- font: 0.75em sans-serif;
- margin-top: 5em;
- margin-bottom: 0;
- padding: 0.5em 2em;
-}
-
-
-/* === Classes =================================== */
-
-table.header-table {
- color: white;
- font-size: small;
-}
-
-.type-note {
- font-size: small;
- color: #DEDEDE;
-}
-
-.xxsection-bar {
- background: #eee;
- color: #333;
- padding: 3px;
-}
-
-.section-bar {
- color: #333;
- border-bottom: 1px solid #999;
- margin-left: -20px;
-}
-
-
-.section-title {
- background: #79a;
- color: #eee;
- padding: 3px;
- margin-top: 2em;
- margin-left: -30px;
- border: 1px solid #999;
-}
-
-.top-aligned-row { vertical-align: top }
-.bottom-aligned-row { vertical-align: bottom }
-
-/* --- Context section classes ----------------------- */
-
-.context-row { }
-.context-item-name { font-family: monospace; font-weight: bold; color: black; }
-.context-item-value { font-size: small; color: #448; }
-.context-item-desc { color: #333; padding-left: 2em; }
-
-/* --- Method classes -------------------------- */
-.method-detail {
- background: #efefef;
- padding: 0;
- margin-top: 0.5em;
- margin-bottom: 1em;
- border: 1px dotted #ccc;
-}
-.method-heading {
- color: black;
- background: #ccc;
- border-bottom: 1px solid #666;
- padding: 0.2em 0.5em 0 0.5em;
-}
-.method-signature { color: black; background: inherit; }
-.method-name { font-weight: bold; }
-.method-args { font-style: italic; }
-.method-description { padding: 0 0.5em 0 0.5em; }
-
-/* --- Source code sections -------------------- */
-
-a.source-toggle { font-size: 90%; }
-div.method-source-code {
- background: #262626;
- color: #ffdead;
- margin: 1em;
- padding: 0.5em;
- border: 1px dashed #999;
- overflow: hidden;
-}
-
-div.method-source-code pre { color: #ffdead; overflow: hidden; }
-
-/* --- Ruby keyword styles --------------------- */
-
-.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
-
-.ruby-constant { color: #7fffd4; background: transparent; }
-.ruby-keyword { color: #00ffff; background: transparent; }
-.ruby-ivar { color: #eedd82; background: transparent; }
-.ruby-operator { color: #00ffee; background: transparent; }
-.ruby-identifier { color: #ffdead; background: transparent; }
-.ruby-node { color: #ffa07a; background: transparent; }
-.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
-.ruby-regexp { color: #ffa07a; background: transparent; }
-.ruby-value { color: #7fffd4; background: transparent; }
- CSS
-
- RDOC_NO_DOCUMENTATION = <<-'ERB'.freeze
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>Found documentation</title>
- <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
- </head>
- <body>
- <div id="fileHeader">
-<%= SEARCH %>
- <h1>No documentation found</h1>
- </div>
-
- <div id="bodyContent">
- <div id="contextContent">
- <div id="description">
- <p>No gems matched <%= h query.inspect %></p>
-
- <p>
- Back to <a href="/">complete gem index</a>
- </p>
-
- </div>
- </div>
- </div>
- <div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
- </div>
- </body>
-</html>
- ERB
-
- RDOC_SEARCH_TEMPLATE = <<-'ERB'.freeze
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>Found documentation</title>
- <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
- </head>
- <body>
- <div id="fileHeader">
-<%= SEARCH %>
- <h1>Found documentation</h1>
- </div>
- <!-- banner header -->
-
- <div id="bodyContent">
- <div id="contextContent">
- <div id="description">
- <h1>Summary</h1>
- <p><%=doc_items.length%> documentation topics found.</p>
- <h1>Topics</h1>
-
- <dl>
- <% doc_items.each do |doc_item| %>
- <dt>
- <b><%=doc_item[:name]%></b>
- <a href="<%=u doc_item[:url]%>">[rdoc]</a>
- </dt>
- <dd>
- <%=h doc_item[:summary]%>
- <br/>
- <br/>
- </dd>
- <% end %>
- </dl>
-
- <p>
- Back to <a href="/">complete gem index</a>
- </p>
-
- </div>
- </div>
- </div>
- <div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
- </div>
- </body>
-</html>
- ERB
-
- def self.run(options)
- new(options[:gemdir], options[:port], options[:daemon],
- options[:launch], options[:addresses]).run
- end
-
- def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
- begin
- require 'webrick'
- rescue LoadError
- abort "webrick is not found. You may need to `gem install webrick` to install webrick."
- end
-
- Gem::RDoc.load_rdoc
- Socket.do_not_reverse_lookup = true
-
- @gem_dirs = Array gem_dirs
- @port = port
- @daemon = daemon
- @launch = launch
- @addresses = addresses
-
- logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
- @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
-
- @spec_dirs = @gem_dirs.map {|gem_dir| File.join gem_dir, 'specifications' }
- @spec_dirs.reject! {|spec_dir| !File.directory? spec_dir }
-
- reset_gems
-
- @have_rdoc_4_plus = nil
- end
-
- def add_date(res)
- res['date'] = @spec_dirs.map do |spec_dir|
- File.stat(spec_dir).mtime
- end.max
- end
-
- def uri_encode(str)
- str.gsub(URI::UNSAFE) do |match|
- match.each_byte.map {|c| sprintf('%%%02X', c.ord) }.join
- end
- end
-
- def doc_root(gem_name)
- if have_rdoc_4_plus?
- "/doc_root/#{u gem_name}/"
- else
- "/doc_root/#{u gem_name}/rdoc/index.html"
- end
- end
-
- def have_rdoc_4_plus?
- @have_rdoc_4_plus ||=
- Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version
- end
-
- def latest_specs(req, res)
- reset_gems
-
- res['content-type'] = 'application/x-gzip'
-
- add_date res
-
- latest_specs = Gem::Specification.latest_specs
-
- specs = latest_specs.sort.map do |spec|
- platform = spec.original_platform || Gem::Platform::RUBY
- [spec.name, spec.version, platform]
- end
-
- specs = Marshal.dump specs
-
- if req.path =~ /\.gz$/
- specs = Gem::Util.gzip specs
- res['content-type'] = 'application/x-gzip'
- else
- res['content-type'] = 'application/octet-stream'
- end
-
- if req.request_method == 'HEAD'
- res['content-length'] = specs.length
- else
- res.body << specs
- end
- end
-
- ##
- # Creates server sockets based on the addresses option. If no addresses
- # were given a server socket for all interfaces is created.
-
- def listen(addresses = @addresses)
- addresses = [nil] unless addresses
-
- listeners = 0
-
- addresses.each do |address|
- begin
- @server.listen address, @port
- @server.listeners[listeners..-1].each do |listener|
- host, port = listener.addr.values_at 2, 1
- host = "[#{host}]" if host =~ /:/ # we don't reverse lookup
- say "Server started at http://#{host}:#{port}"
- end
-
- listeners = @server.listeners.length
- rescue SystemCallError
- next
- end
- end
-
- if @server.listeners.empty?
- say "Unable to start a server."
- say "Check for running servers or your --bind and --port arguments"
- terminate_interaction 1
- end
- end
-
- def prerelease_specs(req, res)
- reset_gems
-
- res['content-type'] = 'application/x-gzip'
-
- add_date res
-
- specs = Gem::Specification.select do |spec|
- spec.version.prerelease?
- end.sort.map do |spec|
- platform = spec.original_platform || Gem::Platform::RUBY
- [spec.name, spec.version, platform]
- end
-
- specs = Marshal.dump specs
-
- if req.path =~ /\.gz$/
- specs = Gem::Util.gzip specs
- res['content-type'] = 'application/x-gzip'
- else
- res['content-type'] = 'application/octet-stream'
- end
-
- if req.request_method == 'HEAD'
- res['content-length'] = specs.length
- else
- res.body << specs
- end
- end
-
- def quick(req, res)
- reset_gems
-
- res['content-type'] = 'text/plain'
- add_date res
-
- case req.request_uri.path
- when %r{^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)\.gemspec\.rz$} then
- marshal_format, full_name = $1, $2
- specs = Gem::Specification.find_all_by_full_name(full_name)
-
- selector = full_name.inspect
-
- if specs.empty?
- res.status = 404
- res.body = "No gems found matching #{selector}"
- elsif specs.length > 1
- res.status = 500
- res.body = "Multiple gems found matching #{selector}"
- elsif marshal_format
- res['content-type'] = 'application/x-deflate'
- res.body << Gem.deflate(Marshal.dump(specs.first))
- end
- else
- raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
- end
- end
-
- def root(req, res)
- reset_gems
-
- add_date res
-
- raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
- req.path == '/'
-
- specs = []
- total_file_count = 0
-
- Gem::Specification.each do |spec|
- total_file_count += spec.files.size
- deps = spec.dependencies.map do |dep|
- {
- "name" => dep.name,
- "type" => dep.type,
- "version" => dep.requirement.to_s,
- }
- end
-
- deps = deps.sort_by {|dep| [dep["name"].downcase, dep["version"]] }
- deps.last["is_last"] = true unless deps.empty?
-
- # executables
- executables = spec.executables.sort.collect {|exec| {"executable" => exec} }
- executables = nil if executables.empty?
- executables.last["is_last"] = true if executables
-
- # Pre-process spec homepage for safety reasons
- begin
- homepage_uri = URI.parse(spec.homepage)
- if [URI::HTTP, URI::HTTPS].member? homepage_uri.class
- homepage_uri = spec.homepage
- else
- homepage_uri = "."
- end
- rescue URI::InvalidURIError
- homepage_uri = "."
- end
-
- specs << {
- "authors" => spec.authors.sort.join(", "),
- "date" => spec.date.to_s,
- "dependencies" => deps,
- "doc_path" => doc_root(spec.full_name),
- "executables" => executables,
- "only_one_executable" => (executables && executables.size == 1),
- "full_name" => spec.full_name,
- "has_deps" => !deps.empty?,
- "homepage" => homepage_uri,
- "name" => spec.name,
- "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?,
- "ri_installed" => Gem::RDoc.new(spec).ri_installed?,
- "summary" => spec.summary,
- "version" => spec.version.to_s,
- }
- end
-
- specs << {
- "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
- "dependencies" => [],
- "doc_path" => doc_root("rubygems-#{Gem::VERSION}"),
- "executables" => [{"executable" => 'gem', "is_last" => true}],
- "only_one_executable" => true,
- "full_name" => "rubygems-#{Gem::VERSION}",
- "has_deps" => false,
- "homepage" => "https://guides.rubygems.org/",
- "name" => 'rubygems',
- "ri_installed" => true,
- "summary" => "RubyGems itself",
- "version" => Gem::VERSION,
- }
-
- specs = specs.sort_by {|spec| [spec["name"].downcase, spec["version"]] }
- specs.last["is_last"] = true
-
- # tag all specs with first_name_entry
- last_spec = nil
- specs.each do |spec|
- is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
- spec["first_name_entry"] = is_first
- last_spec = spec
- end
-
- # create page from template
- template = ERB.new(DOC_TEMPLATE)
- res['content-type'] = 'text/html'
-
- values = { "gem_count" => specs.size.to_s, "specs" => specs,
- "total_file_count" => total_file_count.to_s }
-
- # suppress 1.9.3dev warning about unused variable
- values = values
-
- result = template.result binding
- res.body = result
- end
-
- ##
- # Can be used for quick navigation to the rdoc documentation. You can then
- # define a search shortcut for your browser. E.g. in Firefox connect
- # 'shortcut:rdoc' to http://localhost:8808/rdoc?q=%s template. Then you can
- # directly open the ActionPack documentation by typing 'rdoc actionp'. If
- # there are multiple hits for the search term, they are presented as a list
- # with links.
- #
- # Search algorithm aims for an intuitive search:
- # 1. first try to find the gems and documentation folders which name
- # starts with the search term
- # 2. search for entries, that *contain* the search term
- # 3. show all the gems
- #
- # If there is only one search hit, user is immediately redirected to the
- # documentation for the particular gem, otherwise a list with results is
- # shown.
- #
- # === Additional trick - install documentation for Ruby core
- #
- # Note: please adjust paths accordingly use for example 'locate yaml.rb' and
- # 'gem environment' to identify directories, that are specific for your
- # local installation
- #
- # 1. install Ruby sources
- # cd /usr/src
- # sudo apt-get source ruby
- #
- # 2. generate documentation
- # rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc \
- # /usr/lib/ruby/1.8 ruby1.8-1.8.7.72
- #
- # By typing 'rdoc core' you can now access the core documentation
-
- def rdoc(req, res)
- query = req.query['q']
- show_rdoc_for_pattern("#{query}*", res) && return
- show_rdoc_for_pattern("*#{query}*", res) && return
-
- template = ERB.new RDOC_NO_DOCUMENTATION
-
- res['content-type'] = 'text/html'
- res.body = template.result binding
- end
-
- ##
- # Updates the server to use the latest installed gems.
-
- def reset_gems # :nodoc:
- Gem::Specification.dirs = @gem_dirs
- end
-
- ##
- # Returns true and prepares http response, if rdoc for the requested gem
- # name pattern was found.
- #
- # The search is based on the file system content, not on the gems metadata.
- # This allows additional documentation folders like 'core' for the Ruby core
- # documentation - just put it underneath the main doc folder.
-
- def show_rdoc_for_pattern(pattern, res)
- found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select do |path|
- File.exist? File.join(path, 'rdoc/index.html')
- end
- case found_gems.length
- when 0
- return false
- when 1
- new_path = File.basename(found_gems[0])
- res.status = 302
- res['Location'] = doc_root new_path
- return true
- else
- doc_items = []
- found_gems.each do |file_name|
- base_name = File.basename(file_name)
- doc_items << {
- :name => base_name,
- :url => doc_root(new_path),
- :summary => '',
- }
- end
-
- template = ERB.new(RDOC_SEARCH_TEMPLATE)
- res['content-type'] = 'text/html'
- result = template.result binding
- res.body = result
- return true
- end
- end
-
- def run
- listen
-
- WEBrick::Daemon.start if @daemon
-
- @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
- @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
-
- @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
- method(:latest_specs)
- @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
- method(:latest_specs)
-
- @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}",
- method(:prerelease_specs)
- @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}.gz",
- method(:prerelease_specs)
-
- @server.mount_proc "/quick/", method(:quick)
-
- @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
- res['content-type'] = 'text/css'
- add_date res
- res.body << RDOC_CSS
- end
-
- @server.mount_proc "/", method(:root)
-
- @server.mount_proc "/rdoc", method(:rdoc)
-
- file_handlers = {
- '/gems' => '/cache/',
- }
-
- if have_rdoc_4_plus?
- @server.mount '/doc_root', RDoc::Servlet, '/doc_root'
- else
- file_handlers['/doc_root'] = '/doc/'
- end
-
- @gem_dirs.each do |gem_dir|
- file_handlers.each do |mount_point, mount_dir|
- @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
- File.join(gem_dir, mount_dir), true)
- end
- end
-
- trap("INT") { @server.shutdown; exit! }
- trap("TERM") { @server.shutdown; exit! }
-
- launch if @launch
-
- @server.start
- end
-
- def specs(req, res)
- reset_gems
-
- add_date res
-
- specs = Gem::Specification.sort_by(&:sort_obj).map do |spec|
- platform = spec.original_platform || Gem::Platform::RUBY
- [spec.name, spec.version, platform]
- end
-
- specs = Marshal.dump specs
-
- if req.path =~ /\.gz$/
- specs = Gem::Util.gzip specs
- res['content-type'] = 'application/x-gzip'
- else
- res['content-type'] = 'application/octet-stream'
- end
-
- if req.request_method == 'HEAD'
- res['content-length'] = specs.length
- else
- res.body << specs
- end
- end
-
- def launch
- listeners = @server.listeners.map{|l| l.addr[2] }
-
- # TODO: 0.0.0.0 == any, not localhost.
- host = listeners.any?{|l| l == '0.0.0.0' } ? 'localhost' : listeners.first
-
- say "Launching browser to http://#{host}:#{@port}"
-
- system("#{@launch} http://#{host}:#{@port}")
- end
-end
diff --git a/lib/rubygems/shellwords.rb b/lib/rubygems/shellwords.rb
new file mode 100644
index 0000000000..741dccb363
--- /dev/null
+++ b/lib/rubygems/shellwords.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+autoload :Shellwords, "shellwords"
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index 37e03cdfae..d90e311b65 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rubygems/text"
+require_relative "text"
##
# A Source knows how to list and fetch gems from a RubyGems marshal index.
#
@@ -12,9 +12,9 @@ class Gem::Source
include Gem::Text
FILES = { # :nodoc:
- :released => 'specs',
- :latest => 'latest_specs',
- :prerelease => 'prerelease_specs',
+ released: "specs",
+ latest: "latest_specs",
+ prerelease: "prerelease_specs",
}.freeze
##
@@ -26,15 +26,8 @@ class Gem::Source
# Creates a new Source which will use the index located at +uri+.
def initialize(uri)
- begin
- unless uri.kind_of? URI
- uri = URI.parse(uri.to_s)
- end
- rescue URI::InvalidURIError
- raise if Gem::Source == self.class
- end
-
- @uri = uri
+ require_relative "uri"
+ @uri = Gem::Uri.parse!(uri)
@update_cache = nil
end
@@ -51,25 +44,23 @@ class Gem::Source
Gem::Source::Vendor then
-1
when Gem::Source then
- if !@uri
+ unless @uri
return 0 unless other.uri
return 1
end
- return -1 if !other.uri
+ return -1 unless other.uri
# Returning 1 here ensures that when sorting a list of sources, the
# original ordering of sources supplied by the user is preserved.
return 1 unless @uri.to_s == other.uri.to_s
0
- else
- nil
end
end
def ==(other) # :nodoc:
- self.class === other and @uri == other.uri
+ self.class === other && @uri == other.uri
end
alias_method :eql?, :== # :nodoc:
@@ -78,15 +69,15 @@ class Gem::Source
# Returns a Set that can fetch specifications from this source.
def dependency_resolver_set # :nodoc:
- return Gem::Resolver::IndexSet.new self if 'file' == uri.scheme
+ return Gem::Resolver::IndexSet.new self if uri.scheme == "file"
fetch_uri = if uri.host == "rubygems.org"
- index_uri = uri.dup
- index_uri.host = "index.rubygems.org"
- index_uri
- else
- uri
- end
+ index_uri = uri.dup
+ index_uri.host = "index.rubygems.org"
+ index_uri
+ else
+ uri
+ end
bundler_api_uri = enforce_trailing_slash(fetch_uri)
@@ -109,8 +100,7 @@ class Gem::Source
def cache_dir(uri)
# Correct for windows paths
- escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
- escaped_path.tap(&Gem::UNTAINT)
+ escaped_path = uri.path.sub(%r{^/([a-z]):/}i, '/\\1-/')
File.join Gem.spec_cache_dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
end
@@ -144,11 +134,16 @@ class Gem::Source
if File.exist? local_spec
spec = Gem.read_binary local_spec
- spec = Marshal.load(spec) rescue nil
+ Gem.load_safe_marshal
+ spec = begin
+ Gem::SafeMarshal.safe_load(spec)
+ rescue StandardError
+ nil
+ end
return spec if spec
end
- source_uri.path << '.rz'
+ source_uri.path << ".rz"
spec = fetcher.fetch_path source_uri
spec = Gem::Util.inflate spec
@@ -157,13 +152,14 @@ class Gem::Source
require "fileutils"
FileUtils.mkdir_p cache_dir
- File.open local_spec, 'wb' do |io|
+ File.open local_spec, "wb" do |io|
io.write spec
end
end
+ Gem.load_safe_marshal
# TODO: Investigate setting Gem::Specification#loaded_from to a URI
- Marshal.load spec
+ Gem::SafeMarshal.safe_load spec
end
##
@@ -193,8 +189,9 @@ class Gem::Source
spec_dump = fetcher.cache_update_path spec_path, local_file, update_cache?
+ Gem.load_safe_marshal
begin
- Gem::NameTuple.from_list Marshal.load(spec_dump)
+ Gem::NameTuple.from_list Gem::SafeMarshal.safe_load(spec_dump)
rescue ArgumentError
if update_cache? && !retried
FileUtils.rm local_file
@@ -216,13 +213,13 @@ class Gem::Source
end
def pretty_print(q) # :nodoc:
- q.group 2, '[Remote:', ']' do
+ q.group 2, "[Remote:", "]" do
q.breakable
q.text @uri.to_s
if api = uri
q.breakable
- q.text 'API URI: '
+ q.text "API URI: "
q.text api.to_s
end
end
@@ -236,13 +233,13 @@ class Gem::Source
private
def enforce_trailing_slash(uri)
- uri.merge(uri.path.gsub(/\/+$/, '') + '/')
+ uri.merge(uri.path.gsub(%r{/+$}, "") + "/")
end
end
-require 'rubygems/source/git'
-require 'rubygems/source/installed'
-require 'rubygems/source/specific_file'
-require 'rubygems/source/local'
-require 'rubygems/source/lock'
-require 'rubygems/source/vendor'
+require_relative "source/git"
+require_relative "source/installed"
+require_relative "source/specific_file"
+require_relative "source/local"
+require_relative "source/lock"
+require_relative "source/vendor"
diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb
index 9876adc24e..bda63c6844 100644
--- a/lib/rubygems/source/git.rb
+++ b/lib/rubygems/source/git.rb
@@ -49,16 +49,16 @@ class Gem::Source::Git < Gem::Source
# will be checked out when the gem is installed.
def initialize(name, repository, reference, submodules = false)
- super repository
-
+ require_relative "../uri"
+ @uri = Gem::Uri.parse(repository)
@name = name
@repository = repository
- @reference = reference
+ @reference = reference || "HEAD"
@need_submodules = submodules
@remote = true
@root_dir = Gem.dir
- @git = ENV['git'] || 'git'
+ @git = ENV["git"] || "git"
end
def <=>(other)
@@ -70,16 +70,14 @@ class Gem::Source::Git < Gem::Source
-1
when Gem::Source then
1
- else
- nil
end
end
def ==(other) # :nodoc:
- super and
- @name == other.name and
- @repository == other.repository and
- @reference == other.reference and
+ super &&
+ @name == other.name &&
+ @repository == other.repository &&
+ @reference == other.reference &&
@need_submodules == other.need_submodules
end
@@ -92,17 +90,18 @@ class Gem::Source::Git < Gem::Source
return false unless File.exist? repo_cache_dir
unless File.exist? install_dir
- system @git, 'clone', '--quiet', '--no-checkout',
+ system @git, "clone", "--quiet", "--no-checkout",
repo_cache_dir, install_dir
end
Dir.chdir install_dir do
- system @git, 'fetch', '--quiet', '--force', '--tags', install_dir
+ system @git, "fetch", "--quiet", "--force", "--tags", install_dir
- success = system @git, 'reset', '--quiet', '--hard', rev_parse
+ success = system @git, "reset", "--quiet", "--hard", rev_parse
if @need_submodules
- _, status = Open3.capture2e(@git, 'submodule', 'update', '--quiet', '--init', '--recursive')
+ require "open3"
+ _, status = Open3.capture2e(@git, "submodule", "update", "--quiet", "--init", "--recursive")
success &&= status.success?
end
@@ -119,11 +118,11 @@ class Gem::Source::Git < Gem::Source
if File.exist? repo_cache_dir
Dir.chdir repo_cache_dir do
- system @git, 'fetch', '--quiet', '--force', '--tags',
- @repository, 'refs/heads/*:refs/heads/*'
+ system @git, "fetch", "--quiet", "--force", "--tags",
+ @repository, "refs/heads/*:refs/heads/*"
end
else
- system @git, 'clone', '--quiet', '--bare', '--no-hardlinks',
+ system @git, "clone", "--quiet", "--bare", "--no-hardlinks",
@repository, repo_cache_dir
end
end
@@ -132,7 +131,7 @@ class Gem::Source::Git < Gem::Source
# Directory where git gems get unpacked and so-forth.
def base_dir # :nodoc:
- File.join @root_dir, 'bundler'
+ File.join @root_dir, "bundler"
end
##
@@ -154,11 +153,11 @@ class Gem::Source::Git < Gem::Source
def install_dir # :nodoc:
return unless File.exist? repo_cache_dir
- File.join base_dir, 'gems', "#{@name}-#{dir_shortref}"
+ File.join base_dir, "gems", "#{@name}-#{dir_shortref}"
end
def pretty_print(q) # :nodoc:
- q.group 2, '[Git: ', ']' do
+ q.group 2, "[Git: ", "]" do
q.breakable
q.text @repository
@@ -171,7 +170,7 @@ class Gem::Source::Git < Gem::Source
# The directory where the git gem's repository will be cached.
def repo_cache_dir # :nodoc:
- File.join @root_dir, 'cache', 'bundler', 'git', "#{@name}-#{uri_hash}"
+ File.join @root_dir, "cache", "bundler", "git", "#{@name}-#{uri_hash}"
end
##
@@ -181,7 +180,7 @@ class Gem::Source::Git < Gem::Source
hash = nil
Dir.chdir repo_cache_dir do
- hash = Gem::Util.popen(@git, 'rev-parse', @reference).strip
+ hash = Gem::Util.popen(@git, "rev-parse", @reference).strip
end
raise Gem::Exception,
@@ -200,7 +199,7 @@ class Gem::Source::Git < Gem::Source
return [] unless install_dir
Dir.chdir install_dir do
- Dir['{,*,*/*}.gemspec'].map do |spec_file|
+ Dir["{,*,*/*}.gemspec"].map do |spec_file|
directory = File.dirname spec_file
file = File.basename spec_file
@@ -210,7 +209,7 @@ class Gem::Source::Git < Gem::Source
spec.base_dir = base_dir
spec.extension_dir =
- File.join base_dir, 'extensions', Gem::Platform.local.to_s,
+ File.join base_dir, "extensions", Gem::Platform.local.to_s,
Gem.extension_api_version, "#{name}-#{dir_shortref}"
spec.full_gem_path = File.dirname spec.loaded_from if spec
@@ -222,19 +221,19 @@ class Gem::Source::Git < Gem::Source
end
##
- # A hash for the git gem based on the git repository URI.
+ # A hash for the git gem based on the git repository Gem::URI.
def uri_hash # :nodoc:
- require 'digest' # required here to avoid deadlocking in Gem.activate_bin_path (because digest is a gem on 2.5+)
+ require_relative "../openssl"
normalized =
- if @repository =~ %r{^\w+://(\w+@)?}
- uri = URI(@repository).normalize.to_s.sub %r{/$},''
+ if @repository.match?(%r{^\w+://(\w+@)?})
+ uri = Gem::URI(@repository).normalize.to_s.sub %r{/$},""
uri.sub(/\A(\w+)/) { $1.downcase }
else
@repository
end
- Digest::SHA1.hexdigest normalized
+ OpenSSL::Digest::SHA1.hexdigest normalized
end
end
diff --git a/lib/rubygems/source/installed.rb b/lib/rubygems/source/installed.rb
index 7e1dd7af5a..cbe12a0516 100644
--- a/lib/rubygems/source/installed.rb
+++ b/lib/rubygems/source/installed.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Represents an installed gem. This is used for dependency resolution.
@@ -20,8 +21,6 @@ class Gem::Source::Installed < Gem::Source
0
when Gem::Source then
1
- else
- nil
end
end
@@ -33,6 +32,6 @@ class Gem::Source::Installed < Gem::Source
end
def pretty_print(q) # :nodoc:
- q.text '[Installed]'
+ q.text "[Installed]"
end
end
diff --git a/lib/rubygems/source/local.rb b/lib/rubygems/source/local.rb
index 078b06203f..d81d8343a8 100644
--- a/lib/rubygems/source/local.rb
+++ b/lib/rubygems/source/local.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# The local source finds gems in the current directory for fulfilling
# dependencies.
@@ -23,14 +24,12 @@ class Gem::Source::Local < Gem::Source
0
when Gem::Source then
1
- else
- nil
end
end
def inspect # :nodoc:
- keys = @specs ? @specs.keys.sort : 'NOT LOADED'
- "#<%s specs: %p>" % [self.class, keys]
+ keys = @specs ? @specs.keys.sort : "NOT LOADED"
+ format("#<%s specs: %p>", self.class, keys)
end
def load_specs(type) # :nodoc:
@@ -40,36 +39,35 @@ class Gem::Source::Local < Gem::Source
@specs = {}
Dir["*.gem"].each do |file|
- begin
- pkg = Gem::Package.new(file)
- rescue SystemCallError, Gem::Package::FormatError
- # ignore
- else
- tup = pkg.spec.name_tuple
- @specs[tup] = [File.expand_path(file), pkg]
-
- case type
- when :released
- unless pkg.spec.version.prerelease?
- names << pkg.spec.name_tuple
- end
- when :prerelease
- if pkg.spec.version.prerelease?
- names << pkg.spec.name_tuple
- end
- when :latest
- tup = pkg.spec.name_tuple
-
- cur = names.find {|x| x.name == tup.name }
- if !cur
- names << tup
- elsif cur.version < tup.version
- names.delete cur
- names << tup
- end
- else
+ pkg = Gem::Package.new(file)
+ spec = pkg.spec
+ rescue SystemCallError, Gem::Package::FormatError
+ # ignore
+ else
+ tup = spec.name_tuple
+ @specs[tup] = [File.expand_path(file), pkg]
+
+ case type
+ when :released
+ unless pkg.spec.version.prerelease?
+ names << pkg.spec.name_tuple
+ end
+ when :prerelease
+ if pkg.spec.version.prerelease?
names << pkg.spec.name_tuple
end
+ when :latest
+ tup = pkg.spec.name_tuple
+
+ cur = names.find {|x| x.name == tup.name }
+ if !cur
+ names << tup
+ elsif cur.version < tup.version
+ names.delete cur
+ names << tup
+ end
+ else
+ names << pkg.spec.name_tuple
end
end
@@ -77,27 +75,25 @@ class Gem::Source::Local < Gem::Source
end
end
- def find_gem(gem_name, version = Gem::Requirement.default, # :nodoc:
- prerelease = false)
+ def find_gem(gem_name, version = Gem::Requirement.default, prerelease = false) # :nodoc:
load_specs :complete
found = []
@specs.each do |n, data|
- if n.name == gem_name
- s = data[1].spec
-
- if version.satisfied_by?(s.version)
- if prerelease
- found << s
- elsif !s.version.prerelease? || version.prerelease?
- found << s
- end
+ next unless n.name == gem_name
+ s = data[1].spec
+
+ if version.satisfied_by?(s.version)
+ if prerelease
+ found << s
+ elsif !s.version.prerelease? || version.prerelease?
+ found << s
end
end
end
- found.max_by {|s| s.version }
+ found.max_by(&:version)
end
def fetch_spec(name) # :nodoc:
@@ -113,7 +109,7 @@ class Gem::Source::Local < Gem::Source
def download(spec, cache_dir = nil) # :nodoc:
load_specs :complete
- @specs.each do |name, data|
+ @specs.each do |_name, data|
return data[0] if data[1].spec == spec
end
@@ -121,7 +117,7 @@ class Gem::Source::Local < Gem::Source
end
def pretty_print(q) # :nodoc:
- q.group 2, '[Local gems:', ']' do
+ q.group 2, "[Local gems:", "]" do
q.breakable
q.seplist @specs.keys do |v|
q.text v.full_name
diff --git a/lib/rubygems/source/lock.rb b/lib/rubygems/source/lock.rb
index 49f097467b..70849210bd 100644
--- a/lib/rubygems/source/lock.rb
+++ b/lib/rubygems/source/lock.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A Lock source wraps an installed gem's source and sorts before other sources
# during dependency resolution. This allows RubyGems to prefer gems from
@@ -24,13 +25,11 @@ class Gem::Source::Lock < Gem::Source
@wrapped <=> other.wrapped
when Gem::Source then
1
- else
- nil
end
end
def ==(other) # :nodoc:
- 0 == (self <=> other)
+ (self <=> other) == 0
end
def hash # :nodoc:
diff --git a/lib/rubygems/source/specific_file.rb b/lib/rubygems/source/specific_file.rb
index 24db1440dd..e9b2753646 100644
--- a/lib/rubygems/source/specific_file.rb
+++ b/lib/rubygems/source/specific_file.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# A source representing a single .gem file. This is used for installation of
# local gems.
@@ -33,7 +34,6 @@ class Gem::Source::SpecificFile < Gem::Source
def fetch_spec(name) # :nodoc:
return @spec if name == @name
raise Gem::Exception, "Unable to find '#{name}'"
- @spec
end
def download(spec, dir = nil) # :nodoc:
@@ -42,7 +42,7 @@ class Gem::Source::SpecificFile < Gem::Source
end
def pretty_print(q) # :nodoc:
- q.group 2, '[SpecificFile:', ']' do
+ q.group 2, "[SpecificFile:", "]" do
q.breakable
q.text @path
end
diff --git a/lib/rubygems/source/vendor.rb b/lib/rubygems/source/vendor.rb
index 543acf1388..44ef614441 100644
--- a/lib/rubygems/source/vendor.rb
+++ b/lib/rubygems/source/vendor.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# This represents a vendored source that is similar to an installed gem.
@@ -18,8 +19,6 @@ class Gem::Source::Vendor < Gem::Source::Installed
0
when Gem::Source then
1
- else
- nil
end
end
end
diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb
index 13b25b63dc..33db64fbc1 100644
--- a/lib/rubygems/source_list.rb
+++ b/lib/rubygems/source_list.rb
@@ -36,7 +36,7 @@ class Gem::SourceList
list.replace ary
- return list
+ list
end
def initialize_copy(other) # :nodoc:
@@ -44,20 +44,16 @@ class Gem::SourceList
end
##
- # Appends +obj+ to the source list which may be a Gem::Source, URI or URI
+ # Appends +obj+ to the source list which may be a Gem::Source, Gem::URI or URI
# String.
def <<(obj)
- require "uri"
-
src = case obj
- when URI
- Gem::Source.new(obj)
when Gem::Source
obj
else
- Gem::Source.new(URI.parse(obj))
- end
+ Gem::Source.new(obj)
+ end
@sources << src unless @sources.include?(src)
src
@@ -130,7 +126,7 @@ class Gem::SourceList
# Gem::Source or a source URI.
def include?(other)
- if other.kind_of? Gem::Source
+ if other.is_a? Gem::Source
@sources.include? other
else
@sources.find {|x| x.uri.to_s == other.to_s }
@@ -141,7 +137,7 @@ class Gem::SourceList
# Deletes +source+ from the source list which may be a Gem::Source or a URI.
def delete(source)
- if source.kind_of? Gem::Source
+ if source.is_a? Gem::Source
@sources.delete source
else
@sources.delete_if {|x| x.uri.to_s == source.to_s }
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index b2bcadc49c..610edf25c9 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
-require 'rubygems/remote_fetcher'
-require 'rubygems/user_interaction'
-require 'rubygems/errors'
-require 'rubygems/text'
-require 'rubygems/name_tuple'
+
+require_relative "remote_fetcher"
+require_relative "user_interaction"
+require_relative "errors"
+require_relative "text"
+require_relative "name_tuple"
##
# SpecFetcher handles metadata updates from remote gem repositories.
@@ -68,9 +69,9 @@ class Gem::SpecFetcher
@prerelease_specs = {}
@caches = {
- :latest => @latest_specs,
- :prerelease => @prerelease_specs,
- :released => @specs,
+ latest: @latest_specs,
+ prerelease: @prerelease_specs,
+ released: @specs,
}
@fetcher = Gem::RemoteFetcher.fetcher
@@ -91,14 +92,14 @@ class Gem::SpecFetcher
list.each do |source, specs|
if dependency.name.is_a?(String) && specs.respond_to?(:bsearch)
- start_index = (0 ... specs.length).bsearch{|i| specs[i].name >= dependency.name }
- end_index = (0 ... specs.length).bsearch{|i| specs[i].name > dependency.name }
- specs = specs[start_index ... end_index] if start_index && end_index
+ start_index = (0...specs.length).bsearch {|i| specs[i].name >= dependency.name }
+ end_index = (0...specs.length).bsearch {|i| specs[i].name > dependency.name }
+ specs = specs[start_index...end_index] if start_index && end_index
end
found[source] = specs.select do |tup|
if dependency.match?(tup)
- if matching_platform and !Gem::Platform.match_gem?(tup.platform, tup.name)
+ if matching_platform && !Gem::Platform.match_gem?(tup.platform, tup.name)
pm = (
rejected_specs[dependency] ||= \
Gem::PlatformMismatch.new(tup.name, tup.version))
@@ -121,9 +122,9 @@ class Gem::SpecFetcher
end
end
- tuples = tuples.sort_by {|x| x[0] }
+ tuples = tuples.sort_by {|x| x[0].version }
- return [tuples, errors]
+ [tuples, errors]
end
##
@@ -154,16 +155,14 @@ class Gem::SpecFetcher
specs = []
tuples.each do |tup, source|
- begin
- spec = source.fetch_spec(tup)
- rescue Gem::RemoteFetcher::FetchError => e
- errors << Gem::SourceFetchProblem.new(source, e)
- else
- specs << [spec, source]
- end
+ spec = source.fetch_spec(tup)
+ rescue Gem::RemoteFetcher::FetchError => e
+ errors << Gem::SourceFetchProblem.new(source, e)
+ else
+ specs << [spec, source]
end
- return [specs, errors]
+ [specs, errors]
end
##
@@ -171,19 +170,19 @@ class Gem::SpecFetcher
# alternative gem names.
def suggest_gems_from_name(gem_name, type = :latest, num_results = 5)
- gem_name = gem_name.downcase.tr('_-', '')
+ gem_name = gem_name.downcase.tr("_-", "")
max = gem_name.size / 2
names = available_specs(type).first.values.flatten(1)
matches = names.map do |n|
next unless n.match_platform?
- [n.name, 0] if n.name.downcase.tr('_-', '').include?(gem_name)
+ [n.name, 0] if n.name.downcase.tr("_-", "").include?(gem_name)
end.compact
if matches.length < num_results
matches += names.map do |n|
next unless n.match_platform?
- distance = levenshtein_distance gem_name, n.name.downcase.tr('_-', '')
+ distance = levenshtein_distance gem_name, n.name.downcase.tr("_-", "")
next if distance >= max
return [n.name] if distance == 0
[n.name, distance]
@@ -191,12 +190,12 @@ class Gem::SpecFetcher
end
matches = if matches.empty? && type != :prerelease
- suggest_gems_from_name gem_name, :prerelease
- else
- matches.uniq.sort_by {|name, dist| dist }
- end
+ suggest_gems_from_name gem_name, :prerelease
+ else
+ matches.uniq.sort_by {|_name, dist| dist }
+ end
- matches.map {|name, dist| name }.uniq.first(num_results)
+ matches.map {|name, _dist| name }.uniq.first(num_results)
end
##
@@ -214,34 +213,32 @@ class Gem::SpecFetcher
list = {}
@sources.each_source do |source|
- begin
- names = case type
- when :latest
- tuples_for source, :latest
- when :released
- tuples_for source, :released
- when :complete
- names =
- tuples_for(source, :prerelease, true) +
- tuples_for(source, :released)
-
- names.sort
- when :abs_latest
- names =
- tuples_for(source, :prerelease, true) +
- tuples_for(source, :latest)
-
- names.sort
- when :prerelease
- tuples_for(source, :prerelease)
- else
- raise Gem::Exception, "Unknown type - :#{type}"
- end
- rescue Gem::RemoteFetcher::FetchError => e
- errors << Gem::SourceFetchProblem.new(source, e)
- else
- list[source] = names
+ names = case type
+ when :latest
+ tuples_for source, :latest
+ when :released
+ tuples_for source, :released
+ when :complete
+ names =
+ tuples_for(source, :prerelease, true) +
+ tuples_for(source, :released)
+
+ names.sort
+ when :abs_latest
+ names =
+ tuples_for(source, :prerelease, true) +
+ tuples_for(source, :latest)
+
+ names.sort
+ when :prerelease
+ tuples_for(source, :prerelease)
+ else
+ raise Gem::Exception, "Unknown type - :#{type}"
end
+ rescue Gem::RemoteFetcher::FetchError => e
+ errors << Gem::SourceFetchProblem.new(source, e)
+ else
+ list[source] = names
end
[list, errors]
@@ -253,7 +250,7 @@ class Gem::SpecFetcher
def tuples_for(source, type, gracefully_ignore=false) # :nodoc:
@caches[type][source.uri] ||=
- source.load_specs(type).sort_by {|tup| tup.name }
+ source.load_specs(type).sort_by(&:name)
rescue Gem::RemoteFetcher::FetchError
raise unless gracefully_ignore
[]
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 7206c3eaf0..29139cf725 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -1,16 +1,19 @@
# frozen_string_literal: true
-# -*- coding: utf-8 -*-
+
+#
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/deprecate'
-require 'rubygems/basic_specification'
-require 'rubygems/stub_specification'
-require 'rubygems/specification_policy'
-require 'rubygems/util/list'
+require_relative "deprecate"
+require_relative "basic_specification"
+require_relative "stub_specification"
+require_relative "platform"
+require_relative "util/list"
+
+require "rbconfig"
##
# The Specification class contains the information for a gem. Typically
@@ -74,42 +77,38 @@ class Gem::Specification < Gem::BasicSpecification
# key should be equal to the CURRENT_SPECIFICATION_VERSION.
SPECIFICATION_VERSION_HISTORY = { # :nodoc:
- -1 => ['(RubyGems versions up to and including 0.7 did not have versioned specifications)'],
- 1 => [
+ -1 => ["(RubyGems versions up to and including 0.7 did not have versioned specifications)"],
+ 1 => [
'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"',
'"test_file=x" is a shortcut for "test_files=[x]"',
],
2 => [
'Added "required_rubygems_version"',
- 'Now forward-compatible with future versions',
+ "Now forward-compatible with future versions",
],
3 => [
- 'Added Fixnum validation to the specification_version',
+ "Added Fixnum validation to the specification_version",
],
4 => [
- 'Added sandboxed freeform metadata to the specification version.',
+ "Added sandboxed freeform metadata to the specification version.",
],
}.freeze
MARSHAL_FIELDS = { # :nodoc:
-1 => 16,
- 1 => 16,
- 2 => 16,
- 3 => 17,
- 4 => 18,
+ 1 => 16,
+ 2 => 16,
+ 3 => 17,
+ 4 => 18,
}.freeze
today = Time.now.utc
TODAY = Time.utc(today.year, today.month, today.day) # :nodoc:
- # rubocop:disable Style/MutableConstant
- LOAD_CACHE = {} # :nodoc:
- # rubocop:enable Style/MutableConstant
- LOAD_CACHE_MUTEX = Mutex.new
+ @load_cache = {} # :nodoc:
+ @load_cache_mutex = Thread::Mutex.new
- private_constant :LOAD_CACHE if defined? private_constant
-
- VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/.freeze # :nodoc:
+ VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc:
# :startdoc:
@@ -128,39 +127,39 @@ class Gem::Specification < Gem::BasicSpecification
# Map of attribute names to default values.
@@default_value = {
- :authors => [],
- :autorequire => nil,
- :bindir => 'bin',
- :cert_chain => [],
- :date => nil,
- :dependencies => [],
- :description => nil,
- :email => nil,
- :executables => [],
- :extensions => [],
- :extra_rdoc_files => [],
- :files => [],
- :homepage => nil,
- :licenses => [],
- :metadata => {},
- :name => nil,
- :platform => Gem::Platform::RUBY,
- :post_install_message => nil,
- :rdoc_options => [],
- :require_paths => ['lib'],
- :required_ruby_version => Gem::Requirement.default,
- :required_rubygems_version => Gem::Requirement.default,
- :requirements => [],
- :rubygems_version => Gem::VERSION,
- :signing_key => nil,
- :specification_version => CURRENT_SPECIFICATION_VERSION,
- :summary => nil,
- :test_files => [],
- :version => nil,
+ authors: [],
+ autorequire: nil,
+ bindir: "bin",
+ cert_chain: [],
+ date: nil,
+ dependencies: [],
+ description: nil,
+ email: nil,
+ executables: [],
+ extensions: [],
+ extra_rdoc_files: [],
+ files: [],
+ homepage: nil,
+ licenses: [],
+ metadata: {},
+ name: nil,
+ platform: Gem::Platform::RUBY,
+ post_install_message: nil,
+ rdoc_options: [],
+ require_paths: ["lib"],
+ required_ruby_version: Gem::Requirement.default,
+ required_rubygems_version: Gem::Requirement.default,
+ requirements: [],
+ rubygems_version: Gem::VERSION,
+ signing_key: nil,
+ specification_version: CURRENT_SPECIFICATION_VERSION,
+ summary: nil,
+ test_files: [],
+ version: nil,
}.freeze
# rubocop:disable Style/MutableConstant
- INITIALIZE_CODE_FOR_DEFAULTS = { } # :nodoc:
+ INITIALIZE_CODE_FOR_DEFAULTS = {} # :nodoc:
# rubocop:enable Style/MutableConstant
@@default_value.each do |k,v|
@@ -169,26 +168,30 @@ class Gem::Specification < Gem::BasicSpecification
v.inspect
when String
v.dump
- when Numeric
- "default_value(:#{k})"
else
"default_value(:#{k}).dup"
- end
+ end
end
- @@attributes = @@default_value.keys.sort_by {|s| s.to_s }
- @@array_attributes = @@default_value.reject {|k,v| v != [] }.keys
+ @@attributes = @@default_value.keys.sort_by(&:to_s)
+ @@array_attributes = @@default_value.reject {|_k,v| v != [] }.keys
@@nil_attributes, @@non_nil_attributes = @@default_value.keys.partition do |k|
@@default_value[k].nil?
end
- @@stubs = nil
- @@stubs_by_name = {}
+ def self.clear_specs # :nodoc:
+ @@all = nil
+ @@stubs = nil
+ @@stubs_by_name = {}
+ @@spec_with_requirable_file = {}
+ @@active_stub_with_requirable_file = {}
+ end
+ private_class_method :clear_specs
+
+ clear_specs
# Sentinel object to represent "not found" stubs
NOT_FOUND = Struct.new(:to_spec, :this).new # :nodoc:
- @@spec_with_requirable_file = {}
- @@active_stub_with_requirable_file = {}
# Tracking removed method calls to warn users during build time.
REMOVED_METHODS = [:rubyforge_project=].freeze # :nodoc:
@@ -222,7 +225,7 @@ class Gem::Specification < Gem::BasicSpecification
attr_reader :version
##
- # A short summary of this gem's description. Displayed in `gem list -d`.
+ # A short summary of this gem's description. Displayed in <tt>gem list -d</tt>.
#
# The #description should be more detailed than the summary.
#
@@ -260,15 +263,14 @@ class Gem::Specification < Gem::BasicSpecification
@test_files,
add_bindir(@executables),
@extra_rdoc_files,
- @extensions,
- ].flatten.compact.uniq.sort
+ @extensions].flatten.compact.uniq.sort
end
##
# A list of authors for this gem.
#
# Alternatively, a single author can be specified by assigning a string to
- # `spec.author`
+ # +spec.author+
#
# Usage:
#
@@ -282,6 +284,15 @@ class Gem::Specification < Gem::BasicSpecification
# :section: Recommended gemspec attributes
##
+ # The version of Ruby required by this gem
+ #
+ # Usage:
+ #
+ # spec.required_ruby_version = '>= 2.7.0'
+
+ attr_reader :required_ruby_version
+
+ ##
# A long description of this gem
#
# The description should be more detailed than the summary but not
@@ -290,7 +301,7 @@ class Gem::Specification < Gem::BasicSpecification
#
# Usage:
#
- # spec.description = <<-EOF
+ # spec.description = <<~EOF
# Rake is a Make-like program implemented in Ruby. Tasks and
# dependencies are specified in standard Ruby syntax.
# EOF
@@ -324,17 +335,21 @@ class Gem::Specification < Gem::BasicSpecification
# This should just be the name of your license. The full text of the license
# should be inside of the gem (at the top level) when you build it.
#
- # The simplest way, is to specify the standard SPDX ID
+ # The simplest way is to specify the standard SPDX ID
# https://spdx.org/licenses/ for the license.
- # Ideally you should pick one that is OSI (Open Source Initiative)
- # http://opensource.org/licenses/alphabetical approved.
+ # Ideally, you should pick one that is OSI (Open Source Initiative)
+ # https://opensource.org/licenses/ approved.
+ #
+ # The most commonly used OSI-approved licenses are MIT and Apache-2.0.
+ # GitHub also provides a license picker at https://choosealicense.com/.
#
- # The most commonly used OSI approved licenses are MIT and Apache-2.0.
- # GitHub also provides a license picker at http://choosealicense.com/.
+ # You can also use a custom license file along with your gemspec and specify
+ # a LicenseRef-<idstring>, where idstring is the name of the file containing
+ # the license text.
#
# You should specify a license for your gem so that people know how they are
- # permitted to use it, and any restrictions you're placing on it. Not
- # specifying a license means all rights are reserved; others have no rights
+ # permitted to use it and any restrictions you're placing on it. Not
+ # specifying a license means all rights are reserved; others have no right
# to use the code for any purpose.
#
# You can set multiple licenses with #licenses=
@@ -411,11 +426,11 @@ class Gem::Specification < Gem::BasicSpecification
end
##
- # The path in the gem for executable scripts. Usually 'bin'
+ # The path in the gem for executable scripts. Usually 'exe'
#
# Usage:
#
- # spec.bindir = 'bin'
+ # spec.bindir = 'exe'
attr_accessor :bindir
@@ -458,7 +473,7 @@ class Gem::Specification < Gem::BasicSpecification
# spec.platform = Gem::Platform.local
def platform=(platform)
- if @original_platform.nil? or
+ if @original_platform.nil? ||
@original_platform == Gem::Platform::RUBY
@original_platform = platform
end
@@ -474,12 +489,12 @@ class Gem::Specification < Gem::BasicSpecification
# legacy constants
when nil, Gem::Platform::RUBY then
@new_platform = Gem::Platform::RUBY
- when 'mswin32' then # was Gem::Platform::WIN32
- @new_platform = Gem::Platform.new 'x86-mswin32'
- when 'i586-linux' then # was Gem::Platform::LINUX_586
- @new_platform = Gem::Platform.new 'x86-linux'
- when 'powerpc-darwin' then # was Gem::Platform::DARWIN
- @new_platform = Gem::Platform.new 'ppc-darwin'
+ when "mswin32" then # was Gem::Platform::WIN32
+ @new_platform = Gem::Platform.new "x86-mswin32"
+ when "i586-linux" then # was Gem::Platform::LINUX_586
+ @new_platform = Gem::Platform.new "x86-linux"
+ when "powerpc-darwin" then # was Gem::Platform::DARWIN
+ @new_platform = Gem::Platform.new "ppc-darwin"
else
@new_platform = Gem::Platform.new platform
end
@@ -487,8 +502,6 @@ class Gem::Specification < Gem::BasicSpecification
@platform = @new_platform.to_s
invalidate_memoized_attributes
-
- @new_platform
end
##
@@ -513,23 +526,11 @@ class Gem::Specification < Gem::BasicSpecification
end
##
- # The version of Ruby required by this gem
-
- attr_reader :required_ruby_version
-
- ##
# The RubyGems version required by this gem
attr_reader :required_rubygems_version
##
- # The version of RubyGems used to create this gem.
- #
- # Do not set this, it is set automatically when the gem is packaged.
-
- attr_accessor :rubygems_version
-
- ##
# The key used to sign this gem. See Gem::Security for details.
attr_accessor :signing_key
@@ -567,7 +568,7 @@ class Gem::Specification < Gem::BasicSpecification
##
# Executables included in the gem.
#
- # For example, the rake gem has rake as an executable. You don’t specify the
+ # For example, the rake gem has rake as an executable. You don't specify the
# full path (as in bin/rake); all application-style files are expected to be
# found in bindir. These files must be executable Ruby files. Files that
# use bash or other interpreters will not work.
@@ -588,7 +589,7 @@ class Gem::Specification < Gem::BasicSpecification
# extconf.rb-style files used to compile extensions.
#
# These files will be run when the gem is installed, causing the C (or
- # whatever) code to be compiled on the user’s machine.
+ # whatever) code to be compiled on the user's machine.
#
# Usage:
#
@@ -646,6 +647,8 @@ class Gem::Specification < Gem::BasicSpecification
@rdoc_options ||= []
end
+ LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1")
+
##
# The version of Ruby required by this gem. The ruby version can be
# specified to the patch-level:
@@ -672,6 +675,14 @@ class Gem::Specification < Gem::BasicSpecification
def required_ruby_version=(req)
@required_ruby_version = Gem::Requirement.create req
+
+ @required_ruby_version.requirements.map! do |op, v|
+ if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4
+ [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))]
+ else
+ [op, v]
+ end
+ end
end
##
@@ -707,6 +718,21 @@ class Gem::Specification < Gem::BasicSpecification
end
######################################################################
+ # :section: Read-only attributes
+
+ ##
+ # The version of RubyGems used to create this gem.
+
+ attr_accessor :rubygems_version
+
+ ##
+ # The path where this gem installs its extensions.
+
+ def extensions_dir
+ @extensions_dir ||= super
+ end
+
+ ######################################################################
# :section: Specification internals
##
@@ -714,7 +740,7 @@ class Gem::Specification < Gem::BasicSpecification
attr_accessor :activated
- alias :activated? :activated
+ alias_method :activated?, :activated
##
# Autorequire was used by old RubyGems to automatically require a file.
@@ -744,28 +770,20 @@ class Gem::Specification < Gem::BasicSpecification
attr_accessor :specification_version
def self._all # :nodoc:
- unless defined?(@@all) && @@all
- @@all = stubs.map(&:to_spec)
-
- # After a reset, make sure already loaded specs
- # are still marked as activated.
- specs = {}
- Gem.loaded_specs.each_value{|s| specs[s] = true }
- @@all.each{|s| s.activated = true if specs[s] }
- end
- @@all
+ @@all ||= Gem.loaded_specs.values | stubs.map(&:to_spec)
end
- def self._clear_load_cache # :nodoc:
- LOAD_CACHE_MUTEX.synchronize do
- LOAD_CACHE.clear
+ def self.clear_load_cache # :nodoc:
+ @load_cache_mutex.synchronize do
+ @load_cache.clear
end
end
+ private_class_method :clear_load_cache
def self.each_gemspec(dirs) # :nodoc:
dirs.each do |dir|
Gem::Util.glob_files_in_dir("*.gemspec", dir).each do |path|
- yield path.tap(&Gem::UNTAINT)
+ yield path
end
end
end
@@ -844,7 +862,7 @@ class Gem::Specification < Gem::BasicSpecification
installed_stubs = installed_stubs(Gem::Specification.dirs, pattern)
installed_stubs.select! {|s| Gem::Platform.match_spec? s } if match_platform
stubs = installed_stubs + default_stubs(pattern)
- stubs = stubs.uniq {|stub| stub.full_name }
+ stubs = stubs.uniq(&:full_name)
_resort!(stubs)
stubs
end
@@ -855,7 +873,7 @@ class Gem::Specification < Gem::BasicSpecification
next names if names.nonzero?
versions = b.version <=> a.version
next versions if versions.nonzero?
- b.platform == Gem::Platform::RUBY ? -1 : 1
+ Gem::Platform.sort_priority(b.platform)
end
end
@@ -871,6 +889,30 @@ class Gem::Specification < Gem::BasicSpecification
end
##
+ # Adds +spec+ to the known specifications, keeping the collection
+ # properly sorted.
+
+ def self.add_spec(spec)
+ return if _all.include? spec
+
+ _all << spec
+ stubs << spec
+ (@@stubs_by_name[spec.name] ||= []) << spec
+
+ _resort!(@@stubs_by_name[spec.name])
+ _resort!(stubs)
+ end
+
+ ##
+ # Removes +spec+ from the known specs.
+
+ def self.remove_spec(spec)
+ _all.delete spec.to_spec
+ stubs.delete spec
+ (@@stubs_by_name[spec.name] || []).delete spec
+ end
+
+ ##
# Returns all specifications. This method is discouraged from use.
# You probably want to use one of the Enumerable methods instead.
@@ -901,7 +943,7 @@ class Gem::Specification < Gem::BasicSpecification
# Return full names of all specs in sorted order.
def self.all_names
- self._all.map(&:full_name)
+ _all.map(&:full_name)
end
##
@@ -927,7 +969,7 @@ class Gem::Specification < Gem::BasicSpecification
def self.dirs
@@dirs ||= Gem.path.collect do |dir|
- File.join dir.dup.tap(&Gem::UNTAINT), "specifications"
+ File.join dir, "specifications"
end
end
@@ -936,7 +978,7 @@ class Gem::Specification < Gem::BasicSpecification
# this resets the list of known specs.
def self.dirs=(dirs)
- self.reset
+ reset
@@dirs = Array(dirs).map {|dir| File.join dir, "specifications" }
end
@@ -950,7 +992,7 @@ class Gem::Specification < Gem::BasicSpecification
def self.each
return enum_for(:each) unless block_given?
- self._all.each do |x|
+ _all.each do |x|
yield x
end
end
@@ -961,8 +1003,6 @@ class Gem::Specification < Gem::BasicSpecification
def self.find_all_by_name(name, *requirements)
requirements = Gem::Requirement.default if requirements.empty?
- # TODO: maybe try: find_all { |s| spec === dep }
-
Gem::Dependency.new(name, *requirements).matching_specs
end
@@ -980,20 +1020,24 @@ class Gem::Specification < Gem::BasicSpecification
def self.find_by_name(name, *requirements)
requirements = Gem::Requirement.default if requirements.empty?
- # TODO: maybe try: find { |s| spec === dep }
-
Gem::Dependency.new(name, *requirements).to_spec
end
##
+ # Find the best specification matching a +full_name+.
+ def self.find_by_full_name(full_name)
+ stubs.find {|s| s.full_name == full_name }&.to_spec
+ end
+
+ ##
# Return the best specification that contains the file matching +path+.
def self.find_by_path(path)
path = path.dup.freeze
- spec = @@spec_with_requirable_file[path] ||= (stubs.find do |s|
- next unless Gem::BundlerVersionFinder.compatible?(s)
+ spec = @@spec_with_requirable_file[path] ||= stubs.find do |s|
s.contains_requirable_file? path
- end || NOT_FOUND)
+ end || NOT_FOUND
+
spec.to_spec
end
@@ -1004,16 +1048,16 @@ class Gem::Specification < Gem::BasicSpecification
def self.find_inactive_by_path(path)
stub = stubs.find do |s|
next if s.activated?
- next unless Gem::BundlerVersionFinder.compatible?(s)
s.contains_requirable_file? path
end
- stub && stub.to_spec
+ stub&.to_spec
end
def self.find_active_stub_by_path(path)
- stub = @@active_stub_with_requirable_file[path] ||= (stubs.find do |s|
- s.activated? and s.contains_requirable_file? path
- end || NOT_FOUND)
+ stub = @@active_stub_with_requirable_file[path] ||= stubs.find do |s|
+ s.activated? && s.contains_requirable_file?(path)
+ end || NOT_FOUND
+
stub.this
end
@@ -1030,7 +1074,7 @@ class Gem::Specification < Gem::BasicSpecification
def self.find_in_unresolved_tree(path)
unresolved_specs.each do |spec|
- spec.traverse do |from_spec, dep, to_spec, trail|
+ spec.traverse do |_from_spec, _dep, to_spec, trail|
if to_spec.has_conflicts? || to_spec.conficts_when_loaded_with?(trail)
:next
else
@@ -1043,7 +1087,7 @@ class Gem::Specification < Gem::BasicSpecification
end
def self.unresolved_specs
- unresolved_deps.values.map {|dep| dep.to_specs }.flatten
+ unresolved_deps.values.map(&:to_specs).flatten
end
private_class_method :unresolved_specs
@@ -1071,6 +1115,7 @@ class Gem::Specification < Gem::BasicSpecification
spec.specification_version ||= NONEXISTENT_SPECIFICATION_VERSION
spec.reset_nil_attributes_to_default
+ spec.flatten_require_paths
spec
end
@@ -1080,7 +1125,7 @@ class Gem::Specification < Gem::BasicSpecification
# +prerelease+ is true.
def self.latest_specs(prerelease = false)
- _latest_specs Gem::Specification._all, prerelease
+ _latest_specs Gem::Specification.stubs, prerelease
end
##
@@ -1094,12 +1139,14 @@ class Gem::Specification < Gem::BasicSpecification
result = {}
specs.reverse_each do |spec|
- next if spec.version.prerelease? unless prerelease
+ unless prerelease
+ next if spec.version.prerelease?
+ end
result[spec.name] = spec
end
- result.map(&:last).flatten.sort_by{|tup| tup.name }
+ result.map(&:last).flatten.sort_by(&:name)
end
##
@@ -1108,36 +1155,33 @@ class Gem::Specification < Gem::BasicSpecification
def self.load(file)
return unless file
- _spec = LOAD_CACHE_MUTEX.synchronize { LOAD_CACHE[file] }
- return _spec if _spec
+ spec = @load_cache_mutex.synchronize { @load_cache[file] }
+ return spec if spec
- file = file.dup.tap(&Gem::UNTAINT)
return unless File.file?(file)
- code = File.read file, :mode => 'r:UTF-8:-'
-
- code.tap(&Gem::UNTAINT)
+ code = Gem.open_file(file, "r:UTF-8:-", &:read)
begin
- _spec = eval code, binding, file
+ spec = eval code, binding, file
- if Gem::Specification === _spec
- _spec.loaded_from = File.expand_path file.to_s
- LOAD_CACHE_MUTEX.synchronize do
- prev = LOAD_CACHE[file]
+ if Gem::Specification === spec
+ spec.loaded_from = File.expand_path file.to_s
+ @load_cache_mutex.synchronize do
+ prev = @load_cache[file]
if prev
- _spec = prev
+ spec = prev
else
- LOAD_CACHE[file] = _spec
+ @load_cache[file] = spec
end
end
- return _spec
+ return spec
end
- warn "[#{file}] isn't a Gem::Specification (#{_spec.class} instead)."
+ warn "[#{file}] isn't a Gem::Specification (#{spec.class} instead)."
rescue SignalException, SystemExit
raise
- rescue SyntaxError, Exception => e
+ rescue SyntaxError, StandardError => e
warn "Invalid gemspec in [#{file}]: #{e}"
end
@@ -1199,7 +1243,7 @@ class Gem::Specification < Gem::BasicSpecification
latest_remote = remotes.sort.last
yield [local_spec, latest_remote] if
- latest_remote and local_spec.version < latest_remote
+ latest_remote && local_spec.version < latest_remote
end
nil
@@ -1225,17 +1269,12 @@ class Gem::Specification < Gem::BasicSpecification
def self.reset
@@dirs = nil
- Gem.pre_reset_hooks.each {|hook| hook.call }
- @@all = nil
- @@stubs = nil
- @@stubs_by_name = {}
- @@spec_with_requirable_file = {}
- @@active_stub_with_requirable_file = {}
- _clear_load_cache
+ Gem.pre_reset_hooks.each(&:call)
+ clear_specs
+ clear_load_cache
unresolved = unresolved_deps
unless unresolved.empty?
- w = "W" + "ARN"
- warn "#{w}: Unresolved or ambiguous specs during Gem::Specification.reset:"
+ warn "WARN: Unresolved or ambiguous specs during Gem::Specification.reset:"
unresolved.values.each do |dep|
warn " #{dep}"
@@ -1245,11 +1284,11 @@ class Gem::Specification < Gem::BasicSpecification
versions.each {|s| warn " - #{s.version}" }
end
end
- warn "#{w}: Clearing out unresolved specs. Try 'gem cleanup <gem>'"
+ warn "WARN: Clearing out unresolved specs. Try 'gem cleanup <gem>'"
warn "Please report a bug if this causes problems."
unresolved.clear
end
- Gem.post_reset_hooks.each {|hook| hook.call }
+ Gem.post_reset_hooks.each(&:call)
end
# DOC: This method needs documented or nodoc'd
@@ -1262,8 +1301,50 @@ class Gem::Specification < Gem::BasicSpecification
def self._load(str)
Gem.load_yaml
+ Gem.load_safe_marshal
+
+ yaml_set = false
+ retry_count = 0
+
+ array = begin
+ Gem::SafeMarshal.safe_load str
+ rescue ArgumentError => e
+ # Avoid an infinite retry loop when the argument error has nothing to do
+ # with the classes not being defined.
+ # 1 retry each allowed in case all 3 of
+ # - YAML
+ # - YAML::Syck::DefaultKey
+ # - YAML::PrivateType
+ # need to be defined
+ raise if retry_count >= 3
+
+ #
+ # Some very old marshaled specs included references to `YAML::PrivateType`
+ # and `YAML::Syck::DefaultKey` constants due to bugs in the old emitter
+ # that generated them. Workaround the issue by defining the necessary
+ # constants and retrying.
+ #
+ message = e.message
+ raise unless message.include?("YAML::")
+
+ unless Object.const_defined?(:YAML)
+ Object.const_set "YAML", Psych
+ yaml_set = true
+ end
+
+ if message.include?("YAML::Syck::")
+ YAML.const_set "Syck", YAML unless YAML.const_defined?(:Syck)
- array = Marshal.load str
+ YAML::Syck.const_set "DefaultKey", Class.new if message.include?("YAML::Syck::DefaultKey") && !YAML::Syck.const_defined?(:DefaultKey)
+ elsif message.include?("YAML::PrivateType") && !YAML.const_defined?(:PrivateType)
+ YAML.const_set "PrivateType", Class.new
+ end
+
+ retry_count += 1
+ retry
+ ensure
+ Object.__send__(:remove_const, "YAML") if yaml_set
+ end
spec = Gem::Specification.new
spec.instance_variable_set :@specification_version, array[1]
@@ -1271,22 +1352,17 @@ class Gem::Specification < Gem::BasicSpecification
current_version = CURRENT_SPECIFICATION_VERSION
field_count = if spec.specification_version > current_version
- spec.instance_variable_set :@specification_version,
- current_version
- MARSHAL_FIELDS[current_version]
- else
- MARSHAL_FIELDS[spec.specification_version]
- end
+ spec.instance_variable_set :@specification_version,
+ current_version
+ MARSHAL_FIELDS[current_version]
+ else
+ MARSHAL_FIELDS[spec.specification_version]
+ end
if array.size < field_count
raise TypeError, "invalid Gem::Specification format #{array.inspect}"
end
- # Cleanup any YAML::PrivateType. They only show up for an old bug
- # where nil => null, so just convert them to nil based on the type.
-
- array.map! {|e| e.kind_of?(YAML::PrivateType) ? nil : e }
-
spec.instance_variable_set :@rubygems_version, array[0]
# spec version
spec.instance_variable_set :@name, array[2]
@@ -1342,7 +1418,7 @@ class Gem::Specification < Gem::BasicSpecification
@required_rubygems_version,
@original_platform,
@dependencies,
- '', # rubyforge_project
+ "", # rubyforge_project
@email,
@authors,
@description,
@@ -1361,7 +1437,7 @@ class Gem::Specification < Gem::BasicSpecification
# there are conflicts upon activation.
def activate
- other = Gem.loaded_specs[self.name]
+ other = Gem.loaded_specs[name]
if other
check_version_conflict other
return false
@@ -1372,11 +1448,11 @@ class Gem::Specification < Gem::BasicSpecification
activate_dependencies
add_self_to_load_path
- Gem.loaded_specs[self.name] = self
+ Gem.loaded_specs[name] = self
@activated = true
@loaded = true
- return true
+ true
end
##
@@ -1387,7 +1463,7 @@ class Gem::Specification < Gem::BasicSpecification
def activate_dependencies
unresolved = Gem::Specification.unresolved_deps
- self.runtime_dependencies.each do |spec_dep|
+ runtime_dependencies.each do |spec_dep|
if loaded = Gem.loaded_specs[spec_dep.name]
next if spec_dep.matches_spec? loaded
@@ -1401,7 +1477,7 @@ class Gem::Specification < Gem::BasicSpecification
begin
specs = spec_dep.to_specs
rescue Gem::MissingSpecError => e
- raise Gem::MissingSpecError.new(e.name, e.requirement, "at: #{self.spec_file}")
+ raise Gem::MissingSpecError.new(e.name, e.requirement, "at: #{spec_file}")
end
if specs.size == 1
@@ -1447,7 +1523,7 @@ class Gem::Specification < Gem::BasicSpecification
def sanitize_string(string)
return string unless string
- # HACK the #to_s is in here because RSpec has an Array of Arrays of
+ # HACK: the #to_s is in here because RSpec has an Array of Arrays of
# Strings for authors. Need a way to disallow bad values on gemspec
# generation. (Probably won't happen.)
string.to_s
@@ -1465,8 +1541,8 @@ class Gem::Specification < Gem::BasicSpecification
else
executables
end
- rescue
- return nil
+ rescue StandardError
+ nil
end
##
@@ -1476,10 +1552,10 @@ class Gem::Specification < Gem::BasicSpecification
def add_dependency_with_type(dependency, type, requirements)
requirements = if requirements.empty?
- Gem::Requirement.default
- else
- requirements.flatten
- end
+ Gem::Requirement.default
+ else
+ requirements.flatten
+ end
unless dependency.respond_to?(:name) &&
dependency.respond_to?(:requirement)
@@ -1491,7 +1567,7 @@ class Gem::Specification < Gem::BasicSpecification
private :add_dependency_with_type
- alias add_dependency add_runtime_dependency
+ alias_method :add_dependency, :add_runtime_dependency
##
# Adds this spec's require paths to LOAD_PATH, in the proper location.
@@ -1508,7 +1584,7 @@ class Gem::Specification < Gem::BasicSpecification
# Singular reader for #authors. Returns the first author in the list
def author
- val = authors and val.first
+ (val = authors) && val.first
end
##
@@ -1543,7 +1619,7 @@ class Gem::Specification < Gem::BasicSpecification
def build_args
if File.exist? build_info_file
build_info = File.readlines build_info_file
- build_info = build_info.map {|x| x.strip }
+ build_info = build_info.map(&:strip)
build_info.delete ""
build_info
else
@@ -1556,11 +1632,13 @@ class Gem::Specification < Gem::BasicSpecification
# the gem.build_complete file is missing.
def build_extensions # :nodoc:
- return if default_gem?
return if extensions.empty?
+ return if default_gem?
+ # we need to fresh build when same name and version of default gems
+ return if self.class.find_by_full_name(full_name)&.default_gem?
return if File.exist? gem_build_complete_path
- return if !File.writable?(base_dir)
- return if !File.exist?(File.join(base_dir, 'extensions'))
+ return unless File.writable?(base_dir)
+ return unless File.exist?(File.join(base_dir, "extensions"))
begin
# We need to require things in $LOAD_PATH without looking for the
@@ -1568,9 +1646,9 @@ class Gem::Specification < Gem::BasicSpecification
unresolved_deps = Gem::Specification.unresolved_deps.dup
Gem::Specification.unresolved_deps.clear
- require 'rubygems/config_file'
- require 'rubygems/ext'
- require 'rubygems/user_interaction'
+ require_relative "config_file"
+ require_relative "ext"
+ require_relative "user_interaction"
ui = Gem::SilentUI.new
Gem::DefaultUserInteraction.use_ui ui do
@@ -1578,7 +1656,7 @@ class Gem::Specification < Gem::BasicSpecification
builder.build_extensions
end
ensure
- ui.close if ui
+ ui&.close
Gem::Specification.unresolved_deps.replace unresolved_deps
end
end
@@ -1618,9 +1696,9 @@ class Gem::Specification < Gem::BasicSpecification
def conflicts
conflicts = {}
- self.runtime_dependencies.each do |dep|
+ runtime_dependencies.each do |dep|
spec = Gem.loaded_specs[dep.name]
- if spec and not spec.satisfies_requirement? dep
+ if spec && !spec.satisfies_requirement?(dep)
(conflicts[spec] ||= []) << dep
end
end
@@ -1634,7 +1712,7 @@ class Gem::Specification < Gem::BasicSpecification
def conficts_when_loaded_with?(list_of_specs) # :nodoc:
result = list_of_specs.any? do |spec|
- spec.dependencies.any? {|dep| dep.runtime? && (dep.name == name) && !satisfies_requirement?(dep) }
+ spec.runtime_dependencies.any? {|dep| (dep.name == name) && !satisfies_requirement?(dep) }
end
result
end
@@ -1644,14 +1722,12 @@ class Gem::Specification < Gem::BasicSpecification
def has_conflicts?
return true unless Gem.env_requirement(name).satisfied_by?(version)
- self.dependencies.any? do |dep|
- if dep.runtime?
- spec = Gem.loaded_specs[dep.name]
- spec and not spec.satisfies_requirement? dep
- else
- false
- end
+ runtime_dependencies.any? do |dep|
+ spec = Gem.loaded_specs[dep.name]
+ spec && !spec.satisfies_requirement?(dep)
end
+ rescue ArgumentError => e
+ raise e, "#{name} #{version}: #{e.message}"
end
# The date this gem was created.
@@ -1668,14 +1744,14 @@ class Gem::Specification < Gem::BasicSpecification
DateLike = Object.new # :nodoc:
def DateLike.===(obj) # :nodoc:
- defined?(::Date) and Date === obj
+ defined?(::Date) && Date === obj
end
DateTimeFormat = # :nodoc:
/\A
(\d{4})-(\d{2})-(\d{2})
(\s+ \d{2}:\d{2}:\d{2}\.\d+ \s* (Z | [-+]\d\d:\d\d) )?
- \Z/x.freeze
+ \Z/x
##
# The date this gem was created
@@ -1690,12 +1766,6 @@ class Gem::Specification < Gem::BasicSpecification
when String then
if DateTimeFormat =~ date
Time.utc($1.to_i, $2.to_i, $3.to_i)
-
- # Workaround for where the date format output from psych isn't
- # parsed as a Time object by syck and thus comes through as a
- # string.
- elsif /\A(\d{4})-(\d{2})-(\d{2}) \d{2}:\d{2}:\d{2}\.\d+?Z\z/ =~ date
- Time.utc($1.to_i, $2.to_i, $3.to_i)
else
raise(Gem::InvalidSpecificationException,
"invalid date format in specification: #{date.inspect}")
@@ -1704,7 +1774,7 @@ class Gem::Specification < Gem::BasicSpecification
Time.utc(date.year, date.month, date.day)
else
TODAY
- end
+ end
end
##
@@ -1714,9 +1784,9 @@ class Gem::Specification < Gem::BasicSpecification
# executable now. See Gem.bin_path.
def default_executable # :nodoc:
- if defined?(@default_executable) and @default_executable
+ if defined?(@default_executable) && @default_executable
result = @default_executable
- elsif @executables and @executables.size == 1
+ elsif @executables && @executables.size == 1
result = Array(@executables).first
else
result = nil
@@ -1753,13 +1823,12 @@ class Gem::Specification < Gem::BasicSpecification
Gem::Specification.each do |spec|
deps = check_dev ? spec.dependencies : spec.runtime_dependencies
deps.each do |dep|
- if self.satisfies_requirement?(dep)
- sats = []
- find_all_satisfiers(dep) do |sat|
- sats << sat
- end
- out << [spec, dep, sats]
+ next unless satisfies_requirement?(dep)
+ sats = []
+ find_all_satisfiers(dep) do |sat|
+ sats << sat
end
+ out << [spec, dep, sats]
end
end
out
@@ -1769,7 +1838,7 @@ class Gem::Specification < Gem::BasicSpecification
# Returns all specs that matches this spec's runtime dependencies.
def dependent_specs
- runtime_dependencies.map {|dep| dep.to_specs }.flatten
+ runtime_dependencies.map(&:to_specs).flatten
end
##
@@ -1795,7 +1864,7 @@ class Gem::Specification < Gem::BasicSpecification
# spec.doc_dir 'ri' # => "/path/to/gem_repo/doc/a-1/ri"
def doc_dir(type = nil)
- @doc_dir ||= File.join base_dir, 'doc', full_name
+ @doc_dir ||= File.join base_dir, "doc", full_name
if type
File.join @doc_dir, type
@@ -1807,21 +1876,22 @@ class Gem::Specification < Gem::BasicSpecification
def encode_with(coder) # :nodoc:
mark_version
- coder.add 'name', @name
- coder.add 'version', @version
+ coder.add "name", @name
+ coder.add "version", @version
platform = case @original_platform
- when nil, '' then
- 'ruby'
+ when nil, "" then
+ "ruby"
when String then
@original_platform
else
@original_platform.to_s
- end
- coder.add 'platform', platform
+ end
+ coder.add "platform", platform
attributes = @@attributes.map(&:to_s) - %w[name version platform]
attributes.each do |name|
- coder.add name, instance_variable_get("@#{name}")
+ value = instance_variable_get("@#{name}")
+ coder.add name, value unless value.nil?
end
end
@@ -1833,7 +1903,7 @@ class Gem::Specification < Gem::BasicSpecification
# Singular accessor for #executables
def executable
- val = executables and val.first
+ (val = executables) && val.first
end
##
@@ -1938,18 +2008,18 @@ class Gem::Specification < Gem::BasicSpecification
end
rubygems_deprecate :has_rdoc=
- alias :has_rdoc? :has_rdoc # :nodoc:
+ alias_method :has_rdoc?, :has_rdoc # :nodoc:
rubygems_deprecate :has_rdoc?
##
# True if this gem has files in test_files
def has_unit_tests? # :nodoc:
- not test_files.empty?
+ !test_files.empty?
end
# :stopdoc:
- alias has_test_suite? has_unit_tests?
+ alias_method :has_test_suite?, :has_unit_tests?
# :startdoc:
def hash # :nodoc:
@@ -1998,7 +2068,7 @@ class Gem::Specification < Gem::BasicSpecification
self.name = name if name
self.version = version if version
- if platform = Gem.platforms.last and platform != Gem::Platform::RUBY and platform != Gem::Platform.local
+ if (platform = Gem.platforms.last) && platform != Gem::Platform::RUBY && platform != Gem::Platform.local
self.platform = platform
end
@@ -2006,7 +2076,8 @@ class Gem::Specification < Gem::BasicSpecification
end
##
- # Duplicates array_attributes from +other_spec+ so state isn't shared.
+ # Duplicates Array and Gem::Requirement attributes from +other_spec+ so state isn't shared.
+ #
def initialize_copy(other_spec)
self.class.array_attributes.each do |name|
@@ -2028,15 +2099,18 @@ class Gem::Specification < Gem::BasicSpecification
raise e
end
end
+
+ @required_ruby_version = other_spec.required_ruby_version.dup
+ @required_rubygems_version = other_spec.required_rubygems_version.dup
end
def base_dir
return Gem.dir unless loaded_from
@base_dir ||= if default_gem?
- File.dirname File.dirname File.dirname loaded_from
- else
- File.dirname File.dirname loaded_from
- end
+ File.dirname File.dirname File.dirname loaded_from
+ else
+ File.dirname File.dirname loaded_from
+ end
end
##
@@ -2113,8 +2187,8 @@ class Gem::Specification < Gem::BasicSpecification
return
end
- if @specification_version > CURRENT_SPECIFICATION_VERSION and
- sym.to_s.end_with?("=")
+ if @specification_version > CURRENT_SPECIFICATION_VERSION &&
+ sym.to_s.end_with?("=")
warn "ignoring #{sym} loading #{full_name}" if $DEBUG
else
super
@@ -2126,8 +2200,8 @@ class Gem::Specification < Gem::BasicSpecification
# probably want to build_extensions
def missing_extensions?
- return false if default_gem?
return false if extensions.empty?
+ return false if default_gem?
return false if File.exist? gem_build_complete_path
true
@@ -2140,7 +2214,7 @@ class Gem::Specification < Gem::BasicSpecification
# file list.
def normalize
- if defined?(@extra_rdoc_files) and @extra_rdoc_files
+ if defined?(@extra_rdoc_files) && @extra_rdoc_files
@extra_rdoc_files.uniq!
@files ||= []
@files.concat(@extra_rdoc_files)
@@ -2165,7 +2239,7 @@ class Gem::Specification < Gem::BasicSpecification
# platform. For use with legacy gems.
def original_name # :nodoc:
- if platform == Gem::Platform::RUBY or platform.nil?
+ if platform == Gem::Platform::RUBY || platform.nil?
"#{@name}-#{@version}"
else
"#{@name}-#{@version}-#{@original_platform}"
@@ -2183,11 +2257,11 @@ class Gem::Specification < Gem::BasicSpecification
# The platform this gem runs on. See Gem::Platform for details.
def platform
- @new_platform ||= Gem::Platform::RUBY
+ @new_platform ||= Gem::Platform::RUBY # rubocop:disable Naming/MemoizedInstanceVariableName
end
def pretty_print(q) # :nodoc:
- q.group 2, 'Gem::Specification.new do |s|', 'end' do
+ q.group 2, "Gem::Specification.new do |s|", "end" do
q.breakable
attributes = @@attributes - [:name, :version]
@@ -2196,23 +2270,22 @@ class Gem::Specification < Gem::BasicSpecification
attributes.unshift :name
attributes.each do |attr_name|
- current_value = self.send attr_name
- current_value = current_value.sort if %i[files test_files].include? attr_name
- if current_value != default_value(attr_name) or
- self.class.required_attribute? attr_name
-
- q.text "s.#{attr_name} = "
+ current_value = send attr_name
+ current_value = current_value.sort if [:files, :test_files].include? attr_name
+ next unless current_value != default_value(attr_name) ||
+ self.class.required_attribute?(attr_name)
- if attr_name == :date
- current_value = current_value.utc
+ q.text "s.#{attr_name} = "
- q.text "Time.utc(#{current_value.year}, #{current_value.month}, #{current_value.day})"
- else
- q.pp current_value
- end
+ if attr_name == :date
+ current_value = current_value.utc
- q.breakable
+ q.text "Time.utc(#{current_value.year}, #{current_value.month}, #{current_value.day})"
+ else
+ q.pp current_value
end
+
+ q.breakable
end
end
end
@@ -2222,7 +2295,7 @@ class Gem::Specification < Gem::BasicSpecification
# that is already loaded (+other+)
def check_version_conflict(other) # :nodoc:
- return if self.version == other.version
+ return if version == other.version
# This gem is already loaded. If the currently loaded gem is not in the
# list of candidate gems, then we have a version conflict.
@@ -2230,7 +2303,7 @@ class Gem::Specification < Gem::BasicSpecification
msg = "can't activate #{full_name}, already activated #{other.full_name}"
e = Gem::LoadError.new msg
- e.name = self.name
+ e.name = name
raise e
end
@@ -2257,7 +2330,7 @@ class Gem::Specification < Gem::BasicSpecification
# Singular accessor for #require_paths
def require_path
- val = require_paths and val.first
+ (val = require_paths) && val.first
end
##
@@ -2282,7 +2355,7 @@ class Gem::Specification < Gem::BasicSpecification
# Returns the full path to this spec's ri directory.
def ri_dir
- @ri_dir ||= File.join base_dir, 'ri', full_name
+ @ri_dir ||= File.join base_dir, "ri", full_name
end
##
@@ -2292,16 +2365,16 @@ class Gem::Specification < Gem::BasicSpecification
def ruby_code(obj)
case obj
when String then obj.dump + ".freeze"
- when Array then '[' + obj.map {|x| ruby_code x }.join(", ") + ']'
+ when Array then "[" + obj.map {|x| ruby_code x }.join(", ") + "]"
when Hash then
seg = obj.keys.sort.map {|k| "#{k.to_s.dump} => #{obj[k].to_s.dump}" }
- "{ #{seg.join(', ')} }"
- when Gem::Version then obj.to_s.dump
- when DateLike then obj.strftime('%Y-%m-%d').dump
- when Time then obj.strftime('%Y-%m-%d').dump
+ "{ #{seg.join(", ")} }"
+ when Gem::Version then ruby_code(obj.to_s)
+ when DateLike then obj.strftime("%Y-%m-%d").dump
+ when Time then obj.strftime("%Y-%m-%d").dump
when Numeric then obj.inspect
when true, false, nil then obj.inspect
- when Gem::Platform then "Gem::Platform.new(#{obj.to_a.inspect})"
+ when Gem::Platform then "Gem::Platform.new(#{ruby_code obj.to_a})"
when Gem::Requirement then
list = obj.as_list
"Gem::Requirement.new(#{ruby_code(list.size == 1 ? obj.to_s : list)})"
@@ -2322,7 +2395,7 @@ class Gem::Specification < Gem::BasicSpecification
# True if this gem has the same attributes as +other+.
def same_attributes?(spec)
- @@attributes.all? {|name, default| self.send(name) == spec.send(name) }
+ @@attributes.all? {|name, _default| send(name) == spec.send(name) }
end
private :same_attributes?
@@ -2331,7 +2404,7 @@ class Gem::Specification < Gem::BasicSpecification
# Checks if this specification meets the requirement of +dependency+.
def satisfies_requirement?(dependency)
- return @name == dependency.name &&
+ @name == dependency.name &&
dependency.requirement.satisfied_by?(@version)
end
@@ -2339,7 +2412,7 @@ class Gem::Specification < Gem::BasicSpecification
# Returns an object you can use to sort specifications in #sort_by.
def sort_obj
- [@name, @version, @new_platform == Gem::Platform::RUBY ? -1 : 1]
+ [@name, @version, Gem::Platform.sort_priority(@new_platform)]
end
##
@@ -2386,7 +2459,7 @@ class Gem::Specification < Gem::BasicSpecification
# Singular accessor for #test_files
def test_file # :nodoc:
- val = test_files and val.first
+ (val = test_files) && val.first
end
##
@@ -2408,7 +2481,7 @@ class Gem::Specification < Gem::BasicSpecification
@test_files = [@test_suite_file].flatten
@test_suite_file = nil
end
- if defined?(@test_files) and @test_files
+ if defined?(@test_files) && @test_files
@test_files
else
@test_files = []
@@ -2421,7 +2494,6 @@ class Gem::Specification < Gem::BasicSpecification
# still have their default values are omitted.
def to_ruby
- require_relative 'openssl'
mark_version
result = []
result << "# -*- encoding: utf-8 -*-"
@@ -2433,13 +2505,13 @@ class Gem::Specification < Gem::BasicSpecification
result << " s.name = #{ruby_code name}"
result << " s.version = #{ruby_code version}"
- unless platform.nil? or platform == Gem::Platform::RUBY
+ unless platform.nil? || platform == Gem::Platform::RUBY
result << " s.platform = #{ruby_code original_platform}"
end
result << ""
result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version="
- if metadata and !metadata.empty?
+ if metadata && !metadata.empty?
result << " s.metadata = #{ruby_code metadata} if s.respond_to? :metadata="
end
result << " s.require_paths = #{ruby_code raw_require_paths}"
@@ -2455,42 +2527,35 @@ class Gem::Specification < Gem::BasicSpecification
:has_rdoc,
:default_executable,
:metadata,
+ :signing_key,
]
@@attributes.each do |attr_name|
next if handled.include? attr_name
- current_value = self.send(attr_name)
+ current_value = send(attr_name)
if current_value != default_value(attr_name) || self.class.required_attribute?(attr_name)
- result << " s.#{attr_name} = #{ruby_code current_value}" unless defined?(OpenSSL::PKey::RSA) && current_value.is_a?(OpenSSL::PKey::RSA)
+ result << " s.#{attr_name} = #{ruby_code current_value}"
end
end
+ if String === signing_key
+ result << " s.signing_key = #{ruby_code signing_key}"
+ end
+
if @installed_by_version
result << nil
- result << " s.installed_by_version = \"#{Gem::VERSION}\" if s.respond_to? :installed_by_version"
+ result << " s.installed_by_version = #{ruby_code Gem::VERSION} if s.respond_to? :installed_by_version"
end
unless dependencies.empty?
result << nil
- result << " if s.respond_to? :specification_version then"
- result << " s.specification_version = #{specification_version}"
- result << " end"
+ result << " s.specification_version = #{specification_version}"
result << nil
- result << " if s.respond_to? :add_runtime_dependency then"
-
dependencies.each do |dep|
- req = dep.requirements_list.inspect
dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK
- result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>.freeze, #{req})"
- end
-
- result << " else"
- dependencies.each do |dep|
- version_reqs_param = dep.requirements_list.inspect
- result << " s.add_dependency(%q<#{dep.name}>.freeze, #{version_reqs_param})"
+ result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>.freeze, #{ruby_code dep.requirements_list})"
end
- result << " end"
end
result << "end"
@@ -2527,14 +2592,14 @@ class Gem::Specification < Gem::BasicSpecification
# back, we have to check again here to make sure that our
# psych code was properly loaded, and load it if not.
unless Gem.const_defined?(:NoAliasYAMLTree)
- require 'rubygems/psych_tree'
+ require_relative "psych_tree"
end
builder = Gem::NoAliasYAMLTree.create
builder << self
ast = builder.tree
- require 'stringio'
+ require "stringio"
io = StringIO.new
io.set_encoding Encoding::UTF_8
@@ -2550,10 +2615,9 @@ class Gem::Specification < Gem::BasicSpecification
def traverse(trail = [], visited = {}, &block)
trail.push(self)
begin
- dependencies.each do |dep|
- next unless dep.runtime?
+ runtime_dependencies.each do |dep|
dep.matching_specs(true).each do |dep_spec|
- next if visited.has_key?(dep_spec)
+ next if visited.key?(dep_spec)
visited[dep_spec] = true
trail.push(dep_spec)
begin
@@ -2561,11 +2625,10 @@ class Gem::Specification < Gem::BasicSpecification
ensure
trail.pop
end
- unless result == :next
- spec_name = dep_spec.name
- dep_spec.traverse(trail, visited, &block) unless
- trail.any? {|s| s.name == spec_name }
- end
+ next if result == :next
+ spec_name = dep_spec.name
+ dep_spec.traverse(trail, visited, &block) unless
+ trail.any? {|s| s.name == spec_name }
end
end
ensure
@@ -2612,20 +2675,13 @@ class Gem::Specification < Gem::BasicSpecification
rubygems_deprecate :validate_permissions
##
- # Set the version to +version+, potentially also setting
- # required_rubygems_version if +version+ indicates it is a
- # prerelease.
+ # Set the version to +version+.
def version=(version)
@version = Gem::Version.create(version)
- # skip to set required_ruby_version when pre-released rubygems.
- # It caused to raise CircularDependencyError
- if @version.prerelease? && (@name.nil? || @name.strip != "rubygems")
- self.required_rubygems_version = '> 1.3.1'
- end
- invalidate_memoized_attributes
+ return if @version.nil?
- return @version
+ invalidate_memoized_attributes
end
def stubbed?
@@ -2637,9 +2693,9 @@ class Gem::Specification < Gem::BasicSpecification
case ivar
when "date"
# Force Date to go through the extra coerce logic in date=
- self.date = val.tap(&Gem::UNTAINT)
+ self.date = val
else
- instance_variable_set "@#{ivar}", val.tap(&Gem::UNTAINT)
+ instance_variable_set "@#{ivar}", val
end
end
@@ -2656,17 +2712,26 @@ class Gem::Specification < Gem::BasicSpecification
end
nil_attributes.each do |attribute|
- default = self.default_value attribute
+ default = default_value attribute
value = case default
when Time, Numeric, Symbol, true, false, nil then default
else default.dup
- end
+ end
instance_variable_set "@#{attribute}", value
end
@installed_by_version ||= nil
+
+ nil
+ end
+
+ def flatten_require_paths # :nodoc:
+ return unless raw_require_paths.first.is_a?(Array)
+
+ warn "#{name} #{version} includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this"
+ raw_require_paths.flatten!
end
def raw_require_paths # :nodoc:
diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb
index 2b8b05635e..516c26f53c 100644
--- a/lib/rubygems/specification_policy.rb
+++ b/lib/rubygems/specification_policy.rb
@@ -1,22 +1,25 @@
-require 'rubygems/user_interaction'
+# frozen_string_literal: true
+
+require_relative "user_interaction"
class Gem::SpecificationPolicy
include Gem::UserInteraction
- VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/.freeze # :nodoc:
+ VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc:
- SPECIAL_CHARACTERS = /\A[#{Regexp.escape('.-_')}]+/.freeze # :nodoc:
+ SPECIAL_CHARACTERS = /\A[#{Regexp.escape(".-_")}]+/ # :nodoc:
- VALID_URI_PATTERN = %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z}.freeze # :nodoc:
+ VALID_URI_PATTERN = %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} # :nodoc:
METADATA_LINK_KEYS = %w[
- bug_tracker_uri
- changelog_uri
- documentation_uri
homepage_uri
- mailing_list_uri
+ changelog_uri
source_code_uri
+ documentation_uri
wiki_uri
+ mailing_list_uri
+ bug_tracker_uri
+ download_uri
funding_uri
].freeze # :nodoc:
@@ -100,10 +103,14 @@ class Gem::SpecificationPolicy
validate_dependencies
+ validate_required_ruby_version
+
validate_extensions
validate_removed_attributes
+ validate_unique_links
+
if @warnings > 0
if strict
error "specification has warnings"
@@ -120,30 +127,30 @@ class Gem::SpecificationPolicy
metadata = @specification.metadata
unless Hash === metadata
- error 'metadata must be a hash'
+ error "metadata must be a hash"
end
metadata.each do |key, value|
- if !key.kind_of?(String)
+ entry = "metadata['#{key}']"
+ unless key.is_a?(String)
error "metadata keys must be a String"
end
if key.size > 128
- error "metadata key too large (#{key.size} > 128)"
+ error "metadata key is too large (#{key.size} > 128)"
end
- if !value.kind_of?(String)
- error "metadata values must be a String"
+ unless value.is_a?(String)
+ error "#{entry} value must be a String"
end
if value.size > 1024
- error "metadata value too large (#{value.size} > 1024)"
+ error "#{entry} value is too large (#{value.size} > 1024)"
end
- if METADATA_LINK_KEYS.include? key
- if value !~ VALID_URI_PATTERN
- error "metadata['#{key}'] has invalid link: #{value.inspect}"
- end
+ next unless METADATA_LINK_KEYS.include? key
+ unless VALID_URI_PATTERN.match?(value)
+ error "#{entry} has invalid link: #{value.inspect}"
end
end
end
@@ -153,14 +160,14 @@ class Gem::SpecificationPolicy
def validate_duplicate_dependencies # :nodoc:
# NOTE: see REFACTOR note in Gem::Dependency about types - this might be brittle
- seen = Gem::Dependency::TYPES.inject({}) {|types, type| types.merge({ type => {}}) }
+ seen = Gem::Dependency::TYPES.inject({}) {|types, type| types.merge({ type => {} }) }
error_messages = []
@specification.dependencies.each do |dep|
if prev = seen[dep.type][dep.name]
error_messages << <<-MESSAGE
duplicate dependency on #{dep}, (#{prev.requirement}) use:
- add_#{dep.type}_dependency '#{dep.name}', '#{dep.requirement}', '#{prev.requirement}'
+ add_#{dep.type}_dependency \"#{dep.name}\", \"#{dep.requirement}\", \"#{prev.requirement}\"
MESSAGE
end
@@ -172,6 +179,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
end
##
+ # Checks that the gem does not depend on itself.
# Checks that dependencies use requirements as we recommend. Warnings are
# issued when dependencies are open-ended or overly strict for semantic
# versioning.
@@ -179,6 +187,10 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_dependencies # :nodoc:
warning_messages = []
@specification.dependencies.each do |dep|
+ if dep.name == @specification.name # warn on self reference
+ warning_messages << "Self referencing dependency is unnecessary and strongly discouraged."
+ end
+
prerelease_dep = dep.requirements_list.any? do |req|
Gem::Requirement.new(req).prerelease?
end
@@ -187,37 +199,42 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
prerelease_dep && !@specification.version.prerelease?
open_ended = dep.requirement.requirements.all? do |op, version|
- not version.prerelease? and (op == '>' or op == '>=')
+ !version.prerelease? && [">", ">="].include?(op)
end
- if open_ended
- op, dep_version = dep.requirement.requirements.first
-
- segments = dep_version.segments
+ next unless open_ended
+ op, dep_version = dep.requirement.requirements.first
- base = segments.first 2
+ segments = dep_version.segments
- recommendation = if (op == '>' || op == '>=') && segments == [0]
- " use a bounded requirement, such as '~> x.y'"
- else
- bugfix = if op == '>'
- ", '> #{dep_version}'"
- elsif op == '>=' and base != segments
- ", '>= #{dep_version}'"
- end
+ base = segments.first 2
- " if #{dep.name} is semantically versioned, use:\n" \
- " add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}'#{bugfix}"
- end
+ recommendation = if [">", ">="].include?(op) && segments == [0]
+ " use a bounded requirement, such as \"~> x.y\""
+ else
+ bugfix = if op == ">"
+ ", \"> #{dep_version}\""
+ elsif op == ">=" && base != segments
+ ", \">= #{dep_version}\""
+ end
- warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n"
+ " if #{dep.name} is semantically versioned, use:\n" \
+ " add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join "."}\"#{bugfix}"
end
+
+ warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n"
end
if warning_messages.any?
warning_messages.each {|warning_message| warning warning_message }
end
end
+ def validate_required_ruby_version
+ if @specification.required_ruby_version.requirements == [Gem::Requirement::DefaultRequirement]
+ warning "make sure you specify the oldest ruby version constraint (like \">= 3.0\") that you want your gem to support by setting the `required_ruby_version` gemspec attribute"
+ end
+ end
+
##
# Issues a warning for each file to be packaged which is world-readable.
#
@@ -228,7 +245,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
@specification.files.each do |file|
next unless File.file?(file)
- next if File.stat(file).mode & 0444 == 0444
+ next if File.stat(file).mode & 0o444 == 0o444
warning "#{file} is not world-readable"
end
@@ -247,7 +264,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
@specification.instance_variable_get("@#{attrname}").nil?
end
return if nil_attributes.empty?
- error "#{nil_attributes.join ', '} must not be nil"
+ error "#{nil_attributes.join ", "} must not be nil"
end
def validate_rubygems_version
@@ -273,11 +290,11 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
if !name.is_a?(String)
error "invalid value for attribute name: \"#{name.inspect}\" must be a string"
- elsif name !~ /[a-zA-Z]/
+ elsif !/[a-zA-Z]/.match?(name)
error "invalid value for attribute name: #{name.dump} must include at least one letter"
- elsif name !~ VALID_NAME_PATTERN
+ elsif !VALID_NAME_PATTERN.match?(name)
error "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores"
- elsif name =~ SPECIAL_CHARACTERS
+ elsif SPECIAL_CHARACTERS.match?(name)
error "invalid value for attribute name: #{name.dump} can not begin with a period, dash, or underscore"
end
end
@@ -285,7 +302,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_require_paths
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
def validate_non_files
@@ -309,7 +326,7 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
def validate_specification_version
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
def validate_platform
@@ -335,9 +352,9 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
Gem::Dependency
else
String
- end
+ end
- unless Array === val and val.all? {|x| x.kind_of?(klass) }
+ unless Array === val && val.all? {|x| x.is_a?(klass) || (field == :licenses && x.nil?) }
error "#{field} must be an Array of #{klass}"
end
end
@@ -352,6 +369,8 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
licenses = @specification.licenses
licenses.each do |license|
+ next if license.nil?
+
if license.length > 64
error "each license must be 64 characters or less"
end
@@ -362,26 +381,38 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
licenses = @specification.licenses
licenses.each do |license|
- if !Gem::Licenses.match?(license)
- suggestions = Gem::Licenses.suggestions(license)
- message = <<-WARNING
-license value '#{license}' is invalid. Use a license identifier from
-http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license.
- WARNING
- message += "Did you mean #{suggestions.map {|s| "'#{s}'" }.join(', ')}?\n" unless suggestions.nil?
- warning(message)
+ next if Gem::Licenses.match?(license) || license.nil?
+ license_id_deprecated = Gem::Licenses.deprecated_license_id?(license)
+ exception_id_deprecated = Gem::Licenses.deprecated_exception_id?(license)
+ suggestions = Gem::Licenses.suggestions(license)
+
+ if license_id_deprecated
+ main_message = "License identifier '#{license}' is deprecated"
+ elsif exception_id_deprecated
+ main_message = "Exception identifier at '#{license}' is deprecated"
+ else
+ main_message = "License identifier '#{license}' is invalid"
end
+
+ message = <<-WARNING
+#{main_message}. Use an identifier from
+https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license,
+or set it to nil if you don't want to specify a license.
+ WARNING
+ message += "Did you mean #{suggestions.map {|s| "'#{s}'" }.join(", ")}?\n" unless suggestions.nil?
+ warning(message)
end
warning <<-WARNING if licenses.empty?
-licenses is empty, but is recommended. Use a license identifier from
-http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license.
+licenses is empty, but is recommended. Use an license identifier from
+https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license,
+or set it to nil if you don't want to specify a license.
WARNING
end
- LAZY = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, '')
- LAZY_PATTERN = /FI XME|TO DO/x.freeze
- HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i.freeze
+ LAZY = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, "")
+ LAZY_PATTERN = /\AFI XME|\ATO DO/x
+ HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i
def validate_lazy_metadata
unless @specification.authors.grep(LAZY_PATTERN).empty?
@@ -392,25 +423,25 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li
error "#{LAZY} is not an email"
end
- if @specification.description =~ LAZY_PATTERN
+ if LAZY_PATTERN.match?(@specification.description)
error "#{LAZY} is not a description"
end
- if @specification.summary =~ LAZY_PATTERN
+ if LAZY_PATTERN.match?(@specification.summary)
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'
+ if homepage && !homepage.empty?
+ require_relative "vendor/uri/lib/uri"
begin
- homepage_uri = URI.parse(homepage)
- unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class
+ homepage_uri = Gem::URI.parse(homepage)
+ unless [Gem::URI::HTTP, Gem::URI::HTTPS].member? homepage_uri.class
error "\"#{homepage}\" is not a valid HTTP URI"
end
- rescue URI::InvalidURIError
+ rescue Gem::URI::InvalidURIError
error "\"#{homepage}\" is not a valid HTTP URI"
end
end
@@ -444,7 +475,7 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li
def validate_shebang_line_in(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"
end
@@ -456,17 +487,47 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li
end
def validate_extensions # :nodoc:
- require_relative 'ext'
+ require_relative "ext"
builder = Gem::Ext::Builder.new(@specification)
+ validate_rake_extensions(builder)
+ validate_rust_extensions(builder)
+ end
+
+ def validate_rust_extensions(builder) # :nodoc:
+ rust_extension = @specification.extensions.any? {|s| builder.builder_for(s).is_a? Gem::Ext::CargoBuilder }
+ missing_cargo_lock = !@specification.files.any? {|f| f.end_with?("Cargo.lock") }
+
+ error <<-ERROR if rust_extension && missing_cargo_lock
+You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec.
+ ERROR
+ end
+
+ def validate_rake_extensions(builder) # :nodoc:
rake_extension = @specification.extensions.any? {|s| builder.builder_for(s) == Gem::Ext::RakeBuilder }
- rake_dependency = @specification.dependencies.any? {|d| d.name == 'rake' }
+ rake_dependency = @specification.dependencies.any? {|d| d.name == "rake" && d.type == :runtime }
warning <<-WARNING if rake_extension && !rake_dependency
-You have specified rake based extension, but rake is not added as dependency. It is recommended to add rake as a dependency in gemspec since there's no guarantee rake will be already installed.
+You have specified rake based extension, but rake is not added as runtime dependency. It is recommended to add rake as a runtime dependency in gemspec since there's no guarantee rake will be already installed.
WARNING
end
+ def validate_unique_links
+ links = @specification.metadata.slice(*METADATA_LINK_KEYS)
+ grouped = links.group_by {|_key, uri| uri }
+ grouped.each do |uri, copies|
+ next unless copies.length > 1
+ keys = copies.map(&:first).join("\n ")
+ warning <<~WARNING
+ You have specified the uri:
+ #{uri}
+ for all of the following keys:
+ #{keys}
+ Only the first one will be shown on rubygems.org
+ WARNING
+ end
+ end
+
def warning(statement) # :nodoc:
@warnings += 1
diff --git a/lib/rubygems/stub_specification.rb b/lib/rubygems/stub_specification.rb
index 4246f9de86..58748df5d6 100644
--- a/lib/rubygems/stub_specification.rb
+++ b/lib/rubygems/stub_specification.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
##
# Gem::StubSpecification reads the stub: line from the gemspec. This prevents
# us having to eval the entire gemspec in order to find out certain
@@ -6,10 +7,10 @@
class Gem::StubSpecification < Gem::BasicSpecification
# :nodoc:
- PREFIX = "# stub: ".freeze
+ PREFIX = "# stub: "
# :nodoc:
- OPEN_MODE = 'r:UTF-8:-'.freeze
+ OPEN_MODE = "r:UTF-8:-"
class StubLine # :nodoc: all
attr_reader :name, :version, :platform, :require_paths, :extensions,
@@ -19,9 +20,9 @@ class Gem::StubSpecification < Gem::BasicSpecification
# These are common require paths.
REQUIRE_PATHS = { # :nodoc:
- 'lib' => 'lib'.freeze,
- 'test' => 'test'.freeze,
- 'ext' => 'ext'.freeze,
+ "lib" => "lib",
+ "test" => "test",
+ "ext" => "ext",
}.freeze
# These are common require path lists. This hash is used to optimize
@@ -29,28 +30,28 @@ class Gem::StubSpecification < Gem::BasicSpecification
# in their require paths, so lets take advantage of that by pre-allocating
# a require path list for that case.
REQUIRE_PATH_LIST = { # :nodoc:
- 'lib' => ['lib'].freeze,
+ "lib" => ["lib"].freeze,
}.freeze
def initialize(data, extensions)
- parts = data[PREFIX.length..-1].split(" ".freeze, 4)
- @name = parts[0].freeze
+ parts = data[PREFIX.length..-1].split(" ", 4)
+ @name = -parts[0]
@version = if Gem::Version.correct?(parts[1])
- Gem::Version.new(parts[1])
- else
- Gem::Version.new(0)
- end
+ Gem::Version.new(parts[1])
+ else
+ Gem::Version.new(0)
+ end
@platform = Gem::Platform.new parts[2]
@extensions = extensions
@full_name = if platform == Gem::Platform::RUBY
- "#{name}-#{version}"
- else
- "#{name}-#{version}-#{platform}"
- end
+ "#{name}-#{version}"
+ else
+ "#{name}-#{version}-#{platform}"
+ end
path_list = parts.last
- @require_paths = REQUIRE_PATH_LIST[path_list] || path_list.split("\0".freeze).map! do |x|
+ @require_paths = REQUIRE_PATH_LIST[path_list] || path_list.split("\0").map! do |x|
REQUIRE_PATHS[x] || x
end
end
@@ -68,7 +69,6 @@ class Gem::StubSpecification < Gem::BasicSpecification
def initialize(filename, base_dir, gems_dir, default_gem)
super()
- filename.tap(&Gem::UNTAINT)
self.loaded_from = filename
@data = nil
@@ -84,10 +84,10 @@ class Gem::StubSpecification < Gem::BasicSpecification
def activated?
@activated ||=
- begin
- loaded = Gem.loaded_specs[name]
- loaded && loaded.version == version
- end
+ begin
+ loaded = Gem.loaded_specs[name]
+ loaded && loaded.version == version
+ end
end
def default_gem?
@@ -110,21 +110,24 @@ class Gem::StubSpecification < Gem::BasicSpecification
begin
saved_lineno = $.
- File.open loaded_from, OPEN_MODE do |file|
- begin
- file.readline # discard encoding line
- stubline = file.readline.chomp
- if stubline.start_with?(PREFIX)
- extensions = if /\A#{PREFIX}/ =~ file.readline.chomp
- $'.split "\0"
- else
- StubLine::NO_EXTENSIONS
- end
-
- @data = StubLine.new stubline, extensions
- end
- rescue EOFError
+ Gem.open_file loaded_from, OPEN_MODE do |file|
+ file.readline # discard encoding line
+ stubline = file.readline
+ if stubline.start_with?(PREFIX)
+ extline = file.readline
+
+ extensions =
+ if extline.delete_prefix!(PREFIX)
+ extline.chomp!
+ extline.split "\0"
+ else
+ StubLine::NO_EXTENSIONS
+ end
+
+ stubline.chomp! # readline(chomp: true) allocates 3x as much as .readline.chomp!
+ @data = StubLine.new stubline, extensions
end
+ rescue EOFError
end
ensure
$. = saved_lineno
@@ -183,17 +186,15 @@ class Gem::StubSpecification < Gem::BasicSpecification
##
# The full Gem::Specification for this gem, loaded from evalling its gemspec
- def to_spec
+ def spec
@spec ||= if @data
- loaded = Gem.loaded_specs[name]
- loaded if loaded && loaded.version == version
- end
+ loaded = Gem.loaded_specs[name]
+ loaded if loaded && loaded.version == version
+ end
@spec ||= Gem::Specification.load(loaded_from)
- @spec.ignored = @ignored if @spec
-
- @spec
end
+ alias_method :to_spec, :spec
##
# Is this StubSpecification valid? i.e. have we found a stub line, OR does
diff --git a/lib/rubygems/syck_hack.rb b/lib/rubygems/syck_hack.rb
deleted file mode 100644
index 051483eac8..0000000000
--- a/lib/rubygems/syck_hack.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-# :stopdoc:
-
-# Hack to handle syck's DefaultKey bug
-#
-# This file is always loaded AFTER either syck or psych are already
-# loaded. It then looks at what constants are available and creates
-# a consistent view on all rubys.
-#
-# All this is so that there is always a YAML::Syck::DefaultKey
-# class no matter if the full yaml library has loaded or not.
-#
-
-module YAML # :nodoc:
- # In newer 1.9.2, there is a Syck toplevel constant instead of it
- # being underneath YAML. If so, reference it back under YAML as
- # well.
- if defined? ::Syck
- # for tests that change YAML::ENGINE
- # 1.8 does not support the second argument to const_defined?
- remove_const :Syck rescue nil
-
- Syck = ::Syck
-
- # JRuby's "Syck" is called "Yecht"
- elsif defined? YAML::Yecht
- Syck = YAML::Yecht
-
- # Otherwise, if there is no YAML::Syck, then we've got just psych
- # loaded, so lets define a stub for DefaultKey.
- elsif !defined? YAML::Syck
- module Syck
- class DefaultKey # :nodoc:
- end
- end
- end
-
- # Now that we've got something that is always here, define #to_s
- # so when code tries to use this, it at least just shows up like it
- # should.
- module Syck
- class DefaultKey
- remove_method :to_s rescue nil
-
- def to_s
- '='
- end
- end
- end
-
- SyntaxError = Error unless defined? SyntaxError
-end
-
-# Sometime in the 1.9 dev cycle, the Syck constant was moved from under YAML
-# to be a toplevel constant. So gemspecs created under these versions of Syck
-# will have references to Syck::DefaultKey.
-#
-# So we need to be sure that we reference Syck at the toplevel too so that
-# we can always load these kind of gemspecs.
-#
-if !defined?(Syck)
- Syck = YAML::Syck
-end
-
-# Now that we've got Syck setup in all the right places, store
-# a reference to the DefaultKey class inside Gem. We do this so that
-# if later on YAML, etc are redefined, we've still got a consistent
-# place to find the DefaultKey class for comparison.
-
-module Gem
- # for tests that change YAML::ENGINE
- remove_const :SyckDefaultKey if const_defined? :SyckDefaultKey
-
- SyckDefaultKey = YAML::Syck::DefaultKey
-end
-
-# :startdoc:
diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb
deleted file mode 100644
index 8dde20452e..0000000000
--- a/lib/rubygems/test_case.rb
+++ /dev/null
@@ -1,1584 +0,0 @@
-# frozen_string_literal: true
-
-require 'rubygems'
-
-# 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 'test-unit', '~> 3.0'
-rescue Gem::LoadError
-end
-
-begin
- require 'simplecov'
- SimpleCov.start do
- add_filter "/test/"
- add_filter "/bundler/"
- add_filter "/lib/rubygems/resolver/molinillo"
- end
-rescue LoadError
-end
-
-if File.exist?(bundler_gemspec)
- require_relative '../../bundler/lib/bundler'
-else
- require 'bundler'
-end
-
-require 'test/unit'
-
-ENV["JARS_SKIP"] = "true" if Gem.java_platform? # avoid unnecessary and noisy `jar-dependencies` post install hook
-
-require 'rubygems/deprecate'
-
-require 'fileutils'
-require 'pathname'
-require 'pp'
-require 'rubygems/package'
-require 'shellwords'
-require 'tmpdir'
-require 'uri'
-require 'zlib'
-require 'benchmark' # stdlib
-require 'rubygems/mock_gem_ui'
-
-module Gem
-
- ##
- # Allows setting the gem path searcher. This method is available when
- # requiring 'rubygems/test_case'
-
- def self.searcher=(searcher)
- @searcher = searcher
- end
-
- ##
- # Allows toggling Windows behavior. This method is available when requiring
- # 'rubygems/test_case'
-
- def self.win_platform=(val)
- @@win_platform = val
- end
-
- ##
- # Allows setting path to Ruby. This method is available when requiring
- # 'rubygems/test_case'
-
- def self.ruby=(ruby)
- @ruby = ruby
- end
-
- ##
- # When rubygems/test_case is required the default user interaction is a
- # MockGemUi.
-
- module DefaultUserInteraction
- @ui = Gem::MockGemUi.new
- end
-end
-
-require "rubygems/command"
-
-class Gem::Command
- ##
- # Allows resetting the hash of specific args per command. This method is
- # available when requiring 'rubygems/test_case'
-
- def self.specific_extra_args_hash=(value)
- @specific_extra_args_hash = value
- end
-end
-
-##
-# RubyGemTestCase provides a variety of methods for testing rubygems and
-# gem-related behavior in a sandbox. Through RubyGemTestCase you can install
-# and uninstall gems, fetch remote gems through a stub fetcher and be assured
-# your normal set of gems is not affected.
-
-class Gem::TestCase < Test::Unit::TestCase
- extend Gem::Deprecate
-
- attr_accessor :fetcher # :nodoc:
-
- attr_accessor :gem_repo # :nodoc:
-
- attr_accessor :uri # :nodoc:
-
- TEST_PATH = ENV.fetch('RUBYGEMS_TEST_PATH', File.expand_path('../../../test/rubygems', __FILE__))
-
- def assert_activate(expected, *specs)
- specs.each do |spec|
- case spec
- when String then
- Gem::Specification.find_by_name(spec).activate
- when Gem::Specification then
- spec.activate
- else
- flunk spec.inspect
- end
- end
-
- loaded = Gem.loaded_specs.values.map(&:full_name)
-
- assert_equal expected.sort, loaded.sort if expected
- end
-
- def assert_directory_exists(path, msg = nil)
- msg = build_message(msg, "Expected path '#{path}' to be a directory")
- assert_path_exist path
- assert File.directory?(path), msg
- end
-
- # https://github.com/seattlerb/minitest/blob/21d9e804b63c619f602f3f4ece6c71b48974707a/lib/minitest/assertions.rb#L188
- def _synchronize
- yield
- end
-
- # https://github.com/seattlerb/minitest/blob/21d9e804b63c619f602f3f4ece6c71b48974707a/lib/minitest/assertions.rb#L546
- def capture_subprocess_io
- _synchronize do
- begin
- require "tempfile"
-
- captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err")
-
- orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
- $stdout.reopen captured_stdout
- $stderr.reopen captured_stderr
-
- yield
-
- $stdout.rewind
- $stderr.rewind
-
- return captured_stdout.read, captured_stderr.read
- ensure
- captured_stdout.unlink
- captured_stderr.unlink
- $stdout.reopen orig_stdout
- $stderr.reopen orig_stderr
-
- orig_stdout.close
- orig_stderr.close
- captured_stdout.close
- captured_stderr.close
- end
- end
- end
-
- ##
- # Sets the ENABLE_SHARED entry in RbConfig::CONFIG to +value+ and restores
- # the original value when the block ends
-
- def enable_shared(value)
- enable_shared = RbConfig::CONFIG['ENABLE_SHARED']
- RbConfig::CONFIG['ENABLE_SHARED'] = value
-
- yield
- ensure
- if enable_shared
- RbConfig::CONFIG['enable_shared'] = enable_shared
- else
- RbConfig::CONFIG.delete 'enable_shared'
- end
- end
-
- ##
- # Sets the vendordir entry in RbConfig::CONFIG to +value+ and restores the
- # original value when the block ends
- #
- def vendordir(value)
- vendordir = RbConfig::CONFIG['vendordir']
-
- if value
- RbConfig::CONFIG['vendordir'] = value
- else
- RbConfig::CONFIG.delete 'vendordir'
- end
-
- yield
- ensure
- if vendordir
- RbConfig::CONFIG['vendordir'] = vendordir
- else
- RbConfig::CONFIG.delete 'vendordir'
- end
- end
-
- ##
- # Sets the bindir entry in RbConfig::CONFIG to +value+ and restores the
- # original value when the block ends
- #
- def bindir(value)
- with_clean_path_to_ruby do
- bindir = RbConfig::CONFIG['bindir']
-
- if value
- RbConfig::CONFIG['bindir'] = value
- else
- RbConfig::CONFIG.delete 'bindir'
- end
-
- begin
- yield
- ensure
- if bindir
- RbConfig::CONFIG['bindir'] = bindir
- else
- RbConfig::CONFIG.delete 'bindir'
- end
- end
- end
- end
-
- ##
- # Sets the EXEEXT entry in RbConfig::CONFIG to +value+ and restores the
- # original value when the block ends
- #
- def exeext(value)
- exeext = RbConfig::CONFIG['EXEEXT']
-
- if value
- RbConfig::CONFIG['EXEEXT'] = value
- else
- RbConfig::CONFIG.delete 'EXEEXT'
- end
-
- yield
- ensure
- if exeext
- RbConfig::CONFIG['EXEEXT'] = exeext
- else
- RbConfig::CONFIG.delete 'EXEEXT'
- end
- end
-
- def scan_make_command_lines(output)
- output.scan(/^#{Regexp.escape make_command}(?:[[:blank:]].*)?$/)
- end
-
- def parse_make_command_line(line)
- command, *args = line.shellsplit
-
- targets = []
- macros = {}
-
- args.each do |arg|
- case arg
- when /\A(\w+)=/
- macros[$1] = $'
- else
- targets << arg
- end
- end
-
- targets << '' if targets.empty?
-
- {
- :command => command,
- :targets => targets,
- :macros => macros,
- }
- end
-
- def assert_contains_make_command(target, output, msg = nil)
- if output.match(/\n/)
- msg = build_message(msg,
- "Expected output containing make command \"%s\", but was \n\nBEGIN_OF_OUTPUT\n%sEND_OF_OUTPUT" % [
- ('%s %s' % [make_command, target]).rstrip,
- output,
- ]
- )
- else
- msg = build_message(msg,
- 'Expected make command "%s": %s' % [
- ('%s %s' % [make_command, target]).rstrip,
- output,
- ]
- )
- end
-
- assert scan_make_command_lines(output).any? {|line|
- make = parse_make_command_line(line)
-
- if make[:targets].include?(target)
- yield make, line if block_given?
- true
- else
- false
- end
- }, msg
- end
-
- include Gem::DefaultUserInteraction
-
- ##
- # #setup prepares a sandboxed location to install gems. All installs are
- # directed to a temporary directory. All install plugins are removed.
- #
- # If the +RUBY+ environment variable is set the given path is used for
- # Gem::ruby. The local platform is set to <tt>i386-mswin32</tt> for Windows
- # or <tt>i686-darwin8.10.1</tt> otherwise.
-
- def setup
- @orig_env = ENV.to_hash
- @tmp = File.expand_path("tmp")
-
- FileUtils.mkdir_p @tmp
-
- ENV['GEM_VENDOR'] = nil
- ENV['GEMRC'] = nil
- ENV['XDG_CACHE_HOME'] = nil
- ENV['XDG_CONFIG_HOME'] = nil
- ENV['XDG_DATA_HOME'] = nil
- ENV['SOURCE_DATE_EPOCH'] = nil
- ENV['BUNDLER_VERSION'] = nil
-
- @current_dir = Dir.pwd
- @fetcher = nil
-
- @back_ui = Gem::DefaultUserInteraction.ui
- @ui = Gem::MockGemUi.new
- # This needs to be a new instance since we call use_ui(@ui) when we want to
- # capture output
- Gem::DefaultUserInteraction.ui = Gem::MockGemUi.new
-
- @tempdir = Dir.mktmpdir("test_rubygems_", @tmp)
- @tempdir.tap(&Gem::UNTAINT)
-
- ENV["TMPDIR"] = @tempdir
-
- @orig_SYSTEM_WIDE_CONFIG_FILE = Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE
- Gem::ConfigFile.send :remove_const, :SYSTEM_WIDE_CONFIG_FILE
- Gem::ConfigFile.send :const_set, :SYSTEM_WIDE_CONFIG_FILE,
- File.join(@tempdir, 'system-gemrc')
-
- @gemhome = File.join @tempdir, 'gemhome'
- @userhome = File.join @tempdir, 'userhome'
- ENV["GEM_SPEC_CACHE"] = File.join @tempdir, 'spec_cache'
-
- @orig_ruby = if ENV['RUBY']
- ruby = Gem.ruby
- Gem.ruby = ENV['RUBY']
- ruby
- end
-
- @git = ENV['GIT'] || (win_platform? ? 'git.exe' : 'git')
-
- Gem.ensure_gem_subdirectories @gemhome
- Gem.ensure_default_gem_subdirectories @gemhome
-
- @orig_LOAD_PATH = $LOAD_PATH.dup
- $LOAD_PATH.map! do |s|
- expand_path = File.realpath(s) rescue File.expand_path(s)
- if expand_path != s
- expand_path.tap(&Gem::UNTAINT)
- if s.instance_variable_defined?(:@gem_prelude_index)
- expand_path.instance_variable_set(:@gem_prelude_index, expand_path)
- end
- expand_path.freeze if s.frozen?
- s = expand_path
- end
- s
- end
-
- Dir.chdir @tempdir
-
- ENV['HOME'] = @userhome
- Gem.instance_variable_set :@config_file, nil
- Gem.instance_variable_set :@user_home, nil
- Gem.instance_variable_set :@config_home, nil
- Gem.instance_variable_set :@data_home, nil
- Gem.instance_variable_set :@gemdeps, nil
- Gem.instance_variable_set :@env_requirements_by_name, nil
- Gem.send :remove_instance_variable, :@ruby_version if
- Gem.instance_variables.include? :@ruby_version
-
- FileUtils.mkdir_p @userhome
-
- ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = PRIVATE_KEY_PASSPHRASE
-
- if Gem.java_platform?
- @orig_default_gem_home = RbConfig::CONFIG['default_gem_home']
- RbConfig::CONFIG['default_gem_home'] = @gemhome
- else
- Gem.instance_variable_set(:@default_dir, @gemhome)
- end
-
- @orig_bindir = RbConfig::CONFIG["bindir"]
- RbConfig::CONFIG["bindir"] = File.join @gemhome, "bin"
-
- Gem::Specification.unresolved_deps.clear
- Gem.use_paths(@gemhome)
-
- Gem.loaded_specs.clear
- Gem.instance_variable_set(:@activated_gem_paths, 0)
- Gem.clear_default_specs
- Bundler.reset!
-
- Gem.configuration.verbose = true
- Gem.configuration.update_sources = true
-
- Gem::RemoteFetcher.fetcher = Gem::FakeFetcher.new
-
- @gem_repo = "http://gems.example.com/"
- @uri = URI.parse @gem_repo
- Gem.sources.replace [@gem_repo]
-
- Gem.searcher = nil
- Gem::SpecFetcher.fetcher = nil
-
- @orig_arch = RbConfig::CONFIG['arch']
-
- if win_platform?
- util_set_arch 'i386-mswin32'
- else
- 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
-
- ##
- # #teardown restores the process to its original state and removes the
- # tempdir
-
- def teardown
- $LOAD_PATH.replace @orig_LOAD_PATH if @orig_LOAD_PATH
- if @orig_LOADED_FEATURES
- if @orig_LOAD_PATH
- ($LOADED_FEATURES - @orig_LOADED_FEATURES).each do |feat|
- $LOADED_FEATURES.delete(feat) if feat.start_with?(@tmp)
- end
- else
- $LOADED_FEATURES.replace @orig_LOADED_FEATURES
- end
- end
-
- RbConfig::CONFIG['arch'] = @orig_arch
-
- if defined? Gem::RemoteFetcher
- Gem::RemoteFetcher.fetcher = nil
- end
-
- Dir.chdir @current_dir
-
- FileUtils.rm_rf @tempdir
-
- ENV.replace(@orig_env)
-
- Gem::ConfigFile.send :remove_const, :SYSTEM_WIDE_CONFIG_FILE
- Gem::ConfigFile.send :const_set, :SYSTEM_WIDE_CONFIG_FILE,
- @orig_SYSTEM_WIDE_CONFIG_FILE
-
- Gem.ruby = @orig_ruby if @orig_ruby
-
- RbConfig::CONFIG['bindir'] = @orig_bindir
-
- if Gem.java_platform?
- RbConfig::CONFIG['default_gem_home'] = @orig_default_gem_home
- else
- Gem.instance_variable_set :@default_dir, nil
- end
-
- Gem::Specification._clear_load_cache
- Gem::Specification.unresolved_deps.clear
- Gem::refresh
-
- @orig_hooks.each do |name, hooks|
- Gem.send(name).replace hooks
- end
-
- @back_ui.close
- end
-
- def credential_setup
- @temp_cred = File.join(@userhome, '.gem', 'credentials')
- FileUtils.mkdir_p File.dirname(@temp_cred)
- File.write @temp_cred, ':rubygems_api_key: 701229f217cdf23b1344c7b4b54ca97'
- File.chmod 0600, @temp_cred
- end
-
- def credential_teardown
- FileUtils.rm_rf @temp_cred
- end
-
- def common_installer_setup
- common_installer_teardown
-
- Gem.post_build do |installer|
- @post_build_hook_arg = installer
- true
- end
-
- Gem.post_install do |installer|
- @post_install_hook_arg = installer
- end
-
- Gem.post_uninstall do |uninstaller|
- @post_uninstall_hook_arg = uninstaller
- end
-
- Gem.pre_install do |installer|
- @pre_install_hook_arg = installer
- true
- end
-
- Gem.pre_uninstall do |uninstaller|
- @pre_uninstall_hook_arg = uninstaller
- end
- end
-
- def common_installer_teardown
- Gem.post_build_hooks.clear
- Gem.post_install_hooks.clear
- Gem.done_installing_hooks.clear
- Gem.post_reset_hooks.clear
- Gem.post_uninstall_hooks.clear
- Gem.pre_install_hooks.clear
- Gem.pre_reset_hooks.clear
- Gem.pre_uninstall_hooks.clear
- end
-
- ##
- # A git_gem is used with a gem dependencies file. The gem created here
- # has no files, just a gem specification for the given +name+ and +version+.
- #
- # Yields the +specification+ to the block, if given
-
- def git_gem(name = 'a', version = 1)
- have_git?
-
- directory = File.join 'git', name
- directory = File.expand_path directory
-
- git_spec = Gem::Specification.new name, version do |specification|
- yield specification if block_given?
- end
-
- FileUtils.mkdir_p directory
-
- gemspec = "#{name}.gemspec"
-
- File.open File.join(directory, gemspec), 'w' do |io|
- io.write git_spec.to_ruby
- end
-
- head = nil
-
- Dir.chdir directory do
- unless File.exist? '.git'
- system @git, 'init', '--quiet'
- system @git, 'config', 'user.name', 'RubyGems Tests'
- system @git, 'config', 'user.email', 'rubygems@example'
- end
-
- system @git, 'add', gemspec
- system @git, 'commit', '-a', '-m', 'a non-empty commit message', '--quiet'
- head = Gem::Util.popen(@git, 'rev-parse', 'master').strip
- end
-
- return name, git_spec.version, directory, head
- end
-
- ##
- # Skips this test unless you have a git executable
-
- def have_git?
- return if in_path? @git
-
- skip 'cannot find git executable, use GIT environment variable to set'
- end
-
- def in_path?(executable) # :nodoc:
- return true if %r{\A([A-Z]:|/)} =~ executable and File.exist? executable
-
- ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
- File.exist? File.join directory, executable
- end
- end
-
- ##
- # Builds and installs the Gem::Specification +spec+
-
- def install_gem(spec, options = {})
- require 'rubygems/installer'
-
- gem = spec.cache_file
-
- unless File.exist? gem
- use_ui Gem::MockGemUi.new do
- Dir.chdir @tempdir do
- Gem::Package.build spec
- end
- end
-
- gem = File.join(@tempdir, File.basename(gem)).tap(&Gem::UNTAINT)
- end
-
- Gem::Installer.at(gem, options.merge({:wrappers => true})).install
- end
-
- ##
- # Builds and installs the Gem::Specification +spec+ into the user dir
-
- def install_gem_user(spec)
- install_gem spec, :user_install => true
- end
-
- ##
- # Uninstalls the Gem::Specification +spec+
- def uninstall_gem(spec)
- require 'rubygems/uninstaller'
-
- Class.new(Gem::Uninstaller) do
- def ask_if_ok(spec)
- true
- end
- end.new(spec.name, :executables => true, :user_install => true).uninstall
- end
-
- ##
- # Enables pretty-print for all tests
-
- def mu_pp(obj)
- s = String.new
- s = PP.pp obj, s
- s = s.force_encoding(Encoding.default_external)
- s.chomp
- end
-
- ##
- # Reads a Marshal file at +path+
-
- def read_cache(path)
- File.open path.dup.tap(&Gem::UNTAINT), 'rb' do |io|
- Marshal.load io.read
- end
- end
-
- ##
- # Reads a binary file at +path+
-
- def read_binary(path)
- Gem.read_binary path
- end
-
- ##
- # Writes a binary file to +path+ which is relative to +@gemhome+
-
- def write_file(path)
- path = File.join @gemhome, path unless Pathname.new(path).absolute?
- dir = File.dirname path
- FileUtils.mkdir_p dir unless File.directory? dir
-
- File.open path, 'wb' do |io|
- yield io if block_given?
- end
-
- path
- end
-
- def all_spec_names
- Gem::Specification.map(&:full_name)
- end
-
- ##
- # Creates a Gem::Specification with a minimum of extra work. +name+ and
- # +version+ are the gem's name and version, platform, author, email,
- # homepage, summary and description are defaulted. The specification is
- # yielded for customization.
- #
- # The gem is added to the installed gems in +@gemhome+ and the runtime.
- #
- # Use this with #write_file to build an installed gem.
-
- def quick_gem(name, version='2')
- require 'rubygems/specification'
-
- spec = Gem::Specification.new do |s|
- s.platform = Gem::Platform::RUBY
- s.name = name
- s.version = version
- s.author = 'A User'
- s.email = 'example@example.com'
- s.homepage = 'http://example.com'
- s.summary = "this is a summary"
- s.description = "This is a test description"
-
- yield(s) if block_given?
- end
-
- written_path = write_file spec.spec_file do |io|
- io.write spec.to_ruby_for_cache
- end
-
- spec.loaded_from = written_path
-
- Gem::Specification.reset
-
- return spec
- end
-
- ##
- # Builds a gem from +spec+ and places it in <tt>File.join @gemhome,
- # 'cache'</tt>. Automatically creates files based on +spec.files+
-
- def util_build_gem(spec)
- dir = spec.gem_dir
- FileUtils.mkdir_p dir
-
- Dir.chdir dir do
- spec.files.each do |file|
- next if File.exist? file
- FileUtils.mkdir_p File.dirname(file)
-
- File.open file, 'w' do |fp|
- fp.puts "# #{file}"
- end
- end
-
- use_ui Gem::MockGemUi.new do
- Gem::Package.build spec
- end
-
- cache = spec.cache_file
- FileUtils.mv File.basename(cache), cache
- end
- end
-
- def util_remove_gem(spec)
- FileUtils.rm_rf spec.cache_file
- FileUtils.rm_rf spec.spec_file
- end
-
- ##
- # Removes all installed gems from +@gemhome+.
-
- def util_clear_gems
- FileUtils.rm_rf File.join(@gemhome, "gems")
- FileUtils.mkdir File.join(@gemhome, "gems")
- FileUtils.rm_rf File.join(@gemhome, "specifications")
- FileUtils.mkdir File.join(@gemhome, "specifications")
- Gem::Specification.reset
- end
-
- ##
- # Install the provided specs
-
- def install_specs(*specs)
- specs.each do |spec|
- Gem::Installer.for_spec(spec, :force => true).install
- end
-
- Gem.searcher = nil
- end
-
- ##
- # Installs the provided default specs including writing the spec file
-
- def install_default_gems(*specs)
- specs.each do |spec|
- installer = Gem::Installer.for_spec(spec, :install_as_default => true)
- installer.install
- Gem.register_default_spec(spec)
- end
- end
-
- def loaded_spec_names
- Gem.loaded_specs.values.map(&:full_name).sort
- end
-
- def unresolved_names
- Gem::Specification.unresolved_deps.values.map(&:to_s).sort
- end
-
- def save_loaded_features
- old_loaded_features = $LOADED_FEATURES.dup
- yield
- ensure
- prefix = File.dirname(__FILE__) + "/"
- new_features = ($LOADED_FEATURES - old_loaded_features)
- old_loaded_features.concat(new_features.select {|f| f.rindex(prefix, 0) })
- $LOADED_FEATURES.replace old_loaded_features
- end
-
- def new_default_spec(name, version, deps = nil, *files)
- spec = util_spec name, version, deps
-
- spec.loaded_from = File.join(@gemhome, "specifications", "default", spec.spec_name)
- spec.files = files
-
- lib_dir = File.join(@tempdir, "default_gems", "lib")
- lib_dir.instance_variable_set(:@gem_prelude_index, lib_dir)
- Gem.instance_variable_set(:@default_gem_load_paths, [*Gem.send(:default_gem_load_paths), lib_dir])
- $LOAD_PATH.unshift(lib_dir)
- files.each do |file|
- rb_path = File.join(lib_dir, file)
- FileUtils.mkdir_p(File.dirname(rb_path))
- File.open(rb_path, "w") do |rb|
- rb << "# #{file}"
- end
- end
-
- spec
- end
-
- ##
- # Creates a spec with +name+, +version+. +deps+ can specify the dependency
- # or a +block+ can be given for full customization of the specification.
-
- def util_spec(name, version = 2, deps = nil, *files) # :yields: specification
- raise "deps or block, not both" if deps and block_given?
-
- spec = Gem::Specification.new do |s|
- s.platform = Gem::Platform::RUBY
- s.name = name
- s.version = version
- s.author = 'A User'
- s.email = 'example@example.com'
- s.homepage = 'http://example.com'
- s.summary = "this is a summary"
- s.description = "This is a test description"
-
- s.files.push(*files) unless files.empty?
-
- yield s if block_given?
- end
-
- if deps
- deps.keys.each do |n|
- spec.add_dependency n, (deps[n] || '>= 0')
- end
- end
-
- unless files.empty?
- write_file spec.spec_file do |io|
- io.write spec.to_ruby_for_cache
- end
-
- util_build_gem spec
-
- FileUtils.rm spec.spec_file
- end
-
- return spec
- end
-
- ##
- # Creates a gem with +name+, +version+ and +deps+. The specification will
- # be yielded before gem creation for customization. The gem will be placed
- # in <tt>File.join @tempdir, 'gems'</tt>. The specification and .gem file
- # location are returned.
-
- def util_gem(name, version, deps = nil, &block)
- if deps
- block = proc do |s|
- deps.keys.each do |n|
- s.add_dependency n, (deps[n] || '>= 0')
- end
- end
- end
-
- spec = quick_gem(name, version, &block)
-
- util_build_gem spec
-
- cache_file = File.join @tempdir, 'gems', "#{spec.original_name}.gem"
- FileUtils.mkdir_p File.dirname cache_file
- FileUtils.mv spec.cache_file, cache_file
- FileUtils.rm spec.spec_file
-
- spec.loaded_from = nil
-
- [spec, cache_file]
- end
-
- ##
- # Gzips +data+.
-
- def util_gzip(data)
- out = StringIO.new
-
- Zlib::GzipWriter.wrap out do |io|
- io.write data
- end
-
- out.string
- end
-
- ##
- # Creates several default gems which all have a lib/code.rb file. The gems
- # are not installed but are available in the cache dir.
- #
- # +@a1+:: gem a version 1, this is the best-described gem.
- # +@a2+:: gem a version 2
- # +@a3a:: gem a version 3.a
- # +@a_evil9+:: gem a_evil version 9, use this to ensure similarly-named gems
- # don't collide with a.
- # +@b2+:: gem b version 2
- # +@c1_2+:: gem c version 1.2
- # +@pl1+:: gem pl version 1, this gem has a legacy platform of i386-linux.
- #
- # Additional +prerelease+ gems may also be created:
- #
- # +@a2_pre+:: gem a version 2.a
- # TODO: nuke this and fix tests. this should speed up a lot
-
- def util_make_gems(prerelease = false)
- @a1 = quick_gem 'a', '1' do |s|
- s.files = %w[lib/code.rb]
- s.require_paths = %w[lib]
- s.date = Gem::Specification::TODAY - 86400
- s.homepage = 'http://a.example.com'
- s.email = %w[example@example.com example2@example.com]
- s.authors = %w[Example Example2]
- s.description = <<-DESC
-This line is really, really long. So long, in fact, that it is more than eighty characters long! The purpose of this line is for testing wrapping behavior because sometimes people don't wrap their text to eighty characters. Without the wrapping, the text might not look good in the RSS feed.
-
-Also, a list:
- * An entry that\'s actually kind of sort
- * an entry that\'s really long, which will probably get wrapped funny. That's ok, somebody wasn't thinking straight when they made it more than eighty characters.
- DESC
- end
-
- init = proc do |s|
- s.files = %w[lib/code.rb]
- s.require_paths = %w[lib]
- end
-
- @a2 = quick_gem('a', '2', &init)
- @a3a = quick_gem('a', '3.a', &init)
- @a_evil9 = quick_gem('a_evil', '9', &init)
- @b2 = quick_gem('b', '2', &init)
- @c1_2 = quick_gem('c', '1.2', &init)
- @x = quick_gem('x', '1', &init)
- @dep_x = quick_gem('dep_x', '1') do |s|
- s.files = %w[lib/code.rb]
- s.require_paths = %w[lib]
- s.add_dependency 'x', '>= 1'
- end
-
- @pl1 = quick_gem 'pl', '1' do |s| # l for legacy
- s.files = %w[lib/code.rb]
- s.require_paths = %w[lib]
- s.platform = Gem::Platform.new 'i386-linux'
- s.instance_variable_set :@original_platform, 'i386-linux'
- end
-
- if prerelease
- @a2_pre = quick_gem('a', '2.a', &init)
- write_file File.join(*%W[gems #{@a2_pre.original_name} lib code.rb])
- util_build_gem @a2_pre
- end
-
- write_file File.join(*%W[gems #{@a1.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@a2.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@a3a.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@a_evil9.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@b2.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@x.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@dep_x.original_name} lib code.rb])
-
- [@a1, @a2, @a3a, @a_evil9, @b2, @c1_2, @pl1, @x, @dep_x].each do |spec|
- util_build_gem spec
- end
-
- FileUtils.rm_r File.join(@gemhome, "gems", @pl1.original_name)
- end
-
- ##
- # Set the platform to +arch+
-
- def util_set_arch(arch)
- RbConfig::CONFIG['arch'] = arch
- platform = Gem::Platform.new arch
-
- Gem.instance_variable_set :@platforms, nil
- Gem::Platform.instance_variable_set :@local, nil
-
- yield if block_given?
-
- platform
- end
-
- ##
- # Add +spec+ to +@fetcher+ serving the data in the file +path+.
- # +repo+ indicates which repo to make +spec+ appear to be in.
-
- def add_to_fetcher(spec, path=nil, repo=@gem_repo)
- path ||= spec.cache_file
- @fetcher.data["#{@gem_repo}gems/#{spec.file_name}"] = read_binary(path)
- end
-
- ##
- # Sets up Gem::SpecFetcher to return information from the gems in +specs+.
-
- def util_setup_spec_fetcher(*specs)
- all_specs = Gem::Specification.to_a + specs
- Gem::Specification._resort! all_specs
-
- spec_fetcher = Gem::SpecFetcher.fetcher
-
- prerelease, all = all_specs.partition {|spec| spec.version.prerelease? }
- latest = Gem::Specification._latest_specs all_specs
-
- spec_fetcher.specs[@uri] = []
- all.each do |spec|
- spec_fetcher.specs[@uri] << spec.name_tuple
- end
-
- spec_fetcher.latest_specs[@uri] = []
- latest.each do |spec|
- spec_fetcher.latest_specs[@uri] << spec.name_tuple
- end
-
- spec_fetcher.prerelease_specs[@uri] = []
- prerelease.each do |spec|
- spec_fetcher.prerelease_specs[@uri] << spec.name_tuple
- end
-
- # HACK for test_download_to_cache
- unless Gem::RemoteFetcher === @fetcher
- v = Gem.marshal_version
-
- specs = all.map {|spec| spec.name_tuple }
- s_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic specs
-
- latest_specs = latest.map do |spec|
- spec.name_tuple
- end
-
- l_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic latest_specs
-
- prerelease_specs = prerelease.map {|spec| spec.name_tuple }
- p_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic prerelease_specs
-
- @fetcher.data["#{@gem_repo}specs.#{v}.gz"] = s_zip
- @fetcher.data["#{@gem_repo}latest_specs.#{v}.gz"] = l_zip
- @fetcher.data["#{@gem_repo}prerelease_specs.#{v}.gz"] = p_zip
-
- v = Gem.marshal_version
-
- all_specs.each do |spec|
- path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz"
- data = Marshal.dump spec
- data_deflate = Zlib::Deflate.deflate data
- @fetcher.data[path] = data_deflate
- end
- end
-
- nil # force errors
- end
-
- ##
- # Deflates +data+
-
- def util_zip(data)
- Zlib::Deflate.deflate data
- end
-
- def util_set_RUBY_VERSION(version, patchlevel = nil, revision = nil, description = nil, engine = "ruby", engine_version = nil)
- if Gem.instance_variables.include? :@ruby_version
- Gem.send :remove_instance_variable, :@ruby_version
- end
-
- @RUBY_VERSION = RUBY_VERSION
- @RUBY_PATCHLEVEL = RUBY_PATCHLEVEL if defined?(RUBY_PATCHLEVEL)
- @RUBY_REVISION = RUBY_REVISION if defined?(RUBY_REVISION)
- @RUBY_DESCRIPTION = RUBY_DESCRIPTION if defined?(RUBY_DESCRIPTION)
- @RUBY_ENGINE = RUBY_ENGINE
- @RUBY_ENGINE_VERSION = RUBY_ENGINE_VERSION if defined?(RUBY_ENGINE_VERSION)
-
- util_clear_RUBY_VERSION
-
- Object.const_set :RUBY_VERSION, version
- Object.const_set :RUBY_PATCHLEVEL, patchlevel if patchlevel
- Object.const_set :RUBY_REVISION, revision if revision
- Object.const_set :RUBY_DESCRIPTION, description if description
- Object.const_set :RUBY_ENGINE, engine
- Object.const_set :RUBY_ENGINE_VERSION, engine_version if engine_version
- end
-
- def util_restore_RUBY_VERSION
- util_clear_RUBY_VERSION
-
- Object.const_set :RUBY_VERSION, @RUBY_VERSION
- Object.const_set :RUBY_PATCHLEVEL, @RUBY_PATCHLEVEL if
- defined?(@RUBY_PATCHLEVEL)
- Object.const_set :RUBY_REVISION, @RUBY_REVISION if
- defined?(@RUBY_REVISION)
- Object.const_set :RUBY_DESCRIPTION, @RUBY_DESCRIPTION if
- defined?(@RUBY_DESCRIPTION)
- Object.const_set :RUBY_ENGINE, @RUBY_ENGINE
- Object.const_set :RUBY_ENGINE_VERSION, @RUBY_ENGINE_VERSION if
- defined?(@RUBY_ENGINE_VERSION)
- end
-
- def util_clear_RUBY_VERSION
- Object.send :remove_const, :RUBY_VERSION
- Object.send :remove_const, :RUBY_PATCHLEVEL if defined?(RUBY_PATCHLEVEL)
- Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
- Object.send :remove_const, :RUBY_DESCRIPTION if defined?(RUBY_DESCRIPTION)
- Object.send :remove_const, :RUBY_ENGINE
- Object.send :remove_const, :RUBY_ENGINE_VERSION if defined?(RUBY_ENGINE_VERSION)
- end
-
- ##
- # Is this test being run on a Windows platform?
-
- def self.win_platform?
- Gem.win_platform?
- end
-
- ##
- # Is this test being run on a Windows platform?
-
- def win_platform?
- Gem.win_platform?
- end
-
- ##
- # Is this test being run on a Java platform?
-
- def self.java_platform?
- Gem.java_platform?
- end
-
- ##
- # Is this test being run on a Java platform?
-
- def java_platform?
- Gem.java_platform?
- end
-
- ##
- # Returns whether or not we're on a version of Ruby built with VC++ (or
- # Borland) versus Cygwin, Mingw, etc.
-
- def self.vc_windows?
- RUBY_PLATFORM.match('mswin')
- end
-
- ##
- # Returns whether or not we're on a version of Ruby built with VC++ (or
- # Borland) versus Cygwin, Mingw, etc.
-
- def vc_windows?
- RUBY_PLATFORM.match('mswin')
- end
-
- ##
- # Returns the make command for the current platform. For versions of Ruby
- # built on MS Windows with VC++ or Borland it will return 'nmake'. On all
- # other platforms, including Cygwin, it will return 'make'.
-
- def self.make_command
- ENV["make"] || ENV["MAKE"] || (vc_windows? ? 'nmake' : 'make')
- end
-
- ##
- # Returns the make command for the current platform. For versions of Ruby
- # built on MS Windows with VC++ or Borland it will return 'nmake'. On all
- # other platforms, including Cygwin, it will return 'make'.
-
- def make_command
- ENV["make"] || ENV["MAKE"] || (vc_windows? ? 'nmake' : 'make')
- end
-
- ##
- # Returns whether or not the nmake command could be found.
-
- def nmake_found?
- system('nmake /? 1>NUL 2>&1')
- end
-
- # In case we're building docs in a background process, this method waits for
- # that process to exit (or if it's already been reaped, or never happened,
- # swallows the Errno::ECHILD error).
- def wait_for_child_process_to_exit
- Process.wait if Process.respond_to?(:fork)
- rescue Errno::ECHILD
- end
-
- ##
- # Allows tests to use a random (but controlled) port number instead of
- # a hardcoded one. This helps CI tools when running parallels builds on
- # the same builder slave.
-
- def self.process_based_port
- @@process_based_port ||= 8000 + $$ % 1000
- end
-
- ##
- # See ::process_based_port
-
- def process_based_port
- self.class.process_based_port
- end
-
- ##
- # Allows the proper version of +rake+ to be used for the test.
-
- def build_rake_in(good=true)
- gem_ruby = Gem.ruby
- Gem.ruby = self.class.rubybin
- env_rake = ENV["rake"]
- rake = (good ? @@good_rake : @@bad_rake)
- ENV["rake"] = rake
- yield rake
- ensure
- Gem.ruby = gem_ruby
- if env_rake
- ENV["rake"] = env_rake
- else
- ENV.delete("rake")
- end
- end
-
- ##
- # Finds the path to the Ruby executable
-
- def self.rubybin
- ruby = ENV["RUBY"]
- return ruby if ruby
- ruby = "ruby"
- rubyexe = "#{ruby}.exe"
-
- 3.times do
- if File.exist? ruby and File.executable? ruby and !File.directory? ruby
- return File.expand_path(ruby)
- end
- if File.exist? rubyexe and File.executable? rubyexe
- return File.expand_path(rubyexe)
- end
- ruby = File.join("..", ruby)
- end
-
- begin
- Gem.ruby
- rescue LoadError
- "ruby"
- end
- end
-
- def ruby_with_rubygems_in_load_path
- [Gem.ruby, "-I", File.expand_path("..", __dir__)]
- end
-
- def with_clean_path_to_ruby
- orig_ruby = Gem.ruby
-
- Gem.instance_variable_set :@ruby, nil
-
- yield
- ensure
- Gem.instance_variable_set :@ruby, orig_ruby
- end
-
- class << self
- # :nodoc:
- ##
- # Return the join path, with escaping backticks, dollars, and
- # double-quotes. Unlike `shellescape`, equal-sign is not escaped.
-
- private
-
- def escape_path(*path)
- path = File.join(*path)
- if %r{\A[-+:/=@,.\w]+\z} =~ path
- path
- else
- "\"#{path.gsub(/[`$"]/, '\\&')}\""
- end
- end
- end
-
- @@good_rake = "#{rubybin} #{escape_path(TEST_PATH, 'good_rake.rb')}"
- @@bad_rake = "#{rubybin} #{escape_path(TEST_PATH, 'bad_rake.rb')}"
-
- ##
- # Construct a new Gem::Dependency.
-
- def dep(name, *requirements)
- Gem::Dependency.new name, *requirements
- end
-
- ##
- # Constructs a Gem::Resolver::DependencyRequest from a
- # Gem::Dependency +dep+, a +from_name+ and +from_version+ requesting the
- # dependency and a +parent+ DependencyRequest
-
- def dependency_request(dep, from_name, from_version, parent = nil)
- remote = Gem::Source.new @uri
-
- unless parent
- parent_dep = dep from_name, from_version
- parent = Gem::Resolver::DependencyRequest.new parent_dep, nil
- end
-
- spec = Gem::Resolver::IndexSpecification.new \
- nil, from_name, from_version, remote, Gem::Platform::RUBY
- activation = Gem::Resolver::ActivationRequest.new spec, parent
-
- Gem::Resolver::DependencyRequest.new dep, activation
- end
-
- ##
- # Constructs a new Gem::Requirement.
-
- def req(*requirements)
- return requirements.first if Gem::Requirement === requirements.first
- Gem::Requirement.create requirements
- end
-
- ##
- # Constructs a new Gem::Specification.
-
- def spec(name, version, &block)
- Gem::Specification.new name, v(version), &block
- end
-
- ##
- # Creates a SpecFetcher pre-filled with the gems or specs defined in the
- # block.
- #
- # Yields a +fetcher+ object that responds to +spec+ and +gem+. +spec+ adds
- # a specification to the SpecFetcher while +gem+ adds both a specification
- # and the gem data to the RemoteFetcher so the built gem can be downloaded.
- #
- # If only the a-3 gem is supposed to be downloaded you can save setup
- # time by creating only specs for the other versions:
- #
- # spec_fetcher do |fetcher|
- # fetcher.spec 'a', 1
- # fetcher.spec 'a', 2, 'b' => 3 # dependency on b = 3
- # fetcher.gem 'a', 3 do |spec|
- # # spec is a Gem::Specification
- # # ...
- # end
- # end
-
- def spec_fetcher(repository = @gem_repo)
- Gem::TestCase::SpecFetcherSetup.declare self, repository do |spec_fetcher_setup|
- yield spec_fetcher_setup if block_given?
- end
- end
-
- ##
- # Construct a new Gem::Version.
-
- def v(string)
- Gem::Version.create string
- end
-
- ##
- # A vendor_gem is used with a gem dependencies file. The gem created here
- # has no files, just a gem specification for the given +name+ and +version+.
- #
- # Yields the +specification+ to the block, if given
-
- def vendor_gem(name = 'a', version = 1)
- directory = File.join 'vendor', name
-
- FileUtils.mkdir_p directory
-
- save_gemspec name, version, directory
- end
-
- ##
- # create_gemspec creates gem specification in given +directory+ or '.'
- # for the given +name+ and +version+.
- #
- # Yields the +specification+ to the block, if given
-
- def save_gemspec(name = 'a', version = 1, directory = '.')
- vendor_spec = Gem::Specification.new name, version do |specification|
- yield specification if block_given?
- end
-
- File.open File.join(directory, "#{name}.gemspec"), 'w' do |io|
- io.write vendor_spec.to_ruby
- end
-
- return name, vendor_spec.version, directory
- end
-
- ##
- # The StaticSet is a static set of gem specifications used for testing only.
- # It is available by requiring Gem::TestCase.
-
- class StaticSet < Gem::Resolver::Set
- ##
- # A StaticSet ignores remote because it has a fixed set of gems.
-
- attr_accessor :remote
-
- ##
- # Creates a new StaticSet for the given +specs+
-
- def initialize(specs)
- super()
-
- @specs = specs
-
- @remote = true
- end
-
- ##
- # Adds +spec+ to this set.
-
- def add(spec)
- @specs << spec
- end
-
- ##
- # Finds +dep+ in this set.
-
- def find_spec(dep)
- @specs.reverse_each do |s|
- return s if dep.matches_spec? s
- end
- end
-
- ##
- # Finds all gems matching +dep+ in this set.
-
- def find_all(dep)
- @specs.find_all {|s| dep.match? s, @prerelease }
- end
-
- ##
- # Loads a Gem::Specification from this set which has the given +name+,
- # version +ver+, +platform+. The +source+ is ignored.
-
- def load_spec(name, ver, platform, source)
- dep = Gem::Dependency.new name, ver
- spec = find_spec dep
-
- Gem::Specification.new spec.name, spec.version do |s|
- s.platform = spec.platform
- end
- end
-
- def prefetch(reqs) # :nodoc:
- end
- end
-
- ##
- # Loads certificate named +cert_name+ from <tt>test/rubygems/</tt>.
-
- def self.load_cert(cert_name)
- cert_file = cert_path cert_name
-
- cert = File.read cert_file
-
- OpenSSL::X509::Certificate.new cert
- end
-
- ##
- # Returns the path to the certificate named +cert_name+ from
- # <tt>test/rubygems/</tt>.
-
- def self.cert_path(cert_name)
- if 32 == (Time.at(2**32) rescue 32)
- cert_file = "#{TEST_PATH}/#{cert_name}_cert_32.pem"
-
- return cert_file if File.exist? cert_file
- end
-
- "#{TEST_PATH}/#{cert_name}_cert.pem"
- end
-
- ##
- # Loads an RSA private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt>
-
- def self.load_key(key_name, passphrase = nil)
- key_file = key_path key_name
-
- key = File.read key_file
-
- OpenSSL::PKey::RSA.new key, passphrase
- end
-
- ##
- # Returns the path to the key named +key_name+ from <tt>test/rubygems</tt>
-
- def self.key_path(key_name)
- "#{TEST_PATH}/#{key_name}_key.pem"
- end
-
- # :stopdoc:
- # only available in RubyGems tests
-
- PRIVATE_KEY_PASSPHRASE = 'Foo bar'.freeze
-
- begin
- PRIVATE_KEY = load_key 'private'
- PRIVATE_KEY_PATH = key_path 'private'
-
- # ENCRYPTED_PRIVATE_KEY is PRIVATE_KEY encrypted with PRIVATE_KEY_PASSPHRASE
- ENCRYPTED_PRIVATE_KEY = load_key 'encrypted_private', PRIVATE_KEY_PASSPHRASE
- ENCRYPTED_PRIVATE_KEY_PATH = key_path 'encrypted_private'
-
- PUBLIC_KEY = PRIVATE_KEY.public_key
-
- PUBLIC_CERT = load_cert 'public'
- PUBLIC_CERT_PATH = cert_path 'public'
- rescue Errno::ENOENT
- PRIVATE_KEY = nil
- PUBLIC_KEY = nil
- PUBLIC_CERT = nil
- end if Gem::HAVE_OPENSSL
-end
-
-# https://github.com/seattlerb/minitest/blob/13c48a03d84a2a87855a4de0c959f96800100357/lib/minitest/mock.rb#L192
-class Object
- def stub(name, val_or_callable, *block_args)
- new_name = "__minitest_stub__#{name}"
-
- metaclass = class << self; self; end
-
- if respond_to? name and not methods.map(&:to_s).include? name.to_s
- metaclass.send :define_method, name do |*args|
- super(*args)
- end
- end
-
- metaclass.send :alias_method, new_name, name
-
- metaclass.send :define_method, name do |*args, &blk|
- if val_or_callable.respond_to? :call
- val_or_callable.call(*args, &blk)
- else
- blk.call(*block_args) if blk
- val_or_callable
- end
- end
-
- metaclass.send(:ruby2_keywords, name) if metaclass.respond_to?(:ruby2_keywords, true)
-
- yield self
- ensure
- metaclass.send :undef_method, name
- metaclass.send :alias_method, name, new_name
- metaclass.send :undef_method, new_name
- end
-end
-
-require 'rubygems/test_utilities'
diff --git a/lib/rubygems/test_utilities.rb b/lib/rubygems/test_utilities.rb
deleted file mode 100644
index 08faef6578..0000000000
--- a/lib/rubygems/test_utilities.rb
+++ /dev/null
@@ -1,373 +0,0 @@
-# frozen_string_literal: true
-require 'tempfile'
-require 'rubygems'
-require 'rubygems/remote_fetcher'
-
-##
-# A fake Gem::RemoteFetcher for use in tests or to avoid real live HTTP
-# requests when testing code that uses RubyGems.
-#
-# Example:
-#
-# @fetcher = Gem::FakeFetcher.new
-# @fetcher.data['http://gems.example.com/yaml'] = source_index.to_yaml
-# Gem::RemoteFetcher.fetcher = @fetcher
-#
-# use nested array if multiple response is needed
-#
-# @fetcher.data['http://gems.example.com/sequence'] = [['Success', 200, 'OK'], ['Failed', 401, 'Unauthorized']]
-#
-# @fetcher.fetch_path('http://gems.example.com/sequence') # => ['Success', 200, 'OK']
-# @fetcher.fetch_path('http://gems.example.com/sequence') # => ['Failed', 401, 'Unauthorized']
-#
-# # invoke RubyGems code
-#
-# paths = @fetcher.paths
-# assert_equal 'http://gems.example.com/yaml', paths.shift
-# assert paths.empty?, paths.join(', ')
-#
-# See RubyGems' tests for more examples of FakeFetcher.
-
-class Gem::FakeFetcher
- attr_reader :data
- attr_reader :last_request
- attr_accessor :paths
-
- def initialize
- @data = {}
- @paths = []
- end
-
- def find_data(path)
- return Gem.read_binary path.path if URI === path and 'file' == path.scheme
-
- if URI === path and "URI::#{path.scheme.upcase}" != path.class.name
- raise ArgumentError,
- "mismatch for scheme #{path.scheme} and class #{path.class}"
- end
-
- path = path.to_s
- @paths << path
- raise ArgumentError, 'need full URI' unless path.start_with?("https://", "http://")
-
- unless @data.key? path
- raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
- end
-
- if @data[path].kind_of?(Array) && @data[path].first.kind_of?(Array)
- @data[path].shift
- else
- @data[path]
- end
- end
-
- def fetch_path(path, mtime = nil, head = false)
- data = find_data(path)
-
- if data.respond_to?(:call)
- data.call
- else
- if path.to_s.end_with?(".gz") and not data.nil? and not data.empty?
- data = Gem::Util.gunzip data
- end
- data
- end
- end
-
- def cache_update_path(uri, path = nil, update = true)
- if data = fetch_path(uri)
- File.open(path, 'wb') {|io| io.write data } if path and update
- data
- else
- Gem.read_binary(path) if path
- end
- end
-
- # Thanks, FakeWeb!
- def open_uri_or_path(path)
- data = find_data(path)
- body, code, msg = data
-
- response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
- response.instance_variable_set(:@body, body)
- response.instance_variable_set(:@read, true)
- response
- end
-
- def request(uri, request_class, last_modified = nil)
- data = find_data(uri)
- body, code, msg = (data.respond_to?(:call) ? data.call : data)
-
- @last_request = request_class.new uri.request_uri
- yield @last_request if block_given?
-
- response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
- response.instance_variable_set(:@body, body)
- response.instance_variable_set(:@read, true)
- response
- end
-
- def pretty_print(q) # :nodoc:
- q.group 2, '[FakeFetcher', ']' do
- q.breakable
- q.text 'URIs:'
-
- q.breakable
- q.pp @data.keys
- end
- end
-
- def fetch_size(path)
- path = path.to_s
- @paths << path
-
- raise ArgumentError, 'need full URI' unless path =~ %r{^http://}
-
- unless @data.key? path
- raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
- end
-
- data = @data[path]
-
- data.respond_to?(:call) ? data.call : data.length
- end
-
- def download(spec, source_uri, install_dir = Gem.dir)
- name = File.basename spec.cache_file
- path = if Dir.pwd == install_dir # see fetch_command
- install_dir
- else
- File.join install_dir, "cache"
- end
-
- path = File.join path, name
-
- if source_uri =~ /^http/
- File.open(path, "wb") do |f|
- f.write fetch_path(File.join(source_uri, "gems", name))
- end
- else
- FileUtils.cp source_uri, path
- end
-
- path
- end
-
- def download_to_cache(dependency)
- found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency
-
- return if found.empty?
-
- spec, source = found.first
-
- download spec, source.uri.to_s
- end
-end
-
-# :stopdoc:
-class Gem::RemoteFetcher
- def self.fetcher=(fetcher)
- @fetcher = fetcher
- end
-end
-# :startdoc:
-
-##
-# The SpecFetcherSetup allows easy setup of a remote source in RubyGems tests:
-#
-# spec_fetcher do |f|
-# f.gem 'a', 1
-# f.spec 'a', 2
-# f.gem 'b', 1' 'a' => '~> 1.0'
-# end
-#
-# The above declaration creates two gems, a-1 and b-1, with a dependency from
-# b to a. The declaration creates an additional spec a-2, but no gem for it
-# (so it cannot be installed).
-#
-# After the gems are created they are removed from Gem.dir.
-
-class Gem::TestCase::SpecFetcherSetup
- ##
- # Executes a SpecFetcher setup block. Yields an instance then creates the
- # gems and specifications defined in the instance.
-
- def self.declare(test, repository)
- setup = new test, repository
-
- yield setup
-
- setup.execute
- end
-
- def initialize(test, repository) # :nodoc:
- @test = test
- @repository = repository
-
- @gems = {}
- @downloaded = []
- @installed = []
- @operations = []
- end
-
- ##
- # Returns a Hash of created Specification full names and the corresponding
- # Specification.
-
- def created_specs
- created = {}
-
- @gems.keys.each do |spec|
- created[spec.full_name] = spec
- end
-
- created
- end
-
- ##
- # Creates any defined gems or specifications
-
- def execute # :nodoc:
- execute_operations
-
- setup_fetcher
-
- created_specs
- end
-
- def execute_operations # :nodoc:
- @operations.each do |operation, *arguments|
- block = arguments.pop
- case operation
- when :gem then
- spec, gem = @test.util_gem(*arguments, &block)
-
- write_spec spec
-
- @gems[spec] = gem
- @installed << spec
- when :download then
- spec, gem = @test.util_gem(*arguments, &block)
-
- @gems[spec] = gem
- @downloaded << spec
- when :spec then
- spec = @test.util_spec(*arguments, &block)
-
- write_spec spec
-
- @gems[spec] = nil
- @installed << spec
- end
- end
- end
-
- ##
- # Creates a gem with +name+, +version+ and +deps+. The created gem can be
- # downloaded and installed.
- #
- # The specification will be yielded before gem creation for customization,
- # but only the block or the dependencies may be set, not both.
-
- def gem(name, version, dependencies = nil, &block)
- @operations << [:gem, name, version, dependencies, block]
- end
-
- ##
- # Creates a gem with +name+, +version+ and +deps+. The created gem is
- # downloaded in to the cache directory but is not installed
- #
- # The specification will be yielded before gem creation for customization,
- # but only the block or the dependencies may be set, not both.
-
- def download(name, version, dependencies = nil, &block)
- @operations << [:download, name, version, dependencies, block]
- end
-
- ##
- # Creates a legacy platform spec with the name 'pl' and version 1
-
- def legacy_platform
- spec 'pl', 1 do |s|
- s.platform = Gem::Platform.new 'i386-linux'
- s.instance_variable_set :@original_platform, 'i386-linux'
- end
- end
-
- def setup_fetcher # :nodoc:
- require 'zlib'
- require 'socket'
- require 'rubygems/remote_fetcher'
-
- unless @test.fetcher
- @test.fetcher = Gem::FakeFetcher.new
- Gem::RemoteFetcher.fetcher = @test.fetcher
- end
-
- Gem::Specification.reset
-
- begin
- gem_repo, @test.gem_repo = @test.gem_repo, @repository
- @test.uri = URI @repository
-
- @test.util_setup_spec_fetcher(*@downloaded)
- ensure
- @test.gem_repo = gem_repo
- @test.uri = URI gem_repo
- end
-
- @gems.each do |spec, gem|
- next unless gem
-
- @test.fetcher.data["#{@repository}gems/#{spec.file_name}"] =
- Gem.read_binary(gem)
-
- FileUtils.cp gem, spec.cache_file
- end
- end
-
- ##
- # Creates a spec with +name+, +version+ and +deps+. The created gem can be
- # downloaded and installed.
- #
- # The specification will be yielded before creation for customization,
- # but only the block or the dependencies may be set, not both.
-
- def spec(name, version, dependencies = nil, &block)
- @operations << [:spec, name, version, dependencies, block]
- end
-
- def write_spec(spec) # :nodoc:
- File.open spec.spec_file, 'w' do |io|
- io.write spec.to_ruby_for_cache
- end
- end
-end
-
-##
-# A StringIO duck-typed class that uses Tempfile instead of String as the
-# backing store.
-#
-# This is available when rubygems/test_utilities is required.
-#--
-# This class was added to flush out problems in Rubinius' IO implementation.
-
-class TempIO < Tempfile
- ##
- # Creates a new TempIO that will be initialized to contain +string+.
-
- def initialize(string = '')
- super "TempIO"
- binmode
- write string
- rewind
- end
-
- ##
- # The content of the TempIO as a String.
-
- def string
- flush
- Gem.read_binary path
- end
-end
diff --git a/lib/rubygems/text.rb b/lib/rubygems/text.rb
index acf25a0bcd..da0795b771 100644
--- a/lib/rubygems/text.rb
+++ b/lib/rubygems/text.rb
@@ -4,12 +4,11 @@
# A collection of text-wrangling methods
module Gem::Text
-
##
# Remove any non-printable characters and make the text suitable for
# printing.
def clean_text(text)
- text.gsub(/[\000-\b\v-\f\016-\037\177]/, ".".freeze)
+ text.gsub(/[\000-\b\v-\f\016-\037\177]/, ".")
end
def truncate_text(text, description, max_length = 100_000)
@@ -51,7 +50,7 @@ module Gem::Text
# Returns a value representing the "cost" of transforming str1 into str2
# Vendored version of DidYouMean::Levenshtein.distance from the ruby/did_you_mean gem @ 1.4.0
- # https://git.io/JJgZI
+ # https://github.com/ruby/did_you_mean/blob/2ddf39b874808685965dbc47d344cf6c7651807c/lib/did_you_mean/levenshtein.rb#L7-L37
def levenshtein_distance(str1, str2)
n = str1.length
m = str2.length
@@ -67,7 +66,7 @@ module Gem::Text
str1.each_codepoint.with_index(1) do |char1, i|
j = 0
while j < m
- cost = (char1 == str2_codepoints[j]) ? 0 : 1
+ cost = char1 == str2_codepoints[j] ? 0 : 1
x = min3(
d[j + 1] + 1, # insertion
i + 1, # deletion
diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb
index 51ac3494f3..c96df2a085 100644
--- a/lib/rubygems/uninstaller.rb
+++ b/lib/rubygems/uninstaller.rb
@@ -1,16 +1,17 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'fileutils'
-require 'rubygems'
-require 'rubygems/installer_uninstaller_utils'
-require 'rubygems/dependency_list'
-require 'rubygems/rdoc'
-require 'rubygems/user_interaction'
+require "fileutils"
+require_relative "../rubygems"
+require_relative "installer_uninstaller_utils"
+require_relative "dependency_list"
+require_relative "rdoc"
+require_relative "user_interaction"
##
# An Uninstaller.
@@ -45,7 +46,7 @@ class Gem::Uninstaller
# Constructs an uninstaller that will uninstall +gem+
def initialize(gem, options = {})
- # TODO document the valid options
+ # TODO: document the valid options
@gem = gem
@version = options[:version] || Gem::Requirement.default
@gem_home = File.realpath(options[:install_dir] || Gem.dir)
@@ -70,6 +71,9 @@ class Gem::Uninstaller
# only add user directory if install_dir is not set
@user_install = false
@user_install = options[:user_install] unless options[:install_dir]
+
+ # Optimization: populated during #uninstall
+ @default_specs_matching_uninstall_params = []
end
##
@@ -95,17 +99,13 @@ class Gem::Uninstaller
raise Gem::InstallError, "gem #{@gem.inspect} is not installed"
end
- default_specs, list = list.partition do |spec|
- spec.default_gem?
- end
-
- default_specs.each do |default_spec|
- say "Gem #{default_spec.full_name} cannot be uninstalled because it is a default gem"
- end
+ default_specs, list = list.partition(&:default_gem?)
+ warn_cannot_uninstall_default_gems(default_specs - list)
+ @default_specs_matching_uninstall_params = default_specs
list, other_repo_specs = list.partition do |spec|
- @gem_home == spec.base_dir or
- (@user_install and spec.base_dir == Gem.user_dir)
+ @gem_home == spec.base_dir ||
+ (@user_install && spec.base_dir == Gem.user_dir)
end
list.sort!
@@ -113,7 +113,7 @@ class Gem::Uninstaller
if list.empty?
return unless other_repo_specs.any?
- other_repos = other_repo_specs.map {|spec| spec.base_dir }.uniq
+ other_repos = other_repo_specs.map(&:base_dir).uniq
message = ["#{@gem} is not installed in GEM_HOME, try:"]
message.concat other_repos.map {|repo|
@@ -125,7 +125,7 @@ class Gem::Uninstaller
remove_all list
elsif list.size > 1
- gem_names = list.map {|gem| gem.full_name }
+ gem_names = list.map(&:full_name)
gem_names << "All versions"
say
@@ -133,7 +133,7 @@ class Gem::Uninstaller
if index == list.size
remove_all list
- elsif index >= 0 && index < list.size
+ elsif index && index >= 0 && index < list.size
uninstall_gem list[index]
else
say "Error: must enter a number [1-#{list.size + 1}]"
@@ -199,13 +199,13 @@ class Gem::Uninstaller
executables = executables.map {|exec| formatted_program_filename exec }
remove = if @force_executables.nil?
- ask_yes_no("Remove executables:\n" +
- "\t#{executables.join ', '}\n\n" +
- "in addition to the gem?",
- true)
- else
- @force_executables
- end
+ ask_yes_no("Remove executables:\n" \
+ "\t#{executables.join ", "}\n\n" \
+ "in addition to the gem?",
+ true)
+ else
+ @force_executables
+ end
if remove
bin_dir = @bin_dir || Gem.bindir(spec.base_dir)
@@ -238,10 +238,10 @@ class Gem::Uninstaller
# spec:: the spec of the gem to be uninstalled
def remove(spec)
- unless path_ok?(@gem_home, spec) or
- (@user_install and path_ok?(Gem.user_dir, spec))
+ unless path_ok?(@gem_home, spec) ||
+ (@user_install && path_ok?(Gem.user_dir, spec))
e = Gem::GemNotInHomeException.new \
- "Gem '#{spec.full_name}' is not installed in directory #{@gem_home}"
+ "Gem '#{spec.full_name}' is not installed in directory #{@gem_home}"
e.spec = spec
raise e
@@ -261,7 +261,10 @@ class Gem::Uninstaller
safe_delete { FileUtils.rm_r gem }
- Gem::RDoc.new(spec).remove
+ begin
+ Gem::RDoc.new(spec).remove
+ rescue NameError
+ end
gemspec = spec.spec_file
@@ -270,7 +273,7 @@ class Gem::Uninstaller
end
safe_delete { FileUtils.rm_r gemspec }
- say "Successfully uninstalled #{spec.full_name}"
+ announce_deletion_of(spec)
Gem::Specification.reset
end
@@ -298,8 +301,8 @@ class Gem::Uninstaller
# Is +spec+ in +gem_dir+?
def path_ok?(gem_dir, spec)
- full_path = File.join gem_dir, 'gems', spec.full_name
- original_path = File.join gem_dir, 'gems', spec.original_name
+ full_path = File.join gem_dir, "gems", spec.full_name
+ original_path = File.join gem_dir, "gems", spec.original_name
full_path == spec.full_gem_path || original_path == spec.full_gem_path
end
@@ -328,24 +331,24 @@ class Gem::Uninstaller
# Asks if it is OK to remove +spec+. Returns true if it is OK.
def ask_if_ok(spec) # :nodoc:
- msg = ['']
- msg << 'You have requested to uninstall the gem:'
+ msg = [""]
+ msg << "You have requested to uninstall the gem:"
msg << "\t#{spec.full_name}"
- msg << ''
+ msg << ""
siblings = Gem::Specification.select do |s|
s.name == spec.name && s.full_name != spec.full_name
end
- spec.dependent_gems(@check_dev).each do |dep_spec, dep, satlist|
+ spec.dependent_gems(@check_dev).each do |dep_spec, dep, _satlist|
unless siblings.any? {|s| s.satisfies_requirement? dep }
msg << "#{dep_spec.name}-#{dep_spec.version} depends on #{dep}"
end
end
- msg << 'If you remove this gem, these dependencies will not be met.'
- msg << 'Continue with Uninstall?'
- return ask_yes_no(msg.join("\n"), false)
+ msg << "If you remove this gem, these dependencies will not be met."
+ msg << "Continue with Uninstall?"
+ ask_yes_no(msg.join("\n"), false)
end
##
@@ -356,7 +359,7 @@ class Gem::Uninstaller
# of what it did for us to find rather than trying to recreate
# it again.
if @format_executable
- require 'rubygems/installer'
+ require_relative "installer"
Gem::Installer.exec_format % File.basename(filename)
else
filename
@@ -373,4 +376,34 @@ class Gem::Uninstaller
raise e
end
+
+ private
+
+ def announce_deletion_of(spec)
+ name = spec.full_name
+ say "Successfully uninstalled #{name}"
+ if default_spec_matches?(spec)
+ say(
+ "There was both a regular copy and a default copy of #{name}. The " \
+ "regular copy was successfully uninstalled, but the default copy " \
+ "was left around because default gems can't be removed."
+ )
+ end
+ end
+
+ # @return true if the specs of any default gems are `==` to the given `spec`.
+ def default_spec_matches?(spec)
+ !default_specs_that_match(spec).empty?
+ end
+
+ # @return [Array] specs of default gems that are `==` to the given `spec`.
+ def default_specs_that_match(spec)
+ @default_specs_matching_uninstall_params.select {|default_spec| spec == default_spec }
+ end
+
+ def warn_cannot_uninstall_default_gems(specs)
+ specs.each do |spec|
+ say "Gem #{spec.full_name} cannot be uninstalled because it is a default gem"
+ end
+ end
end
diff --git a/lib/rubygems/update_suggestion.rb b/lib/rubygems/update_suggestion.rb
new file mode 100644
index 0000000000..6f3ec5f493
--- /dev/null
+++ b/lib/rubygems/update_suggestion.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+##
+# Mixin methods for Gem::Command to promote available RubyGems update
+
+module Gem::UpdateSuggestion
+ ONE_WEEK = 7 * 24 * 60 * 60
+
+ ##
+ # Message to promote available RubyGems update with related gem update command.
+
+ def update_suggestion
+ <<-MESSAGE
+
+A new release of RubyGems is available: #{Gem.rubygems_version} → #{Gem.latest_rubygems_version}!
+Run `gem update --system #{Gem.latest_rubygems_version}` to update your installation.
+
+ MESSAGE
+ end
+
+ ##
+ # Determines if current environment is eligible for update suggestion.
+
+ def eligible_for_update?
+ # explicit opt-out
+ return false if Gem.configuration[:prevent_update_suggestion]
+ return false if ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"]
+
+ # focus only on human usage of final RubyGems releases
+ return false unless Gem.ui.tty?
+ return false if Gem.rubygems_version.prerelease?
+ return false if Gem.disable_system_update_message
+ return false if Gem::CIDetector.ci?
+
+ # check makes sense only when we can store timestamp of last try
+ # otherwise we will not be able to prevent "annoying" update message
+ # on each command call
+ return unless Gem.configuration.state_file_writable?
+
+ # load time of last check, ensure the difference is enough to repeat the suggestion
+ check_time = Time.now.to_i
+ last_update_check = Gem.configuration.last_update_check
+ return false if (check_time - last_update_check) < ONE_WEEK
+
+ # compare current and latest version, this is the part where
+ # latest rubygems spec is fetched from remote
+ (Gem.rubygems_version < Gem.latest_rubygems_version).tap do |eligible|
+ # store the time of last successful check into state file
+ Gem.configuration.last_update_check = check_time
+
+ return eligible
+ end
+ rescue StandardError # don't block install command on any problem
+ false
+ end
+end
diff --git a/lib/rubygems/uri.rb b/lib/rubygems/uri.rb
new file mode 100644
index 0000000000..a44aaceba5
--- /dev/null
+++ b/lib/rubygems/uri.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+##
+# The Uri handles rubygems source URIs.
+#
+
+class Gem::Uri
+ ##
+ # Parses and redacts uri
+
+ def self.redact(uri)
+ new(uri).redacted
+ end
+
+ ##
+ # Parses uri, raising if it's invalid
+
+ def self.parse!(uri)
+ require_relative "vendor/uri/lib/uri"
+
+ raise Gem::URI::InvalidURIError unless uri
+
+ return uri unless uri.is_a?(String)
+
+ # 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
+ Gem::URI.parse(uri)
+ rescue Gem::URI::InvalidURIError
+ Gem::URI.parse(Gem::URI::DEFAULT_PARSER.escape(uri))
+ end
+ end
+
+ ##
+ # Parses uri, returning the original uri if it's invalid
+
+ def self.parse(uri)
+ parse!(uri)
+ rescue Gem::URI::InvalidURIError
+ uri
+ end
+
+ def initialize(source_uri)
+ @parsed_uri = parse(source_uri)
+ end
+
+ def redacted
+ return self unless valid_uri?
+
+ if token? || oauth_basic?
+ with_redacted_user
+ elsif password?
+ with_redacted_password
+ else
+ self
+ end
+ end
+
+ def to_s
+ @parsed_uri.to_s
+ end
+
+ def redact_credentials_from(text)
+ return text unless valid_uri? && password? && text.include?(to_s)
+
+ text.sub(password, "REDACTED")
+ end
+
+ def method_missing(method_name, *args, &blk)
+ if @parsed_uri.respond_to?(method_name)
+ @parsed_uri.send(method_name, *args, &blk)
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(method_name, include_private = false)
+ @parsed_uri.respond_to?(method_name, include_private) || super
+ end
+
+ protected
+
+ # Add a protected reader for the cloned instance to access the original object's parsed uri
+ attr_reader :parsed_uri
+
+ private
+
+ def parse!(uri)
+ self.class.parse!(uri)
+ end
+
+ def parse(uri)
+ self.class.parse(uri)
+ end
+
+ def with_redacted_user
+ clone.tap {|uri| uri.user = "REDACTED" }
+ end
+
+ def with_redacted_password
+ clone.tap {|uri| uri.password = "REDACTED" }
+ end
+
+ def valid_uri?
+ !@parsed_uri.is_a?(String)
+ end
+
+ def password?
+ !!password
+ end
+
+ def oauth_basic?
+ password == "x-oauth-basic"
+ end
+
+ def token?
+ !user.nil? && password.nil?
+ end
+
+ def initialize_copy(original)
+ @parsed_uri = original.parsed_uri.clone
+ end
+end
diff --git a/lib/rubygems/uri_formatter.rb b/lib/rubygems/uri_formatter.rb
index 3bda896875..517ce33637 100644
--- a/lib/rubygems/uri_formatter.rb
+++ b/lib/rubygems/uri_formatter.rb
@@ -17,7 +17,7 @@ class Gem::UriFormatter
# Creates a new URI formatter for +uri+.
def initialize(uri)
- require 'cgi'
+ require "cgi"
@uri = uri
end
@@ -34,7 +34,7 @@ class Gem::UriFormatter
# Normalize the URI by adding "http://" if it is missing.
def normalize
- (@uri =~ /^(https?|ftp|file):/i) ? @uri : "http://#{@uri}"
+ /^(https?|ftp|file):/i.match?(@uri) ? @uri : "http://#{@uri}"
end
##
diff --git a/lib/rubygems/uri_parser.rb b/lib/rubygems/uri_parser.rb
deleted file mode 100644
index f350edec8c..0000000000
--- a/lib/rubygems/uri_parser.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# 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
deleted file mode 100644
index 941d7e023a..0000000000
--- a/lib/rubygems/uri_parsing.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb
index 27a9957117..0172c4ee89 100644
--- a/lib/rubygems/user_interaction.rb
+++ b/lib/rubygems/user_interaction.rb
@@ -1,19 +1,19 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/deprecate'
-require 'rubygems/text'
+require_relative "deprecate"
+require_relative "text"
##
# Module that defines the default UserInteraction. Any class including this
# module will have access to the +ui+ method that returns the default UI.
module Gem::DefaultUserInteraction
-
include Gem::Text
##
@@ -68,7 +68,6 @@ module Gem::DefaultUserInteraction
def use_ui(new_ui, &block)
Gem::DefaultUserInteraction.use_ui(new_ui, &block)
end
-
end
##
@@ -91,7 +90,6 @@ end
# end
module Gem::UserInteraction
-
include Gem::DefaultUserInteraction
##
@@ -148,7 +146,7 @@ module Gem::UserInteraction
##
# Displays the given +statement+ on the standard output (or equivalent).
- def say(statement = '')
+ def say(statement = "")
ui.say statement
end
@@ -195,7 +193,7 @@ class Gem::StreamUI
# then special operations (like asking for passwords) will use the TTY
# commands to disable character echo.
- def initialize(in_stream, out_stream, err_stream=STDERR, usetty=true)
+ def initialize(in_stream, out_stream, err_stream=$stderr, usetty=true)
@ins = in_stream
@outs = out_stream
@errs = err_stream
@@ -239,7 +237,8 @@ class Gem::StreamUI
return nil, nil unless result
result = result.strip.to_i - 1
- return list[result], result
+ return nil, nil unless (0...list.size) === result
+ [list[result], result]
end
##
@@ -259,12 +258,12 @@ class Gem::StreamUI
default_answer = case default
when nil
- 'yn'
+ "yn"
when true
- 'Yn'
+ "Yn"
else
- 'yN'
- end
+ "yN"
+ end
result = nil
@@ -273,24 +272,23 @@ class Gem::StreamUI
when /^y/i then true
when /^n/i then false
when /^$/ then default
- else nil
- end
+ end
end
- return result
+ result
end
##
# Ask a question. Returns an answer if connected to a tty, nil otherwise.
def ask(question)
- return nil if not tty?
+ return nil unless tty?
@outs.print(question + " ")
@outs.flush
result = @ins.gets
- result.chomp! if result
+ result&.chomp!
result
end
@@ -298,21 +296,21 @@ class Gem::StreamUI
# Ask for a password. Does not echo response to terminal.
def ask_for_password(question)
- return nil if not tty?
+ return nil unless tty?
@outs.print(question, " ")
@outs.flush
password = _gets_noecho
@outs.puts
- password.chomp! if password
+ password&.chomp!
password
end
def require_io_console
@require_io_console ||= begin
begin
- require 'io/console'
+ require "io/console"
rescue LoadError
end
true
@@ -428,8 +426,7 @@ class Gem::StreamUI
# +size+ items. Shows the given +initial_message+ when progress starts
# and the +terminal_message+ when it is complete.
- def initialize(out_stream, size, initial_message,
- terminal_message = "complete")
+ def initialize(out_stream, size, initial_message, terminal_message = "complete")
@out = out_stream
@total = size
@count = 0
@@ -471,8 +468,7 @@ class Gem::StreamUI
# +size+ items. Shows the given +initial_message+ when progress starts
# and the +terminal_message+ when it is complete.
- def initialize(out_stream, size, initial_message,
- terminal_message = 'complete')
+ def initialize(out_stream, size, initial_message, terminal_message = "complete")
@out = out_stream
@total = size
@count = 0
@@ -543,7 +539,7 @@ class Gem::StreamUI
# A progress reporter that behaves nicely with threaded downloading.
class ThreadedDownloadReporter
- MUTEX = Mutex.new
+ MUTEX = Thread::Mutex.new
##
# The current file name being displayed
@@ -595,8 +591,8 @@ class Gem::StreamUI
end
##
-# Subclass of StreamUI that instantiates the user interaction using STDIN,
-# STDOUT, and STDERR.
+# Subclass of StreamUI that instantiates the user interaction using $stdin,
+# $stdout, and $stderr.
class Gem::ConsoleUI < Gem::StreamUI
##
@@ -604,7 +600,7 @@ class Gem::ConsoleUI < Gem::StreamUI
# stdin, output to stdout and warnings or errors to stderr.
def initialize
- super STDIN, STDOUT, STDERR, true
+ super $stdin, $stdout, $stderr, true
end
end
@@ -616,18 +612,11 @@ class Gem::SilentUI < Gem::StreamUI
# The SilentUI has no arguments as it does not use any stream.
def initialize
- reader, writer = nil, nil
-
- reader = File.open(IO::NULL, 'r')
- writer = File.open(IO::NULL, 'w')
-
- super reader, writer, writer, false
+ io = NullIO.new
+ super io, io, io, false
end
def close
- super
- @ins.close
- @outs.close
end
def download_reporter(*args) # :nodoc:
@@ -637,4 +626,25 @@ class Gem::SilentUI < Gem::StreamUI
def progress_reporter(*args) # :nodoc:
SilentProgressReporter.new(@outs, *args)
end
+
+ ##
+ # An absolutely silent IO.
+
+ class NullIO
+ def puts(*args)
+ end
+
+ def print(*args)
+ end
+
+ def flush
+ end
+
+ def gets(*args)
+ end
+
+ def tty?
+ false
+ end
+ end
end
diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb
index 2a55305172..51f9c2029f 100644
--- a/lib/rubygems/util.rb
+++ b/lib/rubygems/util.rb
@@ -1,18 +1,18 @@
# frozen_string_literal: true
-require 'rubygems/deprecate'
+
+require_relative "deprecate"
##
# This module contains various utility methods as module methods.
module Gem::Util
-
##
# Zlib::GzipReader wrapper that unzips +data+.
def self.gunzip(data)
- require 'zlib'
- require 'stringio'
- data = StringIO.new(data, 'r')
+ require "zlib"
+ require "stringio"
+ data = StringIO.new(data, "r")
gzip_reader = begin
Zlib::GzipReader.new(data)
@@ -29,9 +29,9 @@ module Gem::Util
# Zlib::GzipWriter wrapper that zips +data+.
def self.gzip(data)
- require 'zlib'
- require 'stringio'
- zipped = StringIO.new(String.new, 'w')
+ require "zlib"
+ require "stringio"
+ zipped = StringIO.new(String.new, "w")
zipped.set_encoding Encoding::BINARY
Zlib::GzipWriter.wrap zipped do |io|
@@ -45,7 +45,7 @@ module Gem::Util
# A Zlib::Inflate#inflate wrapper
def self.inflate(data)
- require 'zlib'
+ require "zlib"
Zlib::Inflate.inflate data
end
@@ -60,7 +60,7 @@ module Gem::Util
# Invokes system, but silences all output.
def self.silent_system(*command)
- opt = {:out => IO::NULL, :err => [:child, :out]}
+ opt = { out: IO::NULL, err: [:child, :out] }
if Hash === command.last
opt.update(command.last)
cmds = command[0...-1]
@@ -84,9 +84,13 @@ module Gem::Util
here = File.expand_path directory
loop do
- Dir.chdir here, &block rescue Errno::EACCES
+ begin
+ Dir.chdir here, &block
+ rescue StandardError
+ Errno::EACCES
+ end
- new_here = File.expand_path('..', here)
+ new_here = File.expand_path("..", here)
return if new_here == here # toplevel
here = new_here
end
@@ -97,23 +101,18 @@ module Gem::Util
# 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.expand_path(f, base_path) }
- else
- Dir.glob(File.expand_path(glob, base_path))
- end
+ Dir.glob(glob, base: base_path).map! {|f| File.expand_path(f, base_path) }
end
##
- # Corrects +path+ (usually returned by `URI.parse().path` on Windows), that
+ # Corrects +path+ (usually returned by `Gem::URI.parse().path` on Windows), that
# comes with a leading slash.
def self.correct_for_windows_path(path)
- if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':'
+ if path[0].chr == "/" && path[1].chr.match?(/[a-z]/i) && path[2].chr == ":"
path[1..-1]
else
path
end
end
-
end
diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb
index 29bf310ea0..f3c7201639 100644
--- a/lib/rubygems/util/licenses.rb
+++ b/lib/rubygems/util/licenses.rb
@@ -1,10 +1,15 @@
# frozen_string_literal: true
-require 'rubygems/text'
+
+# This is generated by generate_spdx_license_list.rb, any edits to this
+# file will be discarded.
+
+require_relative "../text"
class Gem::Licenses
extend Gem::Text
- NONSTANDARD = 'Nonstandard'.freeze
+ NONSTANDARD = "Nonstandard"
+ LICENSE_REF = "LicenseRef-.+"
# Software Package Data Exchange (SPDX) standard open-source software
# license identifiers
@@ -17,90 +22,150 @@ class Gem::Licenses
AFL-2.0
AFL-2.1
AFL-3.0
- AGPL-1.0
- AGPL-3.0
+ AGPL-1.0-only
+ AGPL-1.0-or-later
AGPL-3.0-only
AGPL-3.0-or-later
AMDPLPA
AML
+ AML-glslang
AMPAS
ANTLR-PD
+ ANTLR-PD-fallback
APAFML
APL-1.0
APSL-1.0
APSL-1.1
APSL-1.2
APSL-2.0
+ ASWF-Digital-Assets-1.0
+ ASWF-Digital-Assets-1.1
Abstyles
+ AdaCore-doc
Adobe-2006
+ Adobe-Display-PostScript
Adobe-Glyph
+ Adobe-Utopia
Afmparse
Aladdin
Apache-1.0
Apache-1.1
Apache-2.0
+ App-s2p
+ Arphic-1999
Artistic-1.0
Artistic-1.0-Perl
Artistic-1.0-cl8
Artistic-2.0
BSD-1-Clause
BSD-2-Clause
- BSD-2-Clause-FreeBSD
- BSD-2-Clause-NetBSD
+ BSD-2-Clause-Darwin
BSD-2-Clause-Patent
+ BSD-2-Clause-Views
BSD-3-Clause
BSD-3-Clause-Attribution
BSD-3-Clause-Clear
+ BSD-3-Clause-HP
BSD-3-Clause-LBNL
+ BSD-3-Clause-Modification
+ BSD-3-Clause-No-Military-License
BSD-3-Clause-No-Nuclear-License
BSD-3-Clause-No-Nuclear-License-2014
BSD-3-Clause-No-Nuclear-Warranty
+ BSD-3-Clause-Open-MPI
+ BSD-3-Clause-Sun
+ BSD-3-Clause-acpica
+ BSD-3-Clause-flex
BSD-4-Clause
+ BSD-4-Clause-Shortened
BSD-4-Clause-UC
+ BSD-4.3RENO
+ BSD-4.3TAHOE
+ BSD-Advertising-Acknowledgement
+ BSD-Attribution-HPND-disclaimer
+ BSD-Inferno-Nettverk
BSD-Protection
BSD-Source-Code
+ BSD-Source-beginning-file
+ BSD-Systemics
+ BSD-Systemics-W3Works
BSL-1.0
+ BUSL-1.1
+ Baekmuk
Bahyph
Barr
Beerware
BitTorrent-1.0
BitTorrent-1.1
+ Bitstream-Charter
+ Bitstream-Vera
+ BlueOak-1.0.0
+ Boehm-GC
Borceux
+ Brian-Gladman-2-Clause
+ Brian-Gladman-3-Clause
+ C-UDA-1.0
+ CAL-1.0
+ CAL-1.0-Combined-Work-Exception
CATOSL-1.1
CC-BY-1.0
CC-BY-2.0
CC-BY-2.5
+ CC-BY-2.5-AU
CC-BY-3.0
+ CC-BY-3.0-AT
+ CC-BY-3.0-AU
+ CC-BY-3.0-DE
+ CC-BY-3.0-IGO
+ CC-BY-3.0-NL
+ CC-BY-3.0-US
CC-BY-4.0
CC-BY-NC-1.0
CC-BY-NC-2.0
CC-BY-NC-2.5
CC-BY-NC-3.0
+ CC-BY-NC-3.0-DE
CC-BY-NC-4.0
CC-BY-NC-ND-1.0
CC-BY-NC-ND-2.0
CC-BY-NC-ND-2.5
CC-BY-NC-ND-3.0
+ CC-BY-NC-ND-3.0-DE
+ CC-BY-NC-ND-3.0-IGO
CC-BY-NC-ND-4.0
CC-BY-NC-SA-1.0
CC-BY-NC-SA-2.0
+ CC-BY-NC-SA-2.0-DE
+ CC-BY-NC-SA-2.0-FR
+ CC-BY-NC-SA-2.0-UK
CC-BY-NC-SA-2.5
CC-BY-NC-SA-3.0
+ CC-BY-NC-SA-3.0-DE
+ CC-BY-NC-SA-3.0-IGO
CC-BY-NC-SA-4.0
CC-BY-ND-1.0
CC-BY-ND-2.0
CC-BY-ND-2.5
CC-BY-ND-3.0
+ CC-BY-ND-3.0-DE
CC-BY-ND-4.0
CC-BY-SA-1.0
CC-BY-SA-2.0
+ CC-BY-SA-2.0-UK
+ CC-BY-SA-2.1-JP
CC-BY-SA-2.5
CC-BY-SA-3.0
+ CC-BY-SA-3.0-AT
+ CC-BY-SA-3.0-DE
+ CC-BY-SA-3.0-IGO
CC-BY-SA-4.0
+ CC-PDDC
CC0-1.0
CDDL-1.0
CDDL-1.1
+ CDL-1.0
CDLA-Permissive-1.0
+ CDLA-Permissive-2.0
CDLA-Sharing-1.0
CECILL-1.0
CECILL-1.1
@@ -108,106 +173,160 @@ class Gem::Licenses
CECILL-2.1
CECILL-B
CECILL-C
+ CERN-OHL-1.1
+ CERN-OHL-1.2
+ CERN-OHL-P-2.0
+ CERN-OHL-S-2.0
+ CERN-OHL-W-2.0
+ CFITSIO
+ CMU-Mach
+ CMU-Mach-nodoc
CNRI-Jython
CNRI-Python
CNRI-Python-GPL-Compatible
+ COIL-1.0
CPAL-1.0
CPL-1.0
CPOL-1.02
CUA-OPL-1.0
Caldera
+ Caldera-no-preamble
ClArtistic
+ Clips
+ Community-Spec-1.0
Condor-1.1
+ Cornell-Lossless-JPEG
+ Cronyx
Crossword
CrystalStacker
Cube
D-FSL-1.0
+ DEC-3-Clause
+ DL-DE-BY-2.0
+ DL-DE-ZERO-2.0
DOC
+ DRL-1.0
+ DRL-1.1
DSDP
Dotseqn
ECL-1.0
ECL-2.0
EFL-1.0
EFL-2.0
+ EPICS
EPL-1.0
EPL-2.0
EUDatagrid
EUPL-1.0
EUPL-1.1
EUPL-1.2
+ Elastic-2.0
Entessa
ErlPL-1.1
Eurosym
+ FBM
+ FDK-AAC
FSFAP
+ FSFAP-no-warranty-disclaimer
FSFUL
FSFULLR
+ FSFULLRWD
FTL
Fair
+ Ferguson-Twofish
Frameworx-1.0
+ FreeBSD-DOC
FreeImage
- GFDL-1.1
+ Furuseth
+ GCR-docs
+ GD
+ GFDL-1.1-invariants-only
+ GFDL-1.1-invariants-or-later
+ GFDL-1.1-no-invariants-only
+ GFDL-1.1-no-invariants-or-later
GFDL-1.1-only
GFDL-1.1-or-later
- GFDL-1.2
+ GFDL-1.2-invariants-only
+ GFDL-1.2-invariants-or-later
+ GFDL-1.2-no-invariants-only
+ GFDL-1.2-no-invariants-or-later
GFDL-1.2-only
GFDL-1.2-or-later
- GFDL-1.3
+ GFDL-1.3-invariants-only
+ GFDL-1.3-invariants-or-later
+ GFDL-1.3-no-invariants-only
+ GFDL-1.3-no-invariants-or-later
GFDL-1.3-only
GFDL-1.3-or-later
GL2PS
- GPL-1.0
- GPL-1.0+
+ GLWTPL
GPL-1.0-only
GPL-1.0-or-later
- GPL-2.0
- GPL-2.0+
GPL-2.0-only
GPL-2.0-or-later
- GPL-2.0-with-GCC-exception
- GPL-2.0-with-autoconf-exception
- GPL-2.0-with-bison-exception
- GPL-2.0-with-classpath-exception
- GPL-2.0-with-font-exception
- GPL-3.0
- GPL-3.0+
GPL-3.0-only
GPL-3.0-or-later
- GPL-3.0-with-GCC-exception
- GPL-3.0-with-autoconf-exception
Giftware
Glide
Glulxe
+ Graphics-Gems
+ HP-1986
+ HP-1989
HPND
+ HPND-DEC
+ HPND-Fenneberg-Livingston
+ HPND-INRIA-IMAG
+ HPND-Kevlin-Henney
+ HPND-MIT-disclaimer
+ HPND-Markus-Kuhn
+ HPND-Pbmplus
+ HPND-UC
+ HPND-doc
+ HPND-doc-sell
+ HPND-export-US
+ HPND-export-US-modify
+ HPND-sell-MIT-disclaimer-xserver
+ HPND-sell-regexpr
+ HPND-sell-variant
+ HPND-sell-variant-MIT-disclaimer
+ HTMLTIDY
HaskellReport
+ Hippocratic-2.1
IBM-pibs
ICU
+ IEC-Code-Components-EULA
IJG
+ IJG-short
IPA
IPL-1.0
ISC
+ ISC-Veillard
ImageMagick
Imlib2
Info-ZIP
+ Inner-Net-2.0
Intel
Intel-ACPI
Interbase-1.0
+ JPL-image
+ JPNIC
JSON
+ Jam
JasPer-2.0
+ Kastrup
+ Kazlib
+ Knuth-CTAN
LAL-1.2
LAL-1.3
- LGPL-2.0
- LGPL-2.0+
LGPL-2.0-only
LGPL-2.0-or-later
- LGPL-2.1
- LGPL-2.1+
LGPL-2.1-only
LGPL-2.1-or-later
- LGPL-3.0
- LGPL-3.0+
LGPL-3.0-only
LGPL-3.0-or-later
LGPLLR
+ LOOP
+ LPD-document
LPL-1.0
LPL-1.02
LPPL-1.0
@@ -215,35 +334,67 @@ class Gem::Licenses
LPPL-1.2
LPPL-1.3a
LPPL-1.3c
+ LZMA-SDK-9.11-to-9.20
+ LZMA-SDK-9.22
Latex2e
+ Latex2e-translated-notice
Leptonica
LiLiQ-P-1.1
LiLiQ-R-1.1
LiLiQ-Rplus-1.1
Libpng
+ Linux-OpenIB
+ Linux-man-pages-1-para
+ Linux-man-pages-copyleft
+ Linux-man-pages-copyleft-2-para
+ Linux-man-pages-copyleft-var
+ Lucida-Bitmap-Fonts
MIT
+ MIT-0
MIT-CMU
+ MIT-Festival
+ MIT-Modern-Variant
+ MIT-Wu
MIT-advertising
MIT-enna
MIT-feh
+ MIT-open-group
+ MIT-testregex
MITNFA
+ MMIXware
+ MPEG-SSG
MPL-1.0
MPL-1.1
MPL-2.0
MPL-2.0-no-copyleft-exception
+ MS-LPL
MS-PL
MS-RL
MTLL
+ Mackerras-3-Clause
+ Mackerras-3-Clause-acknowledgment
MakeIndex
+ Martin-Birgmeier
+ McPhee-slideshow
+ Minpack
MirOS
Motosoto
+ MulanPSL-1.0
+ MulanPSL-2.0
Multics
Mup
+ NAIST-2003
NASA-1.3
NBPL-1.0
+ NCGL-UK-2.0
NCSA
NGPL
+ NICTA-1.0
+ NIST-PD
+ NIST-PD-fallback
+ NIST-Software
NLOD-1.0
+ NLOD-2.0
NLPL
NOSL
NPL-1.0
@@ -251,18 +402,31 @@ class Gem::Licenses
NPOSL-3.0
NRL
NTP
+ NTP-0
Naumen
Net-SNMP
NetCDF
Newsletr
Nokia
Noweb
- Nunit
+ O-UDA-1.0
OCCT-PL
OCLC-2.0
+ ODC-By-1.0
ODbL-1.0
+ OFFIS
OFL-1.0
+ OFL-1.0-RFN
+ OFL-1.0-no-RFN
OFL-1.1
+ OFL-1.1-RFN
+ OFL-1.1-no-RFN
+ OGC-1.0
+ OGDL-Taiwan-1.0
+ OGL-Canada-2.0
+ OGL-UK-1.0
+ OGL-UK-2.0
+ OGL-UK-3.0
OGTSL
OLDAP-1.1
OLDAP-1.2
@@ -280,22 +444,37 @@ class Gem::Licenses
OLDAP-2.6
OLDAP-2.7
OLDAP-2.8
+ OLFL-1.3
OML
OPL-1.0
+ OPL-UK-3.0
+ OPUBL-1.0
OSET-PL-2.1
OSL-1.0
OSL-1.1
OSL-2.0
OSL-2.1
OSL-3.0
+ OpenPBS-2.3
OpenSSL
+ OpenSSL-standalone
+ OpenVision
+ PADL
PDDL-1.0
PHP-3.0
PHP-3.01
+ PSF-2.0
+ Parity-6.0.0
+ Parity-7.0.0
+ Pixar
Plexus
+ PolyForm-Noncommercial-1.0.0
+ PolyForm-Small-Business-1.0.0
PostgreSQL
Python-2.0
+ Python-2.0.1
QPL-1.0
+ QPL-1.0-INRIA-2004
Qhull
RHeCos-1.1
RPL-1.1
@@ -306,35 +485,65 @@ class Gem::Licenses
Rdisc
Ruby
SAX-PD
+ SAX-PD-2.0
SCEA
SGI-B-1.0
SGI-B-1.1
SGI-B-2.0
+ SGI-OpenGL
+ SGP4
+ SHL-0.5
+ SHL-0.51
SISSL
SISSL-1.2
+ SL
SMLNJ
SMPPL
SNIA
SPL-1.0
+ SSH-OpenSSH
+ SSH-short
+ SSLeay-standalone
+ SSPL-1.0
SWL
Saxpath
+ SchemeReport
Sendmail
+ Sendmail-8.23
SimPL-2.0
Sleepycat
+ Soundex
Spencer-86
Spencer-94
Spencer-99
- StandardML-NJ
SugarCRM-1.1.3
+ Sun-PPP
+ SunPro
+ Symlinks
+ TAPR-OHL-1.0
TCL
TCP-wrappers
+ TGPPL-1.0
TMate
TORQUE-1.1
TOSL
+ TPDL
+ TPL-1.0
+ TTWL
+ TTYP0
+ TU-Berlin-1.0
+ TU-Berlin-2.0
+ TermReadKey
+ UCAR
+ UCL-1.0
+ UMich-Merit
UPL-1.0
+ URT-RLE
+ Unicode-3.0
Unicode-DFS-2015
Unicode-DFS-2016
Unicode-TOU
+ UnixCrypt
Unlicense
VOSTROM
VSL-1.0
@@ -344,11 +553,15 @@ class Gem::Licenses
W3C-20150513
WTFPL
Watcom-1.0
+ Widget-Workshop
Wsuipa
X11
+ X11-distribute-modifications-variant
XFree86-1.1
XSkat
+ Xdebug-1.03
Xerox
+ Xfig
Xnet
YPL-1.0
YPL-1.1
@@ -356,35 +569,103 @@ class Gem::Licenses
ZPL-2.0
ZPL-2.1
Zed
+ Zeeff
Zend-2.0
Zimbra-1.3
Zimbra-1.4
Zlib
- bzip2-1.0.5
+ bcrypt-Solar-Designer
+ blessing
bzip2-1.0.6
+ check-cvs
+ checkmk
+ copyleft-next-0.3.0
+ copyleft-next-0.3.1
curl
diffmark
+ dtoa
dvipdfm
- eCos-2.0
eGenix
+ etalab-2.0
+ fwlw
gSOAP-1.3b
gnuplot
+ gtkbook
+ hdparm
iMatix
+ libpng-2.0
+ libselinux-1.0
libtiff
+ libutil-David-Nugent
+ lsof
+ magaz
+ mailprio
+ metamail
+ mpi-permissive
mpich2
+ mplus
+ pnmstitch
psfrag
psutils
- wxWindows
+ python-ldap
+ radvd
+ snprintf
+ softSurfer
+ ssh-keyscan
+ swrule
+ ulem
+ w3m
xinetd
+ xkeyboard-config-Zinoviev
+ xlock
xpp
zlib-acknowledgement
].freeze
+ DEPRECATED_LICENSE_IDENTIFIERS = %w[
+ AGPL-1.0
+ AGPL-3.0
+ BSD-2-Clause-FreeBSD
+ BSD-2-Clause-NetBSD
+ GFDL-1.1
+ GFDL-1.2
+ GFDL-1.3
+ GPL-1.0
+ GPL-1.0+
+ GPL-2.0
+ GPL-2.0+
+ GPL-2.0-with-GCC-exception
+ GPL-2.0-with-autoconf-exception
+ GPL-2.0-with-bison-exception
+ GPL-2.0-with-classpath-exception
+ GPL-2.0-with-font-exception
+ GPL-3.0
+ GPL-3.0+
+ GPL-3.0-with-GCC-exception
+ GPL-3.0-with-autoconf-exception
+ LGPL-2.0
+ LGPL-2.0+
+ LGPL-2.1
+ LGPL-2.1+
+ LGPL-3.0
+ LGPL-3.0+
+ Nunit
+ StandardML-NJ
+ bzip2-1.0.5
+ eCos-2.0
+ wxWindows
+ ].freeze
+
# exception identifiers
EXCEPTION_IDENTIFIERS = %w[
389-exception
+ Asterisk-exception
Autoconf-exception-2.0
Autoconf-exception-3.0
+ Autoconf-exception-generic
+ Autoconf-exception-generic-3.0
+ Autoconf-exception-macro
+ Bison-exception-1.24
Bison-exception-2.2
Bootloader-exception
CLISP-exception-2.0
@@ -394,36 +675,99 @@ class Gem::Licenses
Fawkes-Runtime-exception
Font-exception-2.0
GCC-exception-2.0
+ GCC-exception-2.0-note
GCC-exception-3.1
+ GNAT-exception
+ GNOME-examples-exception
+ GNU-compiler-exception
+ GPL-3.0-interface-exception
+ GPL-3.0-linking-exception
+ GPL-3.0-linking-source-exception
+ GPL-CC-1.0
+ GStreamer-exception-2005
+ GStreamer-exception-2008
+ Gmsh-exception
+ KiCad-libraries-exception
+ LGPL-3.0-linking-exception
+ LLGPL
+ LLVM-exception
LZMA-exception
Libtool-exception
Linux-syscall-note
- Nokia-Qt-exception-1.1
OCCT-exception-1.0
+ OCaml-LGPL-linking-exception
+ OpenJDK-assembly-exception-1.0
+ PS-or-PDF-font-exception-20170817
+ QPL-1.0-INRIA-2004-exception
+ Qt-GPL-exception-1.0
+ Qt-LGPL-exception-1.1
Qwt-exception-1.0
+ SANE-exception
+ SHL-2.0
+ SHL-2.1
+ SWI-exception
+ Swift-exception
+ Texinfo-exception
+ UBDL-exception
+ Universal-FOSS-exception-1.0
WxWindows-exception-3.1
+ cryptsetup-OpenSSL-exception
eCos-exception-2.0
+ fmt-exception
freertos-exception-2.0
gnu-javamail-exception
i2p-gpl-java-exception
+ libpri-OpenH323-exception
mif-exception
openvpn-openssl-exception
+ stunnel-exception
u-boot-exception-2.0
+ vsftpd-openssl-exception
+ x11vnc-openssl-exception
+ ].freeze
+
+ DEPRECATED_EXCEPTION_IDENTIFIERS = %w[
+ Nokia-Qt-exception-1.1
].freeze
- REGEXP = %r{
+ VALID_REGEXP = /
\A
- (
+ (?:
#{Regexp.union(LICENSE_IDENTIFIERS)}
\+?
- (\s WITH \s #{Regexp.union(EXCEPTION_IDENTIFIERS)})?
+ (?:\s WITH \s #{Regexp.union(EXCEPTION_IDENTIFIERS)})?
| #{NONSTANDARD}
+ | #{LICENSE_REF}
)
\Z
- }ox.freeze
+ /ox
+
+ DEPRECATED_LICENSE_REGEXP = /
+ \A
+ #{Regexp.union(DEPRECATED_LICENSE_IDENTIFIERS)}
+ \+?
+ (?:\s WITH \s .+?)?
+ \Z
+ /ox
+
+ DEPRECATED_EXCEPTION_REGEXP = /
+ \A
+ .+?
+ \+?
+ (?:\s WITH \s #{Regexp.union(DEPRECATED_EXCEPTION_IDENTIFIERS)})
+ \Z
+ /ox
def self.match?(license)
- !REGEXP.match(license).nil?
+ VALID_REGEXP.match?(license)
+ end
+
+ def self.deprecated_license_id?(license)
+ DEPRECATED_LICENSE_REGEXP.match?(license)
+ end
+
+ def self.deprecated_exception_id?(license)
+ DEPRECATED_EXCEPTION_REGEXP.match?(license)
end
def self.suggestions(license)
diff --git a/lib/rubygems/util/list.rb b/lib/rubygems/util/list.rb
index 33c40af4bb..2899e8a2b9 100644
--- a/lib/rubygems/util/list.rb
+++ b/lib/rubygems/util/list.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
+
module Gem
- class List
+ # The Gem::List class is currently unused and will be removed in the next major rubygems version
+ class List # :nodoc:
include Enumerable
attr_accessor :value, :tail
@@ -34,4 +36,5 @@ module Gem
List.new value, list
end
end
+ deprecate_constant :List
end
diff --git a/lib/rubygems/validator.rb b/lib/rubygems/validator.rb
index 30cdd93b5c..57e0747eb4 100644
--- a/lib/rubygems/validator.rb
+++ b/lib/rubygems/validator.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems/package'
-require 'rubygems/installer'
+require_relative "package"
+require_relative "installer"
##
# Validator performs various gem file and gem database validation
@@ -15,7 +16,7 @@ class Gem::Validator
include Gem::UserInteraction
def initialize # :nodoc:
- require 'find'
+ require "find"
end
private
@@ -24,9 +25,9 @@ class Gem::Validator
installed_files = []
Find.find gem_directory do |file_name|
- fn = file_name[gem_directory.size..file_name.size - 1].sub(/^\//, "")
+ fn = file_name[gem_directory.size..file_name.size - 1].sub(%r{^/}, "")
installed_files << fn unless
- fn =~ /CVS/ || fn.empty? || File.directory?(file_name)
+ fn.empty? || fn.include?("CVS") || File.directory?(file_name)
end
installed_files
@@ -62,7 +63,9 @@ class Gem::Validator
errors = Hash.new {|h,k| h[k] = {} }
Gem::Specification.each do |spec|
- next unless gems.include? spec.name unless gems.empty?
+ unless gems.empty?
+ next unless gems.include? spec.name
+ end
next if spec.default_gem?
gem_name = spec.file_name
@@ -87,7 +90,7 @@ class Gem::Validator
good, gone, unreadable = nil, nil, nil, nil
- File.open gem_path, Gem.binary_mode do |file|
+ File.open gem_path, Gem.binary_mode do |_file|
package = Gem::Package.new gem_path
good, gone = package.contents.partition do |file_name|
@@ -107,15 +110,13 @@ class Gem::Validator
end
good.each do |entry, data|
- begin
- next unless data # HACK `gem check -a mkrf`
+ next unless data # HACK: `gem check -a mkrf`
- source = File.join gem_directory, entry['path']
+ source = File.join gem_directory, entry["path"]
- File.open source, Gem.binary_mode do |f|
- unless f.read == data
- errors[gem_name][entry['path']] = "Modified from original"
- end
+ File.open source, Gem.binary_mode do |f|
+ unless f.read == data
+ errors[gem_name][entry["path"]] = "Modified from original"
end
end
end
diff --git a/lib/rubygems/vendor/molinillo/.document b/lib/rubygems/vendor/molinillo/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/molinillo/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo.rb b/lib/rubygems/vendor/molinillo/lib/molinillo.rb
index f67badbde7..dd5600c9e3 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo.rb
@@ -6,6 +6,6 @@ require_relative 'molinillo/resolver'
require_relative 'molinillo/modules/ui'
require_relative 'molinillo/modules/specification_provider'
-# Gem::Resolver::Molinillo is a generic dependency resolution algorithm.
-module Gem::Resolver::Molinillo
+# Gem::Molinillo is a generic dependency resolution algorithm.
+module Gem::Molinillo
end
diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
new file mode 100644
index 0000000000..34842d46d5
--- /dev/null
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Gem::Molinillo
+ # @!visibility private
+ module Delegates
+ # Delegates all {Gem::Molinillo::ResolutionState} methods to a `#state` property.
+ module ResolutionState
+ # (see Gem::Molinillo::ResolutionState#name)
+ def name
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.name
+ end
+
+ # (see Gem::Molinillo::ResolutionState#requirements)
+ def requirements
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.requirements
+ end
+
+ # (see Gem::Molinillo::ResolutionState#activated)
+ def activated
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.activated
+ end
+
+ # (see Gem::Molinillo::ResolutionState#requirement)
+ def requirement
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.requirement
+ end
+
+ # (see Gem::Molinillo::ResolutionState#possibilities)
+ def possibilities
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.possibilities
+ end
+
+ # (see Gem::Molinillo::ResolutionState#depth)
+ def depth
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.depth
+ end
+
+ # (see Gem::Molinillo::ResolutionState#conflicts)
+ def conflicts
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.conflicts
+ end
+
+ # (see Gem::Molinillo::ResolutionState#unused_unwind_options)
+ def unused_unwind_options
+ current_state = state || Gem::Molinillo::ResolutionState.empty
+ current_state.unused_unwind_options
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
index b765226fb0..8417721537 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb
@@ -1,67 +1,67 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
module Delegates
- # Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a
+ # Delegates all {Gem::Molinillo::SpecificationProvider} methods to a
# `#specification_provider` property.
module SpecificationProvider
- # (see Gem::Resolver::Molinillo::SpecificationProvider#search_for)
+ # (see Gem::Molinillo::SpecificationProvider#search_for)
def search_for(dependency)
with_no_such_dependency_error_handling do
specification_provider.search_for(dependency)
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#dependencies_for)
+ # (see Gem::Molinillo::SpecificationProvider#dependencies_for)
def dependencies_for(specification)
with_no_such_dependency_error_handling do
specification_provider.dependencies_for(specification)
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#requirement_satisfied_by?)
+ # (see Gem::Molinillo::SpecificationProvider#requirement_satisfied_by?)
def requirement_satisfied_by?(requirement, activated, spec)
with_no_such_dependency_error_handling do
specification_provider.requirement_satisfied_by?(requirement, activated, spec)
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#dependencies_equal?)
+ # (see Gem::Molinillo::SpecificationProvider#dependencies_equal?)
def dependencies_equal?(dependencies, other_dependencies)
with_no_such_dependency_error_handling do
specification_provider.dependencies_equal?(dependencies, other_dependencies)
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#name_for)
+ # (see Gem::Molinillo::SpecificationProvider#name_for)
def name_for(dependency)
with_no_such_dependency_error_handling do
specification_provider.name_for(dependency)
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#name_for_explicit_dependency_source)
+ # (see Gem::Molinillo::SpecificationProvider#name_for_explicit_dependency_source)
def name_for_explicit_dependency_source
with_no_such_dependency_error_handling do
specification_provider.name_for_explicit_dependency_source
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#name_for_locking_dependency_source)
+ # (see Gem::Molinillo::SpecificationProvider#name_for_locking_dependency_source)
def name_for_locking_dependency_source
with_no_such_dependency_error_handling do
specification_provider.name_for_locking_dependency_source
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#sort_dependencies)
+ # (see Gem::Molinillo::SpecificationProvider#sort_dependencies)
def sort_dependencies(dependencies, activated, conflicts)
with_no_such_dependency_error_handling do
specification_provider.sort_dependencies(dependencies, activated, conflicts)
end
end
- # (see Gem::Resolver::Molinillo::SpecificationProvider#allow_missing?)
+ # (see Gem::Molinillo::SpecificationProvider#allow_missing?)
def allow_missing?(dependency)
with_no_such_dependency_error_handling do
specification_provider.allow_missing?(dependency)
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb
index 16430a79f5..2dbbc589dc 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'tsort'
+require_relative '../../../../vendored_tsort'
require_relative 'dependency_graph/log'
require_relative 'dependency_graph/vertex'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
# A directed acyclic graph that is tuned to hold named dependencies
class DependencyGraph
include Enumerable
@@ -17,7 +17,7 @@ module Gem::Resolver::Molinillo
vertices.values.each { |v| yield v }
end
- include TSort
+ include Gem::TSort
# @!visibility private
alias tsort_each_node each
@@ -32,7 +32,7 @@ module Gem::Resolver::Molinillo
# all belong to the same graph.
# @return [Array<Vertex>] The sorted vertices.
def self.tsort(vertices)
- TSort.tsort(
+ Gem::TSort.tsort(
lambda { |b| vertices.each(&b) },
lambda { |v, &b| (v.successors & vertices).each(&b) }
)
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
index cc140031b3..8707ec451d 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# An action that modifies a {DependencyGraph} that is reversible.
# @abstract
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
index 5570483253..aa9815c5ae 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# @!visibility private
# (see DependencyGraph#add_edge_no_circular)
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
index f1411d5efa..9c7066a669 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# @!visibility private
# (see DependencyGraph#add_vertex)
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
index 3b48d77a50..1e62c0a0b6 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# @!visibility private
# (see DependencyGraph#delete_edge)
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
index 92f60d5be8..6132f969b9 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# @!visibility private
# @see DependencyGraph#detach_vertex_named
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
index 7aeb8847ec..6954c4b1f8 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rb
@@ -7,7 +7,7 @@ require_relative 'detach_vertex_named'
require_relative 'set_payload'
require_relative 'tag'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# A log for dependency graph actions
class Log
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
index 726292a2c3..9bcaaae0f9 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# @!visibility private
# @see DependencyGraph#set_payload
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
index bfe6fd31f8..62f243a2af 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# @!visibility private
# @see DependencyGraph#tag
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
index 77114951b2..074de369be 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class DependencyGraph
# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
# {#payload}
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/errors.rb
index ada03a901c..07ea5fdf37 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/errors.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
# An error that occurred during the resolution process
class ResolverError < StandardError; end
@@ -107,36 +107,42 @@ module Gem::Resolver::Molinillo
end
end
- conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
- o << "\n" << incompatible_version_message_for_conflict.call(name, conflict) << "\n"
- if conflict.locked_requirement
- o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
- o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
- o << %(\n)
- end
- o << %( In #{name_for_explicit_dependency_source}:\n)
- trees = reduce_trees.call(conflict.requirement_trees)
-
- o << trees.map do |tree|
- t = ''.dup
- depth = 2
- tree.each do |req|
- t << ' ' * depth << printable_requirement.call(req)
- unless tree.last == req
- if spec = conflict.activated_by_name[name_for(req)]
- t << %( was resolved to #{version_for_spec.call(spec)}, which)
+ full_message_for_conflict = opts.delete(:full_message_for_conflict) do
+ proc do |name, conflict|
+ o = "\n".dup << incompatible_version_message_for_conflict.call(name, conflict) << "\n"
+ if conflict.locked_requirement
+ o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
+ o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
+ o << %(\n)
+ end
+ o << %( In #{name_for_explicit_dependency_source}:\n)
+ trees = reduce_trees.call(conflict.requirement_trees)
+
+ o << trees.map do |tree|
+ t = ''.dup
+ depth = 2
+ tree.each do |req|
+ t << ' ' * depth << printable_requirement.call(req)
+ unless tree.last == req
+ if spec = conflict.activated_by_name[name_for(req)]
+ t << %( was resolved to #{version_for_spec.call(spec)}, which)
+ end
+ t << %( depends on)
end
- t << %( depends on)
+ t << %(\n)
+ depth += 1
end
- t << %(\n)
- depth += 1
- end
- t
- end.join("\n")
+ t
+ end.join("\n")
- additional_message_for_conflict.call(o, name, conflict)
+ additional_message_for_conflict.call(o, name, conflict)
- o
+ o
+ end
+ end
+
+ conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
+ o << full_message_for_conflict.call(name, conflict)
end.strip
end
end
diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/gem_metadata.rb
new file mode 100644
index 0000000000..8ed3a920a2
--- /dev/null
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/gem_metadata.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+module Gem::Molinillo
+ # The version of Gem::Molinillo.
+ VERSION = '0.8.0'.freeze
+end
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
index 1067bf7439..85860902fc 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
# Provides information about specifications and dependencies to the resolver,
# allowing the {Resolver} class to remain generic while still providing power
# and flexibility.
#
- # This module contains the methods that users of Gem::Resolver::Molinillo must to implement,
+ # This module contains the methods that users of Gem::Molinillo must to implement,
# using knowledge of their own model classes.
module SpecificationProvider
# Search for the specifications that match the given dependency.
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/ui.rb
index a810fd519c..464722902e 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/ui.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
# Conveys information about the resolution process to a user.
module UI
# The {IO} object that should be used to print output. `STDOUT`, by default.
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/resolution.rb
index 8b40e59e42..84ec6cb095 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/resolution.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
class Resolver
# A specific resolution from a given {Resolver}
class Resolution
@@ -103,7 +103,7 @@ module Gem::Resolver::Molinillo
# @return [Boolean] where the requirement of the state we're unwinding
# to directly caused the conflict. Note: in this case, it is
- # impossible for the state we're unwinding to to be a parent of
+ # impossible for the state we're unwinding to be a parent of
# any of the other conflicting requirements (or we would have
# circularity)
def unwinding_to_primary_requirement?
@@ -244,8 +244,8 @@ module Gem::Resolver::Molinillo
require_relative 'delegates/resolution_state'
require_relative 'delegates/specification_provider'
- include Gem::Resolver::Molinillo::Delegates::ResolutionState
- include Gem::Resolver::Molinillo::Delegates::SpecificationProvider
+ include Gem::Molinillo::Delegates::ResolutionState
+ include Gem::Molinillo::Delegates::SpecificationProvider
# Processes the topmost available {RequirementState} on the stack
# @return [void]
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/resolver.rb
index d43121f8ca..86229c3fa1 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/resolver.rb
@@ -2,7 +2,7 @@
require_relative 'dependency_graph'
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
# This class encapsulates a dependency resolver.
# The resolver is responsible for determining which set of dependencies to
# activate, with feedback from the {#specification_provider}
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/state.rb
index 6e7c715fce..c48ec6af9c 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb
+++ b/lib/rubygems/vendor/molinillo/lib/molinillo/state.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module Gem::Resolver::Molinillo
+module Gem::Molinillo
# A state that a {Resolution} can be in
# @attr [String] name the name of the current requirement
# @attr [Array<Object>] requirements currently unsatisfied requirements
diff --git a/lib/rubygems/vendor/net-http/.document b/lib/rubygems/vendor/net-http/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb
new file mode 100644
index 0000000000..7b15c3cf54
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http.rb
@@ -0,0 +1,2496 @@
+# frozen_string_literal: true
+#
+# = net/http.rb
+#
+# Copyright (c) 1999-2007 Yukihiro Matsumoto
+# Copyright (c) 1999-2007 Minero Aoki
+# Copyright (c) 2001 GOTOU Yuuzou
+#
+# Written and maintained by Minero Aoki <aamine@loveruby.net>.
+# HTTPS support added by GOTOU Yuuzou <gotoyuzo@notwork.org>.
+#
+# This file is derived from "http-access.rb".
+#
+# Documented by Minero Aoki; converted to RDoc by William Webber.
+#
+# This program is free software. You can re-distribute and/or
+# modify this program under the same terms of ruby itself ---
+# Ruby Distribution License or GNU General Public License.
+#
+# See Gem::Net::HTTP for an overview and examples.
+#
+
+require_relative '../../../net-protocol/lib/net/protocol'
+require_relative '../../../uri/lib/uri'
+require_relative '../../../resolv/lib/resolv'
+autoload :OpenSSL, 'openssl'
+
+module Gem::Net #:nodoc:
+
+ # :stopdoc:
+ class HTTPBadResponse < StandardError; end
+ class HTTPHeaderSyntaxError < StandardError; end
+ # :startdoc:
+
+ # \Class \Gem::Net::HTTP provides a rich library that implements the client
+ # in a client-server model that uses the \HTTP request-response protocol.
+ # For information about \HTTP, see:
+ #
+ # - {Hypertext Transfer Protocol}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol].
+ # - {Technical overview}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technical_overview].
+ #
+ # == About the Examples
+ #
+ # :include: doc/net-http/examples.rdoc
+ #
+ # == Strategies
+ #
+ # - If you will make only a few GET requests,
+ # consider using {OpenURI}[rdoc-ref:OpenURI].
+ # - If you will make only a few requests of all kinds,
+ # consider using the various singleton convenience methods in this class.
+ # Each of the following methods automatically starts and finishes
+ # a {session}[rdoc-ref:Gem::Net::HTTP@Sessions] that sends a single request:
+ #
+ # # Return string response body.
+ # Gem::Net::HTTP.get(hostname, path)
+ # Gem::Net::HTTP.get(uri)
+ #
+ # # Write string response body to $stdout.
+ # Gem::Net::HTTP.get_print(hostname, path)
+ # Gem::Net::HTTP.get_print(uri)
+ #
+ # # Return response as Gem::Net::HTTPResponse object.
+ # Gem::Net::HTTP.get_response(hostname, path)
+ # Gem::Net::HTTP.get_response(uri)
+ # data = '{"title": "foo", "body": "bar", "userId": 1}'
+ # Gem::Net::HTTP.post(uri, data)
+ # params = {title: 'foo', body: 'bar', userId: 1}
+ # Gem::Net::HTTP.post_form(uri, params)
+ #
+ # - If performance is important, consider using sessions, which lower request overhead.
+ # This {session}[rdoc-ref:Gem::Net::HTTP@Sessions] has multiple requests for
+ # {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]
+ # and {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]:
+ #
+ # Gem::Net::HTTP.start(hostname) do |http|
+ # # Session started automatically before block execution.
+ # http.get(path)
+ # http.head(path)
+ # body = 'Some text'
+ # http.post(path, body) # Can also have a block.
+ # http.put(path, body)
+ # http.delete(path)
+ # http.options(path)
+ # http.trace(path)
+ # http.patch(path, body) # Can also have a block.
+ # http.copy(path)
+ # http.lock(path, body)
+ # http.mkcol(path, body)
+ # http.move(path)
+ # http.propfind(path, body)
+ # http.proppatch(path, body)
+ # http.unlock(path, body)
+ # # Session finished automatically at block exit.
+ # end
+ #
+ # The methods cited above are convenience methods that, via their few arguments,
+ # allow minimal control over the requests.
+ # For greater control, consider using {request objects}[rdoc-ref:Gem::Net::HTTPRequest].
+ #
+ # == URIs
+ #
+ # On the internet, a URI
+ # ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier])
+ # is a string that identifies a particular resource.
+ # It consists of some or all of: scheme, hostname, path, query, and fragment;
+ # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax].
+ #
+ # A Ruby {Gem::URI::Generic}[rdoc-ref:Gem::URI::Generic] object
+ # represents an internet URI.
+ # It provides, among others, methods
+ # +scheme+, +hostname+, +path+, +query+, and +fragment+.
+ #
+ # === Schemes
+ #
+ # An internet \Gem::URI has
+ # a {scheme}[https://en.wikipedia.org/wiki/List_of_URI_schemes].
+ #
+ # The two schemes supported in \Gem::Net::HTTP are <tt>'https'</tt> and <tt>'http'</tt>:
+ #
+ # uri.scheme # => "https"
+ # Gem::URI('http://example.com').scheme # => "http"
+ #
+ # === Hostnames
+ #
+ # A hostname identifies a server (host) to which requests may be sent:
+ #
+ # hostname = uri.hostname # => "jsonplaceholder.typicode.com"
+ # Gem::Net::HTTP.start(hostname) do |http|
+ # # Some HTTP stuff.
+ # end
+ #
+ # === Paths
+ #
+ # A host-specific path identifies a resource on the host:
+ #
+ # _uri = uri.dup
+ # _uri.path = '/todos/1'
+ # hostname = _uri.hostname
+ # path = _uri.path
+ # Gem::Net::HTTP.get(hostname, path)
+ #
+ # === Queries
+ #
+ # A host-specific query adds name/value pairs to the URI:
+ #
+ # _uri = uri.dup
+ # params = {userId: 1, completed: false}
+ # _uri.query = Gem::URI.encode_www_form(params)
+ # _uri # => #<Gem::URI::HTTPS https://jsonplaceholder.typicode.com?userId=1&completed=false>
+ # Gem::Net::HTTP.get(_uri)
+ #
+ # === Fragments
+ #
+ # A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect
+ # in \Gem::Net::HTTP;
+ # the same data is returned, regardless of whether a fragment is included.
+ #
+ # == Request Headers
+ #
+ # Request headers may be used to pass additional information to the host,
+ # similar to arguments passed in a method call;
+ # each header is a name/value pair.
+ #
+ # Each of the \Gem::Net::HTTP methods that sends a request to the host
+ # has optional argument +headers+,
+ # where the headers are expressed as a hash of field-name/value pairs:
+ #
+ # headers = {Accept: 'application/json', Connection: 'Keep-Alive'}
+ # Gem::Net::HTTP.get(uri, headers)
+ #
+ # See lists of both standard request fields and common request fields at
+ # {Request Fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields].
+ # A host may also accept other custom fields.
+ #
+ # == \HTTP Sessions
+ #
+ # A _session_ is a connection between a server (host) and a client that:
+ #
+ # - Is begun by instance method Gem::Net::HTTP#start.
+ # - May contain any number of requests.
+ # - Is ended by instance method Gem::Net::HTTP#finish.
+ #
+ # See example sessions at {Strategies}[rdoc-ref:Gem::Net::HTTP@Strategies].
+ #
+ # === Session Using \Gem::Net::HTTP.start
+ #
+ # If you have many requests to make to a single host (and port),
+ # consider using singleton method Gem::Net::HTTP.start with a block;
+ # the method handles the session automatically by:
+ #
+ # - Calling #start before block execution.
+ # - Executing the block.
+ # - Calling #finish after block execution.
+ #
+ # In the block, you can use these instance methods,
+ # each of which that sends a single request:
+ #
+ # - {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]:
+ #
+ # - #get, #request_get: GET.
+ # - #head, #request_head: HEAD.
+ # - #post, #request_post: POST.
+ # - #delete: DELETE.
+ # - #options: OPTIONS.
+ # - #trace: TRACE.
+ # - #patch: PATCH.
+ #
+ # - {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]:
+ #
+ # - #copy: COPY.
+ # - #lock: LOCK.
+ # - #mkcol: MKCOL.
+ # - #move: MOVE.
+ # - #propfind: PROPFIND.
+ # - #proppatch: PROPPATCH.
+ # - #unlock: UNLOCK.
+ #
+ # === Session Using \Gem::Net::HTTP.start and \Gem::Net::HTTP.finish
+ #
+ # You can manage a session manually using methods #start and #finish:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.start
+ # http.get('/todos/1')
+ # http.get('/todos/2')
+ # http.delete('/posts/1')
+ # http.finish # Needed to free resources.
+ #
+ # === Single-Request Session
+ #
+ # Certain convenience methods automatically handle a session by:
+ #
+ # - Creating an \HTTP object
+ # - Starting a session.
+ # - Sending a single request.
+ # - Finishing the session.
+ # - Destroying the object.
+ #
+ # Such methods that send GET requests:
+ #
+ # - ::get: Returns the string response body.
+ # - ::get_print: Writes the string response body to $stdout.
+ # - ::get_response: Returns a Gem::Net::HTTPResponse object.
+ #
+ # Such methods that send POST requests:
+ #
+ # - ::post: Posts data to the host.
+ # - ::post_form: Posts form data to the host.
+ #
+ # == \HTTP Requests and Responses
+ #
+ # Many of the methods above are convenience methods,
+ # each of which sends a request and returns a string
+ # without directly using \Gem::Net::HTTPRequest and \Gem::Net::HTTPResponse objects.
+ #
+ # You can, however, directly create a request object, send the request,
+ # and retrieve the response object; see:
+ #
+ # - Gem::Net::HTTPRequest.
+ # - Gem::Net::HTTPResponse.
+ #
+ # == Following Redirection
+ #
+ # Each returned response is an instance of a subclass of Gem::Net::HTTPResponse.
+ # See the {response class hierarchy}[rdoc-ref:Gem::Net::HTTPResponse@Response+Subclasses].
+ #
+ # In particular, class Gem::Net::HTTPRedirection is the parent
+ # of all redirection classes.
+ # This allows you to craft a case statement to handle redirections properly:
+ #
+ # def fetch(uri, limit = 10)
+ # # You should choose a better exception.
+ # raise ArgumentError, 'Too many HTTP redirects' if limit == 0
+ #
+ # res = Gem::Net::HTTP.get_response(Gem::URI(uri))
+ # case res
+ # when Gem::Net::HTTPSuccess # Any success class.
+ # res
+ # when Gem::Net::HTTPRedirection # Any redirection class.
+ # location = res['Location']
+ # warn "Redirected to #{location}"
+ # fetch(location, limit - 1)
+ # else # Any other class.
+ # res.value
+ # end
+ # end
+ #
+ # fetch(uri)
+ #
+ # == Basic Authentication
+ #
+ # Basic authentication is performed according to
+ # {RFC2617}[http://www.ietf.org/rfc/rfc2617.txt]:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req.basic_auth('user', 'pass')
+ # res = Gem::Net::HTTP.start(hostname) do |http|
+ # http.request(req)
+ # end
+ #
+ # == Streaming Response Bodies
+ #
+ # By default \Gem::Net::HTTP reads an entire response into memory. If you are
+ # handling large files or wish to implement a progress bar you can instead
+ # stream the body directly to an IO.
+ #
+ # Gem::Net::HTTP.start(hostname) do |http|
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # http.request(req) do |res|
+ # open('t.tmp', 'w') do |f|
+ # res.read_body do |chunk|
+ # f.write chunk
+ # end
+ # end
+ # end
+ # end
+ #
+ # == HTTPS
+ #
+ # HTTPS is enabled for an \HTTP connection by Gem::Net::HTTP#use_ssl=:
+ #
+ # Gem::Net::HTTP.start(hostname, :use_ssl => true) do |http|
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # res = http.request(req)
+ # end
+ #
+ # Or if you simply want to make a GET request, you may pass in a URI
+ # object that has an \HTTPS URL. \Gem::Net::HTTP automatically turns on TLS
+ # verification if the URI object has a 'https' :URI scheme:
+ #
+ # uri # => #<Gem::URI::HTTPS https://jsonplaceholder.typicode.com/>
+ # Gem::Net::HTTP.get(uri)
+ #
+ # == Proxy Server
+ #
+ # An \HTTP object can have
+ # a {proxy server}[https://en.wikipedia.org/wiki/Proxy_server].
+ #
+ # You can create an \HTTP object with a proxy server
+ # using method Gem::Net::HTTP.new or method Gem::Net::HTTP.start.
+ #
+ # The proxy may be defined either by argument +p_addr+
+ # or by environment variable <tt>'http_proxy'</tt>.
+ #
+ # === Proxy Using Argument +p_addr+ as a \String
+ #
+ # When argument +p_addr+ is a string hostname,
+ # the returned +http+ has the given host as its proxy:
+ #
+ # http = Gem::Net::HTTP.new(hostname, nil, 'proxy.example')
+ # http.proxy? # => true
+ # http.proxy_from_env? # => false
+ # http.proxy_address # => "proxy.example"
+ # # These use default values.
+ # http.proxy_port # => 80
+ # http.proxy_user # => nil
+ # http.proxy_pass # => nil
+ #
+ # The port, username, and password for the proxy may also be given:
+ #
+ # http = Gem::Net::HTTP.new(hostname, nil, 'proxy.example', 8000, 'pname', 'ppass')
+ # # => #<Gem::Net::HTTP jsonplaceholder.typicode.com:80 open=false>
+ # http.proxy? # => true
+ # http.proxy_from_env? # => false
+ # http.proxy_address # => "proxy.example"
+ # http.proxy_port # => 8000
+ # http.proxy_user # => "pname"
+ # http.proxy_pass # => "ppass"
+ #
+ # === Proxy Using '<tt>ENV['http_proxy']</tt>'
+ #
+ # When environment variable <tt>'http_proxy'</tt>
+ # is set to a \Gem::URI string,
+ # the returned +http+ will have the server at that URI as its proxy;
+ # note that the \Gem::URI string must have a protocol
+ # such as <tt>'http'</tt> or <tt>'https'</tt>:
+ #
+ # ENV['http_proxy'] = 'http://example.com'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.proxy? # => true
+ # http.proxy_from_env? # => true
+ # http.proxy_address # => "example.com"
+ # # These use default values.
+ # http.proxy_port # => 80
+ # http.proxy_user # => nil
+ # http.proxy_pass # => nil
+ #
+ # The \Gem::URI string may include proxy username, password, and port number:
+ #
+ # ENV['http_proxy'] = 'http://pname:ppass@example.com:8000'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.proxy? # => true
+ # http.proxy_from_env? # => true
+ # http.proxy_address # => "example.com"
+ # http.proxy_port # => 8000
+ # http.proxy_user # => "pname"
+ # http.proxy_pass # => "ppass"
+ #
+ # === Filtering Proxies
+ #
+ # With method Gem::Net::HTTP.new (but not Gem::Net::HTTP.start),
+ # you can use argument +p_no_proxy+ to filter proxies:
+ #
+ # - Reject a certain address:
+ #
+ # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example')
+ # http.proxy_address # => nil
+ #
+ # - Reject certain domains or subdomains:
+ #
+ # http = Gem::Net::HTTP.new('example.com', nil, 'my.proxy.example', 8000, 'pname', 'ppass', 'proxy.example')
+ # http.proxy_address # => nil
+ #
+ # - Reject certain addresses and port combinations:
+ #
+ # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:1234')
+ # http.proxy_address # => "proxy.example"
+ #
+ # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:8000')
+ # http.proxy_address # => nil
+ #
+ # - Reject a list of the types above delimited using a comma:
+ #
+ # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000')
+ # http.proxy_address # => nil
+ #
+ # http = Gem::Net::HTTP.new('example.com', nil, 'my.proxy', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000')
+ # http.proxy_address # => nil
+ #
+ # == Compression and Decompression
+ #
+ # \Gem::Net::HTTP does not compress the body of a request before sending.
+ #
+ # By default, \Gem::Net::HTTP adds header <tt>'Accept-Encoding'</tt>
+ # to a new {request object}[rdoc-ref:Gem::Net::HTTPRequest]:
+ #
+ # Gem::Net::HTTP::Get.new(uri)['Accept-Encoding']
+ # # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
+ #
+ # This requests the server to zip-encode the response body if there is one;
+ # the server is not required to do so.
+ #
+ # \Gem::Net::HTTP does not automatically decompress a response body
+ # if the response has header <tt>'Content-Range'</tt>.
+ #
+ # Otherwise decompression (or not) depends on the value of header
+ # {Content-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-encoding-response-header]:
+ #
+ # - <tt>'deflate'</tt>, <tt>'gzip'</tt>, or <tt>'x-gzip'</tt>:
+ # decompresses the body and deletes the header.
+ # - <tt>'none'</tt> or <tt>'identity'</tt>:
+ # does not decompress the body, but deletes the header.
+ # - Any other value:
+ # leaves the body and header unchanged.
+ #
+ # == What's Here
+ #
+ # This is a categorized summary of methods and attributes.
+ #
+ # === \Gem::Net::HTTP Objects
+ #
+ # - {::new}[rdoc-ref:Gem::Net::HTTP.new]:
+ # Creates a new instance.
+ # - {#inspect}[rdoc-ref:Gem::Net::HTTP#inspect]:
+ # Returns a string representation of +self+.
+ #
+ # === Sessions
+ #
+ # - {::start}[rdoc-ref:Gem::Net::HTTP.start]:
+ # Begins a new session in a new \Gem::Net::HTTP object.
+ # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?]
+ # (aliased as {#active?}[rdoc-ref:Gem::Net::HTTP#active?]):
+ # Returns whether in a session.
+ # - {#finish}[rdoc-ref:Gem::Net::HTTP#finish]:
+ # Ends an active session.
+ # - {#start}[rdoc-ref:Gem::Net::HTTP#start]:
+ # Begins a new session in an existing \Gem::Net::HTTP object (+self+).
+ #
+ # === Connections
+ #
+ # - {:continue_timeout}[rdoc-ref:Gem::Net::HTTP#continue_timeout]:
+ # Returns the continue timeout.
+ # - {#continue_timeout=}[rdoc-ref:Gem::Net::HTTP#continue_timeout=]:
+ # Sets the continue timeout seconds.
+ # - {:keep_alive_timeout}[rdoc-ref:Gem::Net::HTTP#keep_alive_timeout]:
+ # Returns the keep-alive timeout.
+ # - {:keep_alive_timeout=}[rdoc-ref:Gem::Net::HTTP#keep_alive_timeout=]:
+ # Sets the keep-alive timeout.
+ # - {:max_retries}[rdoc-ref:Gem::Net::HTTP#max_retries]:
+ # Returns the maximum retries.
+ # - {#max_retries=}[rdoc-ref:Gem::Net::HTTP#max_retries=]:
+ # Sets the maximum retries.
+ # - {:open_timeout}[rdoc-ref:Gem::Net::HTTP#open_timeout]:
+ # Returns the open timeout.
+ # - {:open_timeout=}[rdoc-ref:Gem::Net::HTTP#open_timeout=]:
+ # Sets the open timeout.
+ # - {:read_timeout}[rdoc-ref:Gem::Net::HTTP#read_timeout]:
+ # Returns the open timeout.
+ # - {:read_timeout=}[rdoc-ref:Gem::Net::HTTP#read_timeout=]:
+ # Sets the read timeout.
+ # - {:ssl_timeout}[rdoc-ref:Gem::Net::HTTP#ssl_timeout]:
+ # Returns the ssl timeout.
+ # - {:ssl_timeout=}[rdoc-ref:Gem::Net::HTTP#ssl_timeout=]:
+ # Sets the ssl timeout.
+ # - {:write_timeout}[rdoc-ref:Gem::Net::HTTP#write_timeout]:
+ # Returns the write timeout.
+ # - {write_timeout=}[rdoc-ref:Gem::Net::HTTP#write_timeout=]:
+ # Sets the write timeout.
+ #
+ # === Requests
+ #
+ # - {::get}[rdoc-ref:Gem::Net::HTTP.get]:
+ # Sends a GET request and returns the string response body.
+ # - {::get_print}[rdoc-ref:Gem::Net::HTTP.get_print]:
+ # Sends a GET request and write the string response body to $stdout.
+ # - {::get_response}[rdoc-ref:Gem::Net::HTTP.get_response]:
+ # Sends a GET request and returns a response object.
+ # - {::post_form}[rdoc-ref:Gem::Net::HTTP.post_form]:
+ # Sends a POST request with form data and returns a response object.
+ # - {::post}[rdoc-ref:Gem::Net::HTTP.post]:
+ # Sends a POST request with data and returns a response object.
+ # - {#copy}[rdoc-ref:Gem::Net::HTTP#copy]:
+ # Sends a COPY request and returns a response object.
+ # - {#delete}[rdoc-ref:Gem::Net::HTTP#delete]:
+ # Sends a DELETE request and returns a response object.
+ # - {#get}[rdoc-ref:Gem::Net::HTTP#get]:
+ # Sends a GET request and returns a response object.
+ # - {#head}[rdoc-ref:Gem::Net::HTTP#head]:
+ # Sends a HEAD request and returns a response object.
+ # - {#lock}[rdoc-ref:Gem::Net::HTTP#lock]:
+ # Sends a LOCK request and returns a response object.
+ # - {#mkcol}[rdoc-ref:Gem::Net::HTTP#mkcol]:
+ # Sends a MKCOL request and returns a response object.
+ # - {#move}[rdoc-ref:Gem::Net::HTTP#move]:
+ # Sends a MOVE request and returns a response object.
+ # - {#options}[rdoc-ref:Gem::Net::HTTP#options]:
+ # Sends a OPTIONS request and returns a response object.
+ # - {#patch}[rdoc-ref:Gem::Net::HTTP#patch]:
+ # Sends a PATCH request and returns a response object.
+ # - {#post}[rdoc-ref:Gem::Net::HTTP#post]:
+ # Sends a POST request and returns a response object.
+ # - {#propfind}[rdoc-ref:Gem::Net::HTTP#propfind]:
+ # Sends a PROPFIND request and returns a response object.
+ # - {#proppatch}[rdoc-ref:Gem::Net::HTTP#proppatch]:
+ # Sends a PROPPATCH request and returns a response object.
+ # - {#put}[rdoc-ref:Gem::Net::HTTP#put]:
+ # Sends a PUT request and returns a response object.
+ # - {#request}[rdoc-ref:Gem::Net::HTTP#request]:
+ # Sends a request and returns a response object.
+ # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get]
+ # (aliased as {#get2}[rdoc-ref:Gem::Net::HTTP#get2]):
+ # Sends a GET request and forms a response object;
+ # if a block given, calls the block with the object,
+ # otherwise returns the object.
+ # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head]
+ # (aliased as {#head2}[rdoc-ref:Gem::Net::HTTP#head2]):
+ # Sends a HEAD request and forms a response object;
+ # if a block given, calls the block with the object,
+ # otherwise returns the object.
+ # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post]
+ # (aliased as {#post2}[rdoc-ref:Gem::Net::HTTP#post2]):
+ # Sends a POST request and forms a response object;
+ # if a block given, calls the block with the object,
+ # otherwise returns the object.
+ # - {#send_request}[rdoc-ref:Gem::Net::HTTP#send_request]:
+ # Sends a request and returns a response object.
+ # - {#trace}[rdoc-ref:Gem::Net::HTTP#trace]:
+ # Sends a TRACE request and returns a response object.
+ # - {#unlock}[rdoc-ref:Gem::Net::HTTP#unlock]:
+ # Sends an UNLOCK request and returns a response object.
+ #
+ # === Responses
+ #
+ # - {:close_on_empty_response}[rdoc-ref:Gem::Net::HTTP#close_on_empty_response]:
+ # Returns whether to close connection on empty response.
+ # - {:close_on_empty_response=}[rdoc-ref:Gem::Net::HTTP#close_on_empty_response=]:
+ # Sets whether to close connection on empty response.
+ # - {:ignore_eof}[rdoc-ref:Gem::Net::HTTP#ignore_eof]:
+ # Returns whether to ignore end-of-file when reading a response body
+ # with <tt>Content-Length</tt> headers.
+ # - {:ignore_eof=}[rdoc-ref:Gem::Net::HTTP#ignore_eof=]:
+ # Sets whether to ignore end-of-file when reading a response body
+ # with <tt>Content-Length</tt> headers.
+ # - {:response_body_encoding}[rdoc-ref:Gem::Net::HTTP#response_body_encoding]:
+ # Returns the encoding to use for the response body.
+ # - {#response_body_encoding=}[rdoc-ref:Gem::Net::HTTP#response_body_encoding=]:
+ # Sets the response body encoding.
+ #
+ # === Proxies
+ #
+ # - {:proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]:
+ # Returns the proxy address.
+ # - {:proxy_address=}[rdoc-ref:Gem::Net::HTTP#proxy_address=]:
+ # Sets the proxy address.
+ # - {::proxy_class?}[rdoc-ref:Gem::Net::HTTP.proxy_class?]:
+ # Returns whether +self+ is a proxy class.
+ # - {#proxy?}[rdoc-ref:Gem::Net::HTTP#proxy?]:
+ # Returns whether +self+ has a proxy.
+ # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]
+ # (aliased as {#proxyaddr}[rdoc-ref:Gem::Net::HTTP#proxyaddr]):
+ # Returns the proxy address.
+ # - {#proxy_from_env?}[rdoc-ref:Gem::Net::HTTP#proxy_from_env?]:
+ # Returns whether the proxy is taken from an environment variable.
+ # - {:proxy_from_env=}[rdoc-ref:Gem::Net::HTTP#proxy_from_env=]:
+ # Sets whether the proxy is to be taken from an environment variable.
+ # - {:proxy_pass}[rdoc-ref:Gem::Net::HTTP#proxy_pass]:
+ # Returns the proxy password.
+ # - {:proxy_pass=}[rdoc-ref:Gem::Net::HTTP#proxy_pass=]:
+ # Sets the proxy password.
+ # - {:proxy_port}[rdoc-ref:Gem::Net::HTTP#proxy_port]:
+ # Returns the proxy port.
+ # - {:proxy_port=}[rdoc-ref:Gem::Net::HTTP#proxy_port=]:
+ # Sets the proxy port.
+ # - {#proxy_user}[rdoc-ref:Gem::Net::HTTP#proxy_user]:
+ # Returns the proxy user name.
+ # - {:proxy_user=}[rdoc-ref:Gem::Net::HTTP#proxy_user=]:
+ # Sets the proxy user.
+ #
+ # === Security
+ #
+ # - {:ca_file}[rdoc-ref:Gem::Net::HTTP#ca_file]:
+ # Returns the path to a CA certification file.
+ # - {:ca_file=}[rdoc-ref:Gem::Net::HTTP#ca_file=]:
+ # Sets the path to a CA certification file.
+ # - {:ca_path}[rdoc-ref:Gem::Net::HTTP#ca_path]:
+ # Returns the path of to CA directory containing certification files.
+ # - {:ca_path=}[rdoc-ref:Gem::Net::HTTP#ca_path=]:
+ # Sets the path of to CA directory containing certification files.
+ # - {:cert}[rdoc-ref:Gem::Net::HTTP#cert]:
+ # Returns the OpenSSL::X509::Certificate object to be used for client certification.
+ # - {:cert=}[rdoc-ref:Gem::Net::HTTP#cert=]:
+ # Sets the OpenSSL::X509::Certificate object to be used for client certification.
+ # - {:cert_store}[rdoc-ref:Gem::Net::HTTP#cert_store]:
+ # Returns the X509::Store to be used for verifying peer certificate.
+ # - {:cert_store=}[rdoc-ref:Gem::Net::HTTP#cert_store=]:
+ # Sets the X509::Store to be used for verifying peer certificate.
+ # - {:ciphers}[rdoc-ref:Gem::Net::HTTP#ciphers]:
+ # Returns the available SSL ciphers.
+ # - {:ciphers=}[rdoc-ref:Gem::Net::HTTP#ciphers=]:
+ # Sets the available SSL ciphers.
+ # - {:extra_chain_cert}[rdoc-ref:Gem::Net::HTTP#extra_chain_cert]:
+ # Returns the extra X509 certificates to be added to the certificate chain.
+ # - {:extra_chain_cert=}[rdoc-ref:Gem::Net::HTTP#extra_chain_cert=]:
+ # Sets the extra X509 certificates to be added to the certificate chain.
+ # - {:key}[rdoc-ref:Gem::Net::HTTP#key]:
+ # Returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
+ # - {:key=}[rdoc-ref:Gem::Net::HTTP#key=]:
+ # Sets the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
+ # - {:max_version}[rdoc-ref:Gem::Net::HTTP#max_version]:
+ # Returns the maximum SSL version.
+ # - {:max_version=}[rdoc-ref:Gem::Net::HTTP#max_version=]:
+ # Sets the maximum SSL version.
+ # - {:min_version}[rdoc-ref:Gem::Net::HTTP#min_version]:
+ # Returns the minimum SSL version.
+ # - {:min_version=}[rdoc-ref:Gem::Net::HTTP#min_version=]:
+ # Sets the minimum SSL version.
+ # - {#peer_cert}[rdoc-ref:Gem::Net::HTTP#peer_cert]:
+ # Returns the X509 certificate chain for the session's socket peer.
+ # - {:ssl_version}[rdoc-ref:Gem::Net::HTTP#ssl_version]:
+ # Returns the SSL version.
+ # - {:ssl_version=}[rdoc-ref:Gem::Net::HTTP#ssl_version=]:
+ # Sets the SSL version.
+ # - {#use_ssl=}[rdoc-ref:Gem::Net::HTTP#use_ssl=]:
+ # Sets whether a new session is to use Transport Layer Security.
+ # - {#use_ssl?}[rdoc-ref:Gem::Net::HTTP#use_ssl?]:
+ # Returns whether +self+ uses SSL.
+ # - {:verify_callback}[rdoc-ref:Gem::Net::HTTP#verify_callback]:
+ # Returns the callback for the server certification verification.
+ # - {:verify_callback=}[rdoc-ref:Gem::Net::HTTP#verify_callback=]:
+ # Sets the callback for the server certification verification.
+ # - {:verify_depth}[rdoc-ref:Gem::Net::HTTP#verify_depth]:
+ # Returns the maximum depth for the certificate chain verification.
+ # - {:verify_depth=}[rdoc-ref:Gem::Net::HTTP#verify_depth=]:
+ # Sets the maximum depth for the certificate chain verification.
+ # - {:verify_hostname}[rdoc-ref:Gem::Net::HTTP#verify_hostname]:
+ # Returns the flags for server the certification verification at the beginning of the SSL/TLS session.
+ # - {:verify_hostname=}[rdoc-ref:Gem::Net::HTTP#verify_hostname=]:
+ # Sets he flags for server the certification verification at the beginning of the SSL/TLS session.
+ # - {:verify_mode}[rdoc-ref:Gem::Net::HTTP#verify_mode]:
+ # Returns the flags for server the certification verification at the beginning of the SSL/TLS session.
+ # - {:verify_mode=}[rdoc-ref:Gem::Net::HTTP#verify_mode=]:
+ # Sets the flags for server the certification verification at the beginning of the SSL/TLS session.
+ #
+ # === Addresses and Ports
+ #
+ # - {:address}[rdoc-ref:Gem::Net::HTTP#address]:
+ # Returns the string host name or host IP.
+ # - {::default_port}[rdoc-ref:Gem::Net::HTTP.default_port]:
+ # Returns integer 80, the default port to use for HTTP requests.
+ # - {::http_default_port}[rdoc-ref:Gem::Net::HTTP.http_default_port]:
+ # Returns integer 80, the default port to use for HTTP requests.
+ # - {::https_default_port}[rdoc-ref:Gem::Net::HTTP.https_default_port]:
+ # Returns integer 443, the default port to use for HTTPS requests.
+ # - {#ipaddr}[rdoc-ref:Gem::Net::HTTP#ipaddr]:
+ # Returns the IP address for the connection.
+ # - {#ipaddr=}[rdoc-ref:Gem::Net::HTTP#ipaddr=]:
+ # Sets the IP address for the connection.
+ # - {:local_host}[rdoc-ref:Gem::Net::HTTP#local_host]:
+ # Returns the string local host used to establish the connection.
+ # - {:local_host=}[rdoc-ref:Gem::Net::HTTP#local_host=]:
+ # Sets the string local host used to establish the connection.
+ # - {:local_port}[rdoc-ref:Gem::Net::HTTP#local_port]:
+ # Returns the integer local port used to establish the connection.
+ # - {:local_port=}[rdoc-ref:Gem::Net::HTTP#local_port=]:
+ # Sets the integer local port used to establish the connection.
+ # - {:port}[rdoc-ref:Gem::Net::HTTP#port]:
+ # Returns the integer port number.
+ #
+ # === \HTTP Version
+ #
+ # - {::version_1_2?}[rdoc-ref:Gem::Net::HTTP.version_1_2?]
+ # (aliased as {::is_version_1_2?}[rdoc-ref:Gem::Net::HTTP.is_version_1_2?]
+ # and {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]):
+ # Returns true; retained for compatibility.
+ #
+ # === Debugging
+ #
+ # - {#set_debug_output}[rdoc-ref:Gem::Net::HTTP#set_debug_output]:
+ # Sets the output stream for debugging.
+ #
+ class HTTP < Protocol
+
+ # :stopdoc:
+ VERSION = "0.4.0"
+ HTTPVersion = '1.1'
+ begin
+ require 'zlib'
+ HAVE_ZLIB=true
+ rescue LoadError
+ HAVE_ZLIB=false
+ end
+ # :startdoc:
+
+ # Returns +true+; retained for compatibility.
+ def HTTP.version_1_2
+ true
+ end
+
+ # Returns +true+; retained for compatibility.
+ def HTTP.version_1_2?
+ true
+ end
+
+ # Returns +false+; retained for compatibility.
+ def HTTP.version_1_1? #:nodoc:
+ false
+ end
+
+ class << HTTP
+ alias is_version_1_1? version_1_1? #:nodoc:
+ alias is_version_1_2? version_1_2? #:nodoc:
+ end
+
+ # :call-seq:
+ # Gem::Net::HTTP.get_print(hostname, path, port = 80) -> nil
+ # Gem::Net::HTTP:get_print(uri, headers = {}, port = uri.port) -> nil
+ #
+ # Like Gem::Net::HTTP.get, but writes the returned body to $stdout;
+ # returns +nil+.
+ def HTTP.get_print(uri_or_host, path_or_headers = nil, port = nil)
+ get_response(uri_or_host, path_or_headers, port) {|res|
+ res.read_body do |chunk|
+ $stdout.print chunk
+ end
+ }
+ nil
+ end
+
+ # :call-seq:
+ # Gem::Net::HTTP.get(hostname, path, port = 80) -> body
+ # Gem::Net::HTTP:get(uri, headers = {}, port = uri.port) -> body
+ #
+ # Sends a GET request and returns the \HTTP response body as a string.
+ #
+ # With string arguments +hostname+ and +path+:
+ #
+ # hostname = 'jsonplaceholder.typicode.com'
+ # path = '/todos/1'
+ # puts Gem::Net::HTTP.get(hostname, path)
+ #
+ # Output:
+ #
+ # {
+ # "userId": 1,
+ # "id": 1,
+ # "title": "delectus aut autem",
+ # "completed": false
+ # }
+ #
+ # With URI object +uri+ and optional hash argument +headers+:
+ #
+ # uri = Gem::URI('https://jsonplaceholder.typicode.com/todos/1')
+ # headers = {'Content-type' => 'application/json; charset=UTF-8'}
+ # Gem::Net::HTTP.get(uri, headers)
+ #
+ # Related:
+ #
+ # - Gem::Net::HTTP::Get: request class for \HTTP method +GET+.
+ # - Gem::Net::HTTP#get: convenience method for \HTTP method +GET+.
+ #
+ def HTTP.get(uri_or_host, path_or_headers = nil, port = nil)
+ get_response(uri_or_host, path_or_headers, port).body
+ end
+
+ # :call-seq:
+ # Gem::Net::HTTP.get_response(hostname, path, port = 80) -> http_response
+ # Gem::Net::HTTP:get_response(uri, headers = {}, port = uri.port) -> http_response
+ #
+ # Like Gem::Net::HTTP.get, but returns a Gem::Net::HTTPResponse object
+ # instead of the body string.
+ def HTTP.get_response(uri_or_host, path_or_headers = nil, port = nil, &block)
+ if path_or_headers && !path_or_headers.is_a?(Hash)
+ host = uri_or_host
+ path = path_or_headers
+ new(host, port || HTTP.default_port).start {|http|
+ return http.request_get(path, &block)
+ }
+ else
+ uri = uri_or_host
+ headers = path_or_headers
+ start(uri.hostname, uri.port,
+ :use_ssl => uri.scheme == 'https') {|http|
+ return http.request_get(uri, headers, &block)
+ }
+ end
+ end
+
+ # Posts data to a host; returns a Gem::Net::HTTPResponse object.
+ #
+ # Argument +url+ must be a URL;
+ # argument +data+ must be a string:
+ #
+ # _uri = uri.dup
+ # _uri.path = '/posts'
+ # data = '{"title": "foo", "body": "bar", "userId": 1}'
+ # headers = {'content-type': 'application/json'}
+ # res = Gem::Net::HTTP.post(_uri, data, headers) # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ # puts res.body
+ #
+ # Output:
+ #
+ # {
+ # "title": "foo",
+ # "body": "bar",
+ # "userId": 1,
+ # "id": 101
+ # }
+ #
+ # Related:
+ #
+ # - Gem::Net::HTTP::Post: request class for \HTTP method +POST+.
+ # - Gem::Net::HTTP#post: convenience method for \HTTP method +POST+.
+ #
+ def HTTP.post(url, data, header = nil)
+ start(url.hostname, url.port,
+ :use_ssl => url.scheme == 'https' ) {|http|
+ http.post(url, data, header)
+ }
+ end
+
+ # Posts data to a host; returns a Gem::Net::HTTPResponse object.
+ #
+ # Argument +url+ must be a URI;
+ # argument +data+ must be a hash:
+ #
+ # _uri = uri.dup
+ # _uri.path = '/posts'
+ # data = {title: 'foo', body: 'bar', userId: 1}
+ # res = Gem::Net::HTTP.post_form(_uri, data) # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ # puts res.body
+ #
+ # Output:
+ #
+ # {
+ # "title": "foo",
+ # "body": "bar",
+ # "userId": "1",
+ # "id": 101
+ # }
+ #
+ def HTTP.post_form(url, params)
+ req = Post.new(url)
+ req.form_data = params
+ req.basic_auth url.user, url.password if url.user
+ start(url.hostname, url.port,
+ :use_ssl => url.scheme == 'https' ) {|http|
+ http.request(req)
+ }
+ end
+
+ #
+ # \HTTP session management
+ #
+
+ # Returns integer +80+, the default port to use for \HTTP requests:
+ #
+ # Gem::Net::HTTP.default_port # => 80
+ #
+ def HTTP.default_port
+ http_default_port()
+ end
+
+ # Returns integer +80+, the default port to use for \HTTP requests:
+ #
+ # Gem::Net::HTTP.http_default_port # => 80
+ #
+ def HTTP.http_default_port
+ 80
+ end
+
+ # Returns integer +443+, the default port to use for HTTPS requests:
+ #
+ # Gem::Net::HTTP.https_default_port # => 443
+ #
+ def HTTP.https_default_port
+ 443
+ end
+
+ def HTTP.socket_type #:nodoc: obsolete
+ BufferedIO
+ end
+
+ # :call-seq:
+ # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) -> http
+ # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) {|http| ... } -> object
+ #
+ # Creates a new \Gem::Net::HTTP object, +http+, via \Gem::Net::HTTP.new:
+ #
+ # - For arguments +address+ and +port+, see Gem::Net::HTTP.new.
+ # - For proxy-defining arguments +p_addr+ through +p_pass+,
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ # - For argument +opts+, see below.
+ #
+ # With no block given:
+ #
+ # - Calls <tt>http.start</tt> with no block (see #start),
+ # which opens a TCP connection and \HTTP session.
+ # - Returns +http+.
+ # - The caller should call #finish to close the session:
+ #
+ # http = Gem::Net::HTTP.start(hostname)
+ # http.started? # => true
+ # http.finish
+ # http.started? # => false
+ #
+ # With a block given:
+ #
+ # - Calls <tt>http.start</tt> with the block (see #start), which:
+ #
+ # - Opens a TCP connection and \HTTP session.
+ # - Calls the block,
+ # which may make any number of requests to the host.
+ # - Closes the \HTTP session and TCP connection on block exit.
+ # - Returns the block's value +object+.
+ #
+ # - Returns +object+.
+ #
+ # Example:
+ #
+ # hostname = 'jsonplaceholder.typicode.com'
+ # Gem::Net::HTTP.start(hostname) do |http|
+ # puts http.get('/todos/1').body
+ # puts http.get('/todos/2').body
+ # end
+ #
+ # Output:
+ #
+ # {
+ # "userId": 1,
+ # "id": 1,
+ # "title": "delectus aut autem",
+ # "completed": false
+ # }
+ # {
+ # "userId": 1,
+ # "id": 2,
+ # "title": "quis ut nam facilis et officia qui",
+ # "completed": false
+ # }
+ #
+ # If the last argument given is a hash, it is the +opts+ hash,
+ # where each key is a method or accessor to be called,
+ # and its value is the value to be set.
+ #
+ # The keys may include:
+ #
+ # - #ca_file
+ # - #ca_path
+ # - #cert
+ # - #cert_store
+ # - #ciphers
+ # - #close_on_empty_response
+ # - +ipaddr+ (calls #ipaddr=)
+ # - #keep_alive_timeout
+ # - #key
+ # - #open_timeout
+ # - #read_timeout
+ # - #ssl_timeout
+ # - #ssl_version
+ # - +use_ssl+ (calls #use_ssl=)
+ # - #verify_callback
+ # - #verify_depth
+ # - #verify_mode
+ # - #write_timeout
+ #
+ # Note: If +port+ is +nil+ and <tt>opts[:use_ssl]</tt> is a truthy value,
+ # the value passed to +new+ is Gem::Net::HTTP.https_default_port, not +port+.
+ #
+ def HTTP.start(address, *arg, &block) # :yield: +http+
+ arg.pop if opt = Hash.try_convert(arg[-1])
+ port, p_addr, p_port, p_user, p_pass = *arg
+ p_addr = :ENV if arg.size < 2
+ port = https_default_port if !port && opt && opt[:use_ssl]
+ http = new(address, port, p_addr, p_port, p_user, p_pass)
+ http.ipaddr = opt[:ipaddr] if opt && opt[:ipaddr]
+
+ if opt
+ if opt[:use_ssl]
+ opt = {verify_mode: OpenSSL::SSL::VERIFY_PEER}.update(opt)
+ end
+ http.methods.grep(/\A(\w+)=\z/) do |meth|
+ key = $1.to_sym
+ opt.key?(key) or next
+ http.__send__(meth, opt[key])
+ end
+ end
+
+ http.start(&block)
+ end
+
+ class << HTTP
+ alias newobj new # :nodoc:
+ end
+
+ # Returns a new \Gem::Net::HTTP object +http+
+ # (but does not open a TCP connection or \HTTP session).
+ #
+ # With only string argument +address+ given
+ # (and <tt>ENV['http_proxy']</tt> undefined or +nil+),
+ # the returned +http+:
+ #
+ # - Has the given address.
+ # - Has the default port number, Gem::Net::HTTP.default_port (80).
+ # - Has no proxy.
+ #
+ # Example:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # # => #<Gem::Net::HTTP jsonplaceholder.typicode.com:80 open=false>
+ # http.address # => "jsonplaceholder.typicode.com"
+ # http.port # => 80
+ # http.proxy? # => false
+ #
+ # With integer argument +port+ also given,
+ # the returned +http+ has the given port:
+ #
+ # http = Gem::Net::HTTP.new(hostname, 8000)
+ # # => #<Gem::Net::HTTP jsonplaceholder.typicode.com:8000 open=false>
+ # http.port # => 8000
+ #
+ # For proxy-defining arguments +p_addr+ through +p_no_proxy+,
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ #
+ def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil)
+ http = super address, port
+
+ if proxy_class? then # from Gem::Net::HTTP::Proxy()
+ http.proxy_from_env = @proxy_from_env
+ http.proxy_address = @proxy_address
+ http.proxy_port = @proxy_port
+ http.proxy_user = @proxy_user
+ http.proxy_pass = @proxy_pass
+ elsif p_addr == :ENV then
+ http.proxy_from_env = true
+ else
+ if p_addr && p_no_proxy && !Gem::URI::Generic.use_proxy?(address, address, port, p_no_proxy)
+ p_addr = nil
+ p_port = nil
+ end
+ http.proxy_address = p_addr
+ http.proxy_port = p_port || default_port
+ http.proxy_user = p_user
+ http.proxy_pass = p_pass
+ end
+
+ http
+ end
+
+ # Creates a new \Gem::Net::HTTP object for the specified server address,
+ # without opening the TCP connection or initializing the \HTTP session.
+ # The +address+ should be a DNS hostname or IP address.
+ def initialize(address, port = nil) # :nodoc:
+ @address = address
+ @port = (port || HTTP.default_port)
+ @ipaddr = nil
+ @local_host = nil
+ @local_port = nil
+ @curr_http_version = HTTPVersion
+ @keep_alive_timeout = 2
+ @last_communicated = nil
+ @close_on_empty_response = false
+ @socket = nil
+ @started = false
+ @open_timeout = 60
+ @read_timeout = 60
+ @write_timeout = 60
+ @continue_timeout = nil
+ @max_retries = 1
+ @debug_output = nil
+ @response_body_encoding = false
+ @ignore_eof = true
+
+ @proxy_from_env = false
+ @proxy_uri = nil
+ @proxy_address = nil
+ @proxy_port = nil
+ @proxy_user = nil
+ @proxy_pass = nil
+
+ @use_ssl = false
+ @ssl_context = nil
+ @ssl_session = nil
+ @sspi_enabled = false
+ SSL_IVNAMES.each do |ivname|
+ instance_variable_set ivname, nil
+ end
+ end
+
+ # Returns a string representation of +self+:
+ #
+ # Gem::Net::HTTP.new(hostname).inspect
+ # # => "#<Gem::Net::HTTP jsonplaceholder.typicode.com:80 open=false>"
+ #
+ def inspect
+ "#<#{self.class} #{@address}:#{@port} open=#{started?}>"
+ end
+
+ # *WARNING* This method opens a serious security hole.
+ # Never use this method in production code.
+ #
+ # Sets the output stream for debugging:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # File.open('t.tmp', 'w') do |file|
+ # http.set_debug_output(file)
+ # http.start
+ # http.get('/nosuch/1')
+ # http.finish
+ # end
+ # puts File.read('t.tmp')
+ #
+ # Output:
+ #
+ # opening connection to jsonplaceholder.typicode.com:80...
+ # opened
+ # <- "GET /nosuch/1 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: jsonplaceholder.typicode.com\r\n\r\n"
+ # -> "HTTP/1.1 404 Not Found\r\n"
+ # -> "Date: Mon, 12 Dec 2022 21:14:11 GMT\r\n"
+ # -> "Content-Type: application/json; charset=utf-8\r\n"
+ # -> "Content-Length: 2\r\n"
+ # -> "Connection: keep-alive\r\n"
+ # -> "X-Powered-By: Express\r\n"
+ # -> "X-Ratelimit-Limit: 1000\r\n"
+ # -> "X-Ratelimit-Remaining: 999\r\n"
+ # -> "X-Ratelimit-Reset: 1670879660\r\n"
+ # -> "Vary: Origin, Accept-Encoding\r\n"
+ # -> "Access-Control-Allow-Credentials: true\r\n"
+ # -> "Cache-Control: max-age=43200\r\n"
+ # -> "Pragma: no-cache\r\n"
+ # -> "Expires: -1\r\n"
+ # -> "X-Content-Type-Options: nosniff\r\n"
+ # -> "Etag: W/\"2-vyGp6PvFo4RvsFtPoIWeCReyIC8\"\r\n"
+ # -> "Via: 1.1 vegur\r\n"
+ # -> "CF-Cache-Status: MISS\r\n"
+ # -> "Server-Timing: cf-q-config;dur=1.3000000762986e-05\r\n"
+ # -> "Report-To: {\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=yOr40jo%2BwS1KHzhTlVpl54beJ5Wx2FcG4gGV0XVrh3X9OlR5q4drUn2dkt5DGO4GDcE%2BVXT7CNgJvGs%2BZleIyMu8CLieFiDIvOviOY3EhHg94m0ZNZgrEdpKD0S85S507l1vsEwEHkoTm%2Ff19SiO\"}],\"group\":\"cf-nel\",\"max_age\":604800}\r\n"
+ # -> "NEL: {\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}\r\n"
+ # -> "Server: cloudflare\r\n"
+ # -> "CF-RAY: 778977dc484ce591-DFW\r\n"
+ # -> "alt-svc: h3=\":443\"; ma=86400, h3-29=\":443\"; ma=86400\r\n"
+ # -> "\r\n"
+ # reading 2 bytes...
+ # -> "{}"
+ # read 2 bytes
+ # Conn keep-alive
+ #
+ def set_debug_output(output)
+ warn 'Gem::Net::HTTP#set_debug_output called after HTTP started', uplevel: 1 if started?
+ @debug_output = output
+ end
+
+ # Returns the string host name or host IP given as argument +address+ in ::new.
+ attr_reader :address
+
+ # Returns the integer port number given as argument +port+ in ::new.
+ attr_reader :port
+
+ # Sets or returns the string local host used to establish the connection;
+ # initially +nil+.
+ attr_accessor :local_host
+
+ # Sets or returns the integer local port used to establish the connection;
+ # initially +nil+.
+ attr_accessor :local_port
+
+ # Returns the encoding to use for the response body;
+ # see #response_body_encoding=.
+ attr_reader :response_body_encoding
+
+ # Sets the encoding to be used for the response body;
+ # returns the encoding.
+ #
+ # The given +value+ may be:
+ #
+ # - An Encoding object.
+ # - The name of an encoding.
+ # - An alias for an encoding name.
+ #
+ # See {Encoding}[rdoc-ref:Encoding].
+ #
+ # Examples:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.response_body_encoding = Encoding::US_ASCII # => #<Encoding:US-ASCII>
+ # http.response_body_encoding = 'US-ASCII' # => "US-ASCII"
+ # http.response_body_encoding = 'ASCII' # => "ASCII"
+ #
+ def response_body_encoding=(value)
+ value = Encoding.find(value) if value.is_a?(String)
+ @response_body_encoding = value
+ end
+
+ # Sets whether to determine the proxy from environment variable
+ # '<tt>ENV['http_proxy']</tt>';
+ # see {Proxy Using ENV['http_proxy']}[rdoc-ref:Gem::Net::HTTP@Proxy+Using+-27ENV-5B-27http_proxy-27-5D-27].
+ attr_writer :proxy_from_env
+
+ # Sets the proxy address;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ attr_writer :proxy_address
+
+ # Sets the proxy port;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ attr_writer :proxy_port
+
+ # Sets the proxy user;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ attr_writer :proxy_user
+
+ # Sets the proxy password;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ attr_writer :proxy_pass
+
+ # Returns the IP address for the connection.
+ #
+ # If the session has not been started,
+ # returns the value set by #ipaddr=,
+ # or +nil+ if it has not been set:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.ipaddr # => nil
+ # http.ipaddr = '172.67.155.76'
+ # http.ipaddr # => "172.67.155.76"
+ #
+ # If the session has been started,
+ # returns the IP address from the socket:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.start
+ # http.ipaddr # => "172.67.155.76"
+ # http.finish
+ #
+ def ipaddr
+ started? ? @socket.io.peeraddr[3] : @ipaddr
+ end
+
+ # Sets the IP address for the connection:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.ipaddr # => nil
+ # http.ipaddr = '172.67.155.76'
+ # http.ipaddr # => "172.67.155.76"
+ #
+ # The IP address may not be set if the session has been started.
+ def ipaddr=(addr)
+ raise IOError, "ipaddr value changed, but session already started" if started?
+ @ipaddr = addr
+ end
+
+ # Sets or returns the numeric (\Integer or \Float) number of seconds
+ # to wait for a connection to open;
+ # initially 60.
+ # If the connection is not made in the given interval,
+ # an exception is raised.
+ attr_accessor :open_timeout
+
+ # Returns the numeric (\Integer or \Float) number of seconds
+ # to wait for one block to be read (via one read(2) call);
+ # see #read_timeout=.
+ attr_reader :read_timeout
+
+ # Returns the numeric (\Integer or \Float) number of seconds
+ # to wait for one block to be written (via one write(2) call);
+ # see #write_timeout=.
+ attr_reader :write_timeout
+
+ # Sets the maximum number of times to retry an idempotent request in case of
+ # \Gem::Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET,
+ # Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError,
+ # Gem::Timeout::Error.
+ # The initial value is 1.
+ #
+ # Argument +retries+ must be a non-negative numeric value:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.max_retries = 2 # => 2
+ # http.max_retries # => 2
+ #
+ def max_retries=(retries)
+ retries = retries.to_int
+ if retries < 0
+ raise ArgumentError, 'max_retries should be non-negative integer number'
+ end
+ @max_retries = retries
+ end
+
+ # Returns the maximum number of times to retry an idempotent request;
+ # see #max_retries=.
+ attr_reader :max_retries
+
+ # Sets the read timeout, in seconds, for +self+ to integer +sec+;
+ # the initial value is 60.
+ #
+ # Argument +sec+ must be a non-negative numeric value:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.read_timeout # => 60
+ # http.get('/todos/1') # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ # http.read_timeout = 0
+ # http.get('/todos/1') # Raises Gem::Net::ReadTimeout.
+ #
+ def read_timeout=(sec)
+ @socket.read_timeout = sec if @socket
+ @read_timeout = sec
+ end
+
+ # Sets the write timeout, in seconds, for +self+ to integer +sec+;
+ # the initial value is 60.
+ #
+ # Argument +sec+ must be a non-negative numeric value:
+ #
+ # _uri = uri.dup
+ # _uri.path = '/posts'
+ # body = 'bar' * 200000
+ # data = <<EOF
+ # {"title": "foo", "body": "#{body}", "userId": "1"}
+ # EOF
+ # headers = {'content-type': 'application/json'}
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.write_timeout # => 60
+ # http.post(_uri.path, data, headers)
+ # # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ # http.write_timeout = 0
+ # http.post(_uri.path, data, headers) # Raises Gem::Net::WriteTimeout.
+ #
+ def write_timeout=(sec)
+ @socket.write_timeout = sec if @socket
+ @write_timeout = sec
+ end
+
+ # Returns the continue timeout value;
+ # see continue_timeout=.
+ attr_reader :continue_timeout
+
+ # Sets the continue timeout value,
+ # which is the number of seconds to wait for an expected 100 Continue response.
+ # If the \HTTP object does not receive a response in this many seconds
+ # it sends the request body.
+ def continue_timeout=(sec)
+ @socket.continue_timeout = sec if @socket
+ @continue_timeout = sec
+ end
+
+ # Sets or returns the numeric (\Integer or \Float) number of seconds
+ # to keep the connection open after a request is sent;
+ # initially 2.
+ # If a new request is made during the given interval,
+ # the still-open connection is used;
+ # otherwise the connection will have been closed
+ # and a new connection is opened.
+ attr_accessor :keep_alive_timeout
+
+ # Sets or returns whether to ignore end-of-file when reading a response body
+ # with <tt>Content-Length</tt> headers;
+ # initially +true+.
+ attr_accessor :ignore_eof
+
+ # Returns +true+ if the \HTTP session has been started:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.started? # => false
+ # http.start
+ # http.started? # => true
+ # http.finish # => nil
+ # http.started? # => false
+ #
+ # Gem::Net::HTTP.start(hostname) do |http|
+ # http.started?
+ # end # => true
+ # http.started? # => false
+ #
+ def started?
+ @started
+ end
+
+ alias active? started? #:nodoc: obsolete
+
+ # Sets or returns whether to close the connection when the response is empty;
+ # initially +false+.
+ attr_accessor :close_on_empty_response
+
+ # Returns +true+ if +self+ uses SSL, +false+ otherwise.
+ # See Gem::Net::HTTP#use_ssl=.
+ def use_ssl?
+ @use_ssl
+ end
+
+ # Sets whether a new session is to use
+ # {Transport Layer Security}[https://en.wikipedia.org/wiki/Transport_Layer_Security]:
+ #
+ # Raises IOError if attempting to change during a session.
+ #
+ # Raises OpenSSL::SSL::SSLError if the port is not an HTTPS port.
+ def use_ssl=(flag)
+ flag = flag ? true : false
+ if started? and @use_ssl != flag
+ raise IOError, "use_ssl value changed, but session already started"
+ end
+ @use_ssl = flag
+ end
+
+ SSL_IVNAMES = [
+ :@ca_file,
+ :@ca_path,
+ :@cert,
+ :@cert_store,
+ :@ciphers,
+ :@extra_chain_cert,
+ :@key,
+ :@ssl_timeout,
+ :@ssl_version,
+ :@min_version,
+ :@max_version,
+ :@verify_callback,
+ :@verify_depth,
+ :@verify_mode,
+ :@verify_hostname,
+ ] # :nodoc:
+ SSL_ATTRIBUTES = [
+ :ca_file,
+ :ca_path,
+ :cert,
+ :cert_store,
+ :ciphers,
+ :extra_chain_cert,
+ :key,
+ :ssl_timeout,
+ :ssl_version,
+ :min_version,
+ :max_version,
+ :verify_callback,
+ :verify_depth,
+ :verify_mode,
+ :verify_hostname,
+ ] # :nodoc:
+
+ # Sets or returns the path to a CA certification file in PEM format.
+ attr_accessor :ca_file
+
+ # Sets or returns the path of to CA directory
+ # containing certification files in PEM format.
+ attr_accessor :ca_path
+
+ # Sets or returns the OpenSSL::X509::Certificate object
+ # to be used for client certification.
+ attr_accessor :cert
+
+ # Sets or returns the X509::Store to be used for verifying peer certificate.
+ attr_accessor :cert_store
+
+ # Sets or returns the available SSL ciphers.
+ # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D].
+ attr_accessor :ciphers
+
+ # Sets or returns the extra X509 certificates to be added to the certificate chain.
+ # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate].
+ attr_accessor :extra_chain_cert
+
+ # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
+ attr_accessor :key
+
+ # Sets or returns the SSL timeout seconds.
+ attr_accessor :ssl_timeout
+
+ # Sets or returns the SSL version.
+ # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D].
+ attr_accessor :ssl_version
+
+ # Sets or returns the minimum SSL version.
+ # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D].
+ attr_accessor :min_version
+
+ # Sets or returns the maximum SSL version.
+ # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D].
+ attr_accessor :max_version
+
+ # Sets or returns the callback for the server certification verification.
+ attr_accessor :verify_callback
+
+ # Sets or returns the maximum depth for the certificate chain verification.
+ attr_accessor :verify_depth
+
+ # Sets or returns the flags for server the certification verification
+ # at the beginning of the SSL/TLS session.
+ # OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable.
+ attr_accessor :verify_mode
+
+ # Sets or returns whether to verify that the server certificate is valid
+ # for the hostname.
+ # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode].
+ attr_accessor :verify_hostname
+
+ # Returns the X509 certificate chain (an array of strings)
+ # for the session's socket peer,
+ # or +nil+ if none.
+ def peer_cert
+ if not use_ssl? or not @socket
+ return nil
+ end
+ @socket.io.peer_cert
+ end
+
+ # Starts an \HTTP session.
+ #
+ # Without a block, returns +self+:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # # => #<Gem::Net::HTTP jsonplaceholder.typicode.com:80 open=false>
+ # http.start
+ # # => #<Gem::Net::HTTP jsonplaceholder.typicode.com:80 open=true>
+ # http.started? # => true
+ # http.finish
+ #
+ # With a block, calls the block with +self+,
+ # finishes the session when the block exits,
+ # and returns the block's value:
+ #
+ # http.start do |http|
+ # http
+ # end
+ # # => #<Gem::Net::HTTP jsonplaceholder.typicode.com:80 open=false>
+ # http.started? # => false
+ #
+ def start # :yield: http
+ raise IOError, 'HTTP session already opened' if @started
+ if block_given?
+ begin
+ do_start
+ return yield(self)
+ ensure
+ do_finish
+ end
+ end
+ do_start
+ self
+ end
+
+ def do_start
+ connect
+ @started = true
+ end
+ private :do_start
+
+ def connect
+ if use_ssl?
+ # reference early to load OpenSSL before connecting,
+ # as OpenSSL may take time to load.
+ @ssl_context = OpenSSL::SSL::SSLContext.new
+ end
+
+ if proxy? then
+ conn_addr = proxy_address
+ conn_port = proxy_port
+ else
+ conn_addr = conn_address
+ conn_port = port
+ end
+
+ debug "opening connection to #{conn_addr}:#{conn_port}..."
+ s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) {
+ begin
+ TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
+ rescue => e
+ raise e, "Failed to open TCP connection to " +
+ "#{conn_addr}:#{conn_port} (#{e.message})"
+ end
+ }
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+ debug "opened"
+ if use_ssl?
+ if proxy?
+ plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
+ write_timeout: @write_timeout,
+ continue_timeout: @continue_timeout,
+ debug_output: @debug_output)
+ buf = +"CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n" \
+ "Host: #{@address}:#{@port}\r\n"
+ if proxy_user
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0')
+ buf << "Proxy-Authorization: Basic #{credential}\r\n"
+ end
+ buf << "\r\n"
+ plain_sock.write(buf)
+ HTTPResponse.read_new(plain_sock).value
+ # assuming nothing left in buffers after successful CONNECT response
+ end
+
+ ssl_parameters = Hash.new
+ iv_list = instance_variables
+ SSL_IVNAMES.each_with_index do |ivname, i|
+ if iv_list.include?(ivname)
+ value = instance_variable_get(ivname)
+ unless value.nil?
+ ssl_parameters[SSL_ATTRIBUTES[i]] = value
+ end
+ end
+ end
+ @ssl_context.set_params(ssl_parameters)
+ unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby
+ @ssl_context.session_cache_mode =
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
+ end
+ if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby
+ @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess }
+ end
+
+ # Still do the post_connection_check below even if connecting
+ # to IP address
+ verify_hostname = @ssl_context.verify_hostname
+
+ # Server Name Indication (SNI) RFC 3546/6066
+ case @address
+ when Gem::Resolv::IPv4::Regex, Gem::Resolv::IPv6::Regex
+ # don't set SNI, as IP addresses in SNI is not valid
+ # per RFC 6066, section 3.
+
+ # Avoid openssl warning
+ @ssl_context.verify_hostname = false
+ else
+ ssl_host_address = @address
+ end
+
+ debug "starting SSL for #{conn_addr}:#{conn_port}..."
+ s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
+ s.sync_close = true
+ s.hostname = ssl_host_address if s.respond_to?(:hostname=) && ssl_host_address
+
+ if @ssl_session and
+ Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout
+ s.session = @ssl_session
+ end
+ ssl_socket_connect(s, @open_timeout)
+ if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && verify_hostname
+ s.post_connection_check(@address)
+ end
+ debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}"
+ end
+ @socket = BufferedIO.new(s, read_timeout: @read_timeout,
+ write_timeout: @write_timeout,
+ continue_timeout: @continue_timeout,
+ debug_output: @debug_output)
+ @last_communicated = nil
+ on_connect
+ rescue => exception
+ if s
+ debug "Conn close because of connect error #{exception}"
+ s.close
+ end
+ raise
+ end
+ private :connect
+
+ def on_connect
+ end
+ private :on_connect
+
+ # Finishes the \HTTP session:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.start
+ # http.started? # => true
+ # http.finish # => nil
+ # http.started? # => false
+ #
+ # Raises IOError if not in a session.
+ def finish
+ raise IOError, 'HTTP session not yet started' unless started?
+ do_finish
+ end
+
+ def do_finish
+ @started = false
+ @socket.close if @socket
+ @socket = nil
+ end
+ private :do_finish
+
+ #
+ # proxy
+ #
+
+ public
+
+ # no proxy
+ @is_proxy_class = false
+ @proxy_from_env = false
+ @proxy_addr = nil
+ @proxy_port = nil
+ @proxy_user = nil
+ @proxy_pass = nil
+
+ # Creates an \HTTP proxy class which behaves like \Gem::Net::HTTP, but
+ # performs all access via the specified proxy.
+ #
+ # This class is obsolete. You may pass these same parameters directly to
+ # \Gem::Net::HTTP.new. See Gem::Net::HTTP.new for details of the arguments.
+ def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc:
+ return self unless p_addr
+
+ Class.new(self) {
+ @is_proxy_class = true
+
+ if p_addr == :ENV then
+ @proxy_from_env = true
+ @proxy_address = nil
+ @proxy_port = nil
+ else
+ @proxy_from_env = false
+ @proxy_address = p_addr
+ @proxy_port = p_port || default_port
+ end
+
+ @proxy_user = p_user
+ @proxy_pass = p_pass
+ }
+ end
+
+ class << HTTP
+ # Returns true if self is a class which was created by HTTP::Proxy.
+ def proxy_class?
+ defined?(@is_proxy_class) ? @is_proxy_class : false
+ end
+
+ # Returns the address of the proxy host, or +nil+ if none;
+ # see Gem::Net::HTTP@Proxy+Server.
+ attr_reader :proxy_address
+
+ # Returns the port number of the proxy host, or +nil+ if none;
+ # see Gem::Net::HTTP@Proxy+Server.
+ attr_reader :proxy_port
+
+ # Returns the user name for accessing the proxy, or +nil+ if none;
+ # see Gem::Net::HTTP@Proxy+Server.
+ attr_reader :proxy_user
+
+ # Returns the password for accessing the proxy, or +nil+ if none;
+ # see Gem::Net::HTTP@Proxy+Server.
+ attr_reader :proxy_pass
+ end
+
+ # Returns +true+ if a proxy server is defined, +false+ otherwise;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ def proxy?
+ !!(@proxy_from_env ? proxy_uri : @proxy_address)
+ end
+
+ # Returns +true+ if the proxy server is defined in the environment,
+ # +false+ otherwise;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ def proxy_from_env?
+ @proxy_from_env
+ end
+
+ # The proxy URI determined from the environment for this connection.
+ def proxy_uri # :nodoc:
+ return if @proxy_uri == false
+ @proxy_uri ||= Gem::URI::HTTP.new(
+ "http", nil, address, port, nil, nil, nil, nil, nil
+ ).find_proxy || false
+ @proxy_uri || nil
+ end
+
+ # Returns the address of the proxy server, if defined, +nil+ otherwise;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ def proxy_address
+ if @proxy_from_env then
+ proxy_uri&.hostname
+ else
+ @proxy_address
+ end
+ end
+
+ # Returns the port number of the proxy server, if defined, +nil+ otherwise;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ def proxy_port
+ if @proxy_from_env then
+ proxy_uri&.port
+ else
+ @proxy_port
+ end
+ end
+
+ # Returns the user name of the proxy server, if defined, +nil+ otherwise;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ def proxy_user
+ if @proxy_from_env
+ user = proxy_uri&.user
+ unescape(user) if user
+ else
+ @proxy_user
+ end
+ end
+
+ # Returns the password of the proxy server, if defined, +nil+ otherwise;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
+ def proxy_pass
+ if @proxy_from_env
+ pass = proxy_uri&.password
+ unescape(pass) if pass
+ else
+ @proxy_pass
+ end
+ end
+
+ alias proxyaddr proxy_address #:nodoc: obsolete
+ alias proxyport proxy_port #:nodoc: obsolete
+
+ private
+
+ def unescape(value)
+ require 'cgi/util'
+ CGI.unescape(value)
+ end
+
+ # without proxy, obsolete
+
+ def conn_address # :nodoc:
+ @ipaddr || address()
+ end
+
+ def conn_port # :nodoc:
+ port()
+ end
+
+ def edit_path(path)
+ if proxy?
+ if path.start_with?("ftp://") || use_ssl?
+ path
+ else
+ "http://#{addr_port}#{path}"
+ end
+ else
+ path
+ end
+ end
+
+ #
+ # HTTP operations
+ #
+
+ public
+
+ # :call-seq:
+ # get(path, initheader = nil) {|res| ... }
+ #
+ # Sends a GET request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Get object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # With a block given, calls the block with the response body:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.get('/todos/1') do |res|
+ # p res
+ # end # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ # Output:
+ #
+ # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}"
+ #
+ # With no block given, simply returns the response object:
+ #
+ # http.get('/') # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ # Related:
+ #
+ # - Gem::Net::HTTP::Get: request class for \HTTP method GET.
+ # - Gem::Net::HTTP.get: sends GET request, returns response body.
+ #
+ def get(path, initheader = nil, dest = nil, &block) # :yield: +body_segment+
+ res = nil
+
+ request(Get.new(path, initheader)) {|r|
+ r.read_body dest, &block
+ res = r
+ }
+ res
+ end
+
+ # Sends a HEAD request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Head object
+ # created from string +path+ and initial headers hash +initheader+:
+ #
+ # res = http.head('/todos/1') # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ # res.body # => nil
+ # res.to_hash.take(3)
+ # # =>
+ # [["date", ["Wed, 15 Feb 2023 15:25:42 GMT"]],
+ # ["content-type", ["application/json; charset=utf-8"]],
+ # ["connection", ["close"]]]
+ #
+ def head(path, initheader = nil)
+ request(Head.new(path, initheader))
+ end
+
+ # :call-seq:
+ # post(path, data, initheader = nil) {|res| ... }
+ #
+ # Sends a POST request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Post object
+ # created from string +path+, string +data+, and initial headers hash +initheader+.
+ #
+ # With a block given, calls the block with the response body:
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.post('/todos', data) do |res|
+ # p res
+ # end # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ # Output:
+ #
+ # "{\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\",\n \"id\": 201\n}"
+ #
+ # With no block given, simply returns the response object:
+ #
+ # http.post('/todos', data) # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ # Related:
+ #
+ # - Gem::Net::HTTP::Post: request class for \HTTP method POST.
+ # - Gem::Net::HTTP.post: sends POST request, returns response body.
+ #
+ def post(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
+ send_entity(path, data, initheader, dest, Post, &block)
+ end
+
+ # :call-seq:
+ # patch(path, data, initheader = nil) {|res| ... }
+ #
+ # Sends a PATCH request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Patch object
+ # created from string +path+, string +data+, and initial headers hash +initheader+.
+ #
+ # With a block given, calls the block with the response body:
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.patch('/todos/1', data) do |res|
+ # p res
+ # end # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ # Output:
+ #
+ # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false,\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\"\n}"
+ #
+ # With no block given, simply returns the response object:
+ #
+ # http.patch('/todos/1', data) # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
+ send_entity(path, data, initheader, dest, Patch, &block)
+ end
+
+ # Sends a PUT request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Put object
+ # created from string +path+, string +data+, and initial headers hash +initheader+.
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.put('/todos/1', data) # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ def put(path, data, initheader = nil)
+ request(Put.new(path, initheader), data)
+ end
+
+ # Sends a PROPPATCH request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Proppatch object
+ # created from string +path+, string +body+, and initial headers hash +initheader+.
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.proppatch('/todos/1', data)
+ #
+ def proppatch(path, body, initheader = nil)
+ request(Proppatch.new(path, initheader), body)
+ end
+
+ # Sends a LOCK request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Lock object
+ # created from string +path+, string +body+, and initial headers hash +initheader+.
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.lock('/todos/1', data)
+ #
+ def lock(path, body, initheader = nil)
+ request(Lock.new(path, initheader), body)
+ end
+
+ # Sends an UNLOCK request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Unlock object
+ # created from string +path+, string +body+, and initial headers hash +initheader+.
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.unlock('/todos/1', data)
+ #
+ def unlock(path, body, initheader = nil)
+ request(Unlock.new(path, initheader), body)
+ end
+
+ # Sends an Options request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Options object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.options('/')
+ #
+ def options(path, initheader = nil)
+ request(Options.new(path, initheader))
+ end
+
+ # Sends a PROPFIND request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Propfind object
+ # created from string +path+, string +body+, and initial headers hash +initheader+.
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.propfind('/todos/1', data)
+ #
+ def propfind(path, body = nil, initheader = {'Depth' => '0'})
+ request(Propfind.new(path, initheader), body)
+ end
+
+ # Sends a DELETE request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Delete object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.delete('/todos/1')
+ #
+ def delete(path, initheader = {'Depth' => 'Infinity'})
+ request(Delete.new(path, initheader))
+ end
+
+ # Sends a MOVE request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Move object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.move('/todos/1')
+ #
+ def move(path, initheader = nil)
+ request(Move.new(path, initheader))
+ end
+
+ # Sends a COPY request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Copy object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.copy('/todos/1')
+ #
+ def copy(path, initheader = nil)
+ request(Copy.new(path, initheader))
+ end
+
+ # Sends a MKCOL request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Mkcol object
+ # created from string +path+, string +body+, and initial headers hash +initheader+.
+ #
+ # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
+ # http.mkcol('/todos/1', data)
+ # http = Gem::Net::HTTP.new(hostname)
+ #
+ def mkcol(path, body = nil, initheader = nil)
+ request(Mkcol.new(path, initheader), body)
+ end
+
+ # Sends a TRACE request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Trace object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.trace('/todos/1')
+ #
+ def trace(path, initheader = nil)
+ request(Trace.new(path, initheader))
+ end
+
+ # Sends a GET request to the server;
+ # forms the response into a Gem::Net::HTTPResponse object.
+ #
+ # The request is based on the Gem::Net::HTTP::Get object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # With no block given, returns the response object:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.request_get('/todos') # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ # With a block given, calls the block with the response object
+ # and returns the response object:
+ #
+ # http.request_get('/todos') do |res|
+ # p res
+ # end # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ # Output:
+ #
+ # #<Gem::Net::HTTPOK 200 OK readbody=false>
+ #
+ def request_get(path, initheader = nil, &block) # :yield: +response+
+ request(Get.new(path, initheader), &block)
+ end
+
+ # Sends a HEAD request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Head object
+ # created from string +path+ and initial headers hash +initheader+.
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.head('/todos/1') # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ def request_head(path, initheader = nil, &block)
+ request(Head.new(path, initheader), &block)
+ end
+
+ # Sends a POST request to the server;
+ # forms the response into a Gem::Net::HTTPResponse object.
+ #
+ # The request is based on the Gem::Net::HTTP::Post object
+ # created from string +path+, string +data+, and initial headers hash +initheader+.
+ #
+ # With no block given, returns the response object:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.post('/todos', 'xyzzy')
+ # # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ # With a block given, calls the block with the response body
+ # and returns the response object:
+ #
+ # http.post('/todos', 'xyzzy') do |res|
+ # p res
+ # end # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ # Output:
+ #
+ # "{\n \"xyzzy\": \"\",\n \"id\": 201\n}"
+ #
+ def request_post(path, data, initheader = nil, &block) # :yield: +response+
+ request Post.new(path, initheader), data, &block
+ end
+
+ # Sends a PUT request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTP::Put object
+ # created from string +path+, string +data+, and initial headers hash +initheader+.
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.put('/todos/1', 'xyzzy')
+ # # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ def request_put(path, data, initheader = nil, &block) #:nodoc:
+ request Put.new(path, initheader), data, &block
+ end
+
+ alias get2 request_get #:nodoc: obsolete
+ alias head2 request_head #:nodoc: obsolete
+ alias post2 request_post #:nodoc: obsolete
+ alias put2 request_put #:nodoc: obsolete
+
+ # Sends an \HTTP request to the server;
+ # returns an instance of a subclass of Gem::Net::HTTPResponse.
+ #
+ # The request is based on the Gem::Net::HTTPRequest object
+ # created from string +path+, string +data+, and initial headers hash +header+.
+ # That object is an instance of the
+ # {subclass of Gem::Net::HTTPRequest}[rdoc-ref:Gem::Net::HTTPRequest@Request+Subclasses],
+ # that corresponds to the given uppercase string +name+,
+ # which must be
+ # an {HTTP request method}[https://en.wikipedia.org/wiki/HTTP#Request_methods]
+ # or a {WebDAV request method}[https://en.wikipedia.org/wiki/WebDAV#Implementation].
+ #
+ # Examples:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.send_request('GET', '/todos/1')
+ # # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ # http.send_request('POST', '/todos', 'xyzzy')
+ # # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ def send_request(name, path, data = nil, header = nil)
+ has_response_body = name != 'HEAD'
+ r = HTTPGenericRequest.new(name,(data ? true : false),has_response_body,path,header)
+ request r, data
+ end
+
+ # Sends the given request +req+ to the server;
+ # forms the response into a Gem::Net::HTTPResponse object.
+ #
+ # The given +req+ must be an instance of a
+ # {subclass of Gem::Net::HTTPRequest}[rdoc-ref:Gem::Net::HTTPRequest@Request+Subclasses].
+ # Argument +body+ should be given only if needed for the request.
+ #
+ # With no block given, returns the response object:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ #
+ # req = Gem::Net::HTTP::Get.new('/todos/1')
+ # http.request(req)
+ # # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ # req = Gem::Net::HTTP::Post.new('/todos')
+ # http.request(req, 'xyzzy')
+ # # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ # With a block given, calls the block with the response and returns the response:
+ #
+ # req = Gem::Net::HTTP::Get.new('/todos/1')
+ # http.request(req) do |res|
+ # p res
+ # end # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+ #
+ # Output:
+ #
+ # #<Gem::Net::HTTPOK 200 OK readbody=false>
+ #
+ def request(req, body = nil, &block) # :yield: +response+
+ unless started?
+ start {
+ req['connection'] ||= 'close'
+ return request(req, body, &block)
+ }
+ end
+ if proxy_user()
+ req.proxy_basic_auth proxy_user(), proxy_pass() unless use_ssl?
+ end
+ req.set_body_internal body
+ res = transport_request(req, &block)
+ if sspi_auth?(res)
+ sspi_auth(req)
+ res = transport_request(req, &block)
+ end
+ res
+ end
+
+ private
+
+ # Executes a request which uses a representation
+ # and returns its body.
+ def send_entity(path, data, initheader, dest, type, &block)
+ res = nil
+ request(type.new(path, initheader), data) {|r|
+ r.read_body dest, &block
+ res = r
+ }
+ res
+ end
+
+ IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc:
+
+ def transport_request(req)
+ count = 0
+ begin
+ begin_transport req
+ res = catch(:response) {
+ begin
+ req.exec @socket, @curr_http_version, edit_path(req.path)
+ rescue Errno::EPIPE
+ # Failure when writing full request, but we can probably
+ # still read the received response.
+ end
+
+ begin
+ res = HTTPResponse.read_new(@socket)
+ res.decode_content = req.decode_content
+ res.body_encoding = @response_body_encoding
+ res.ignore_eof = @ignore_eof
+ end while res.kind_of?(HTTPInformation)
+
+ res.uri = req.uri
+
+ res
+ }
+ res.reading_body(@socket, req.response_body_permitted?) {
+ yield res if block_given?
+ }
+ rescue Gem::Net::OpenTimeout
+ raise
+ rescue Gem::Net::ReadTimeout, IOError, EOFError,
+ Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, Errno::ETIMEDOUT,
+ # avoid a dependency on OpenSSL
+ defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : IOError,
+ Gem::Timeout::Error => exception
+ if count < max_retries && IDEMPOTENT_METHODS_.include?(req.method)
+ count += 1
+ @socket.close if @socket
+ debug "Conn close because of error #{exception}, and retry"
+ retry
+ end
+ debug "Conn close because of error #{exception}"
+ @socket.close if @socket
+ raise
+ end
+
+ end_transport req, res
+ res
+ rescue => exception
+ debug "Conn close because of error #{exception}"
+ @socket.close if @socket
+ raise exception
+ end
+
+ def begin_transport(req)
+ if @socket.closed?
+ connect
+ elsif @last_communicated
+ if @last_communicated + @keep_alive_timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ debug 'Conn close because of keep_alive_timeout'
+ @socket.close
+ connect
+ elsif @socket.io.to_io.wait_readable(0) && @socket.eof?
+ debug "Conn close because of EOF"
+ @socket.close
+ connect
+ end
+ end
+
+ if not req.response_body_permitted? and @close_on_empty_response
+ req['connection'] ||= 'close'
+ end
+
+ req.update_uri address, port, use_ssl?
+ req['host'] ||= addr_port()
+ end
+
+ def end_transport(req, res)
+ @curr_http_version = res.http_version
+ @last_communicated = nil
+ if @socket.closed?
+ debug 'Conn socket closed'
+ elsif not res.body and @close_on_empty_response
+ debug 'Conn close'
+ @socket.close
+ elsif keep_alive?(req, res)
+ debug 'Conn keep-alive'
+ @last_communicated = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ else
+ debug 'Conn close'
+ @socket.close
+ end
+ end
+
+ def keep_alive?(req, res)
+ return false if req.connection_close?
+ if @curr_http_version <= '1.0'
+ res.connection_keep_alive?
+ else # HTTP/1.1 or later
+ not res.connection_close?
+ end
+ end
+
+ def sspi_auth?(res)
+ return false unless @sspi_enabled
+ if res.kind_of?(HTTPProxyAuthenticationRequired) and
+ proxy? and res["Proxy-Authenticate"].include?("Negotiate")
+ begin
+ require 'win32/sspi'
+ true
+ rescue LoadError
+ false
+ end
+ else
+ false
+ end
+ end
+
+ def sspi_auth(req)
+ n = Win32::SSPI::NegotiateAuth.new
+ req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}"
+ # Some versions of ISA will close the connection if this isn't present.
+ req["Connection"] = "Keep-Alive"
+ req["Proxy-Connection"] = "Keep-Alive"
+ res = transport_request(req)
+ authphrase = res["Proxy-Authenticate"] or return res
+ req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication(authphrase)}"
+ rescue => err
+ raise HTTPAuthenticationError.new('HTTP authentication failed', err)
+ end
+
+ #
+ # utils
+ #
+
+ private
+
+ def addr_port
+ addr = address
+ addr = "[#{addr}]" if addr.include?(":")
+ default_port = use_ssl? ? HTTP.https_default_port : HTTP.http_default_port
+ default_port == port ? addr : "#{addr}:#{port}"
+ end
+
+ # Adds a message to debugging output
+ def debug(msg)
+ return unless @debug_output
+ @debug_output << msg
+ @debug_output << "\n"
+ end
+
+ alias_method :D, :debug
+ end
+
+end
+
+require_relative 'http/exceptions'
+
+require_relative 'http/header'
+
+require_relative 'http/generic_request'
+require_relative 'http/request'
+require_relative 'http/requests'
+
+require_relative 'http/response'
+require_relative 'http/responses'
+
+require_relative 'http/proxy_delta'
+
+require_relative 'http/backward'
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/backward.rb b/lib/rubygems/vendor/net-http/lib/net/http/backward.rb
new file mode 100644
index 0000000000..10dbc16224
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/backward.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+# for backward compatibility
+
+# :enddoc:
+
+class Gem::Net::HTTP
+ ProxyMod = ProxyDelta
+ deprecate_constant :ProxyMod
+end
+
+module Gem::Net::NetPrivate
+ HTTPRequest = ::Gem::Net::HTTPRequest
+ deprecate_constant :HTTPRequest
+end
+
+module Gem::Net
+ HTTPSession = HTTP
+
+ HTTPInformationCode = HTTPInformation
+ HTTPSuccessCode = HTTPSuccess
+ HTTPRedirectionCode = HTTPRedirection
+ HTTPRetriableCode = HTTPRedirection
+ HTTPClientErrorCode = HTTPClientError
+ HTTPFatalErrorCode = HTTPClientError
+ HTTPServerErrorCode = HTTPServerError
+ HTTPResponseReceiver = HTTPResponse
+
+ HTTPResponceReceiver = HTTPResponse # Typo since 2001
+
+ deprecate_constant :HTTPSession,
+ :HTTPInformationCode,
+ :HTTPSuccessCode,
+ :HTTPRedirectionCode,
+ :HTTPRetriableCode,
+ :HTTPClientErrorCode,
+ :HTTPFatalErrorCode,
+ :HTTPServerErrorCode,
+ :HTTPResponseReceiver,
+ :HTTPResponceReceiver
+end
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb
new file mode 100644
index 0000000000..c629c0113b
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+module Gem::Net
+ # Gem::Net::HTTP exception class.
+ # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use
+ # its subclasses.
+ module HTTPExceptions
+ def initialize(msg, res) #:nodoc:
+ super msg
+ @response = res
+ end
+ attr_reader :response
+ alias data response #:nodoc: obsolete
+ end
+
+ class HTTPError < ProtocolError
+ include HTTPExceptions
+ end
+
+ class HTTPRetriableError < ProtoRetriableError
+ include HTTPExceptions
+ end
+
+ class HTTPClientException < ProtoServerError
+ include HTTPExceptions
+ end
+
+ class HTTPFatalError < ProtoFatalError
+ include HTTPExceptions
+ end
+
+ # We cannot use the name "HTTPServerError", it is the name of the response.
+ HTTPServerException = HTTPClientException # :nodoc:
+ deprecate_constant(:HTTPServerException)
+end
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb
new file mode 100644
index 0000000000..5cfe75a7cd
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb
@@ -0,0 +1,414 @@
+# frozen_string_literal: true
+#
+# \HTTPGenericRequest is the parent of the Gem::Net::HTTPRequest class.
+#
+# Do not use this directly; instead, use a subclass of Gem::Net::HTTPRequest.
+#
+# == About the Examples
+#
+# :include: doc/net-http/examples.rdoc
+#
+class Gem::Net::HTTPGenericRequest
+
+ include Gem::Net::HTTPHeader
+
+ def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc:
+ @method = m
+ @request_has_body = reqbody
+ @response_has_body = resbody
+
+ if Gem::URI === uri_or_path then
+ raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path
+ hostname = uri_or_path.hostname
+ raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0)
+ @uri = uri_or_path.dup
+ host = @uri.hostname.dup
+ host << ":" << @uri.port.to_s if @uri.port != @uri.default_port
+ @path = uri_or_path.request_uri
+ raise ArgumentError, "no HTTP request path given" unless @path
+ else
+ @uri = nil
+ host = nil
+ raise ArgumentError, "no HTTP request path given" unless uri_or_path
+ raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty?
+ @path = uri_or_path.dup
+ end
+
+ @decode_content = false
+
+ if Gem::Net::HTTP::HAVE_ZLIB then
+ if !initheader ||
+ !initheader.keys.any? { |k|
+ %w[accept-encoding range].include? k.downcase
+ } then
+ @decode_content = true if @response_has_body
+ initheader = initheader ? initheader.dup : {}
+ initheader["accept-encoding"] =
+ "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
+ end
+ end
+
+ initialize_http_header initheader
+ self['Accept'] ||= '*/*'
+ self['User-Agent'] ||= 'Ruby'
+ self['Host'] ||= host if host
+ @body = nil
+ @body_stream = nil
+ @body_data = nil
+ end
+
+ # Returns the string method name for the request:
+ #
+ # Gem::Net::HTTP::Get.new(uri).method # => "GET"
+ # Gem::Net::HTTP::Post.new(uri).method # => "POST"
+ #
+ attr_reader :method
+
+ # Returns the string path for the request:
+ #
+ # Gem::Net::HTTP::Get.new(uri).path # => "/"
+ # Gem::Net::HTTP::Post.new('example.com').path # => "example.com"
+ #
+ attr_reader :path
+
+ # Returns the Gem::URI object for the request, or +nil+ if none:
+ #
+ # Gem::Net::HTTP::Get.new(uri).uri
+ # # => #<Gem::URI::HTTPS https://jsonplaceholder.typicode.com/>
+ # Gem::Net::HTTP::Get.new('example.com').uri # => nil
+ #
+ attr_reader :uri
+
+ # Returns +false+ if the request's header <tt>'Accept-Encoding'</tt>
+ # has been set manually or deleted
+ # (indicating that the user intends to handle encoding in the response),
+ # +true+ otherwise:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri) # => #<Gem::Net::HTTP::Get GET>
+ # req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
+ # req.decode_content # => true
+ # req['Accept-Encoding'] = 'foo'
+ # req.decode_content # => false
+ # req.delete('Accept-Encoding')
+ # req.decode_content # => false
+ #
+ attr_reader :decode_content
+
+ # Returns a string representation of the request:
+ #
+ # Gem::Net::HTTP::Post.new(uri).inspect # => "#<Gem::Net::HTTP::Post POST>"
+ #
+ def inspect
+ "\#<#{self.class} #{@method}>"
+ end
+
+ ##
+ # Don't automatically decode response content-encoding if the user indicates
+ # they want to handle it.
+
+ def []=(key, val) # :nodoc:
+ @decode_content = false if key.downcase == 'accept-encoding'
+
+ super key, val
+ end
+
+ # Returns whether the request may have a body:
+ #
+ # Gem::Net::HTTP::Post.new(uri).request_body_permitted? # => true
+ # Gem::Net::HTTP::Get.new(uri).request_body_permitted? # => false
+ #
+ def request_body_permitted?
+ @request_has_body
+ end
+
+ # Returns whether the response may have a body:
+ #
+ # Gem::Net::HTTP::Post.new(uri).response_body_permitted? # => true
+ # Gem::Net::HTTP::Head.new(uri).response_body_permitted? # => false
+ #
+ def response_body_permitted?
+ @response_has_body
+ end
+
+ def body_exist? # :nodoc:
+ warn "Gem::Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE
+ response_body_permitted?
+ end
+
+ # Returns the string body for the request, or +nil+ if there is none:
+ #
+ # req = Gem::Net::HTTP::Post.new(uri)
+ # req.body # => nil
+ # req.body = '{"title": "foo","body": "bar","userId": 1}'
+ # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}"
+ #
+ attr_reader :body
+
+ # Sets the body for the request:
+ #
+ # req = Gem::Net::HTTP::Post.new(uri)
+ # req.body # => nil
+ # req.body = '{"title": "foo","body": "bar","userId": 1}'
+ # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}"
+ #
+ def body=(str)
+ @body = str
+ @body_stream = nil
+ @body_data = nil
+ str
+ end
+
+ # Returns the body stream object for the request, or +nil+ if there is none:
+ #
+ # req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
+ # req.body_stream # => nil
+ # require 'stringio'
+ # req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8>
+ # req.body_stream # => #<StringIO:0x0000027d1e5affa8>
+ #
+ attr_reader :body_stream
+
+ # Sets the body stream for the request:
+ #
+ # req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
+ # req.body_stream # => nil
+ # require 'stringio'
+ # req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8>
+ # req.body_stream # => #<StringIO:0x0000027d1e5affa8>
+ #
+ def body_stream=(input)
+ @body = nil
+ @body_stream = input
+ @body_data = nil
+ input
+ end
+
+ def set_body_internal(str) #:nodoc: internal use only
+ raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream)
+ self.body = str if str
+ if @body.nil? && @body_stream.nil? && @body_data.nil? && request_body_permitted?
+ self.body = ''
+ end
+ end
+
+ #
+ # write
+ #
+
+ def exec(sock, ver, path) #:nodoc: internal use only
+ if @body
+ send_request_with_body sock, ver, path, @body
+ elsif @body_stream
+ send_request_with_body_stream sock, ver, path, @body_stream
+ elsif @body_data
+ send_request_with_body_data sock, ver, path, @body_data
+ else
+ write_header sock, ver, path
+ end
+ end
+
+ def update_uri(addr, port, ssl) # :nodoc: internal use only
+ # reflect the connection and @path to @uri
+ return unless @uri
+
+ if ssl
+ scheme = 'https'
+ klass = Gem::URI::HTTPS
+ else
+ scheme = 'http'
+ klass = Gem::URI::HTTP
+ end
+
+ if host = self['host']
+ host.sub!(/:.*/m, '')
+ elsif host = @uri.host
+ else
+ host = addr
+ end
+ # convert the class of the Gem::URI
+ if @uri.is_a?(klass)
+ @uri.host = host
+ @uri.port = port
+ else
+ @uri = klass.new(
+ scheme, @uri.userinfo,
+ host, port, nil,
+ @uri.path, nil, @uri.query, nil)
+ end
+ end
+
+ private
+
+ class Chunker #:nodoc:
+ def initialize(sock)
+ @sock = sock
+ @prev = nil
+ end
+
+ def write(buf)
+ # avoid memcpy() of buf, buf can huge and eat memory bandwidth
+ rv = buf.bytesize
+ @sock.write("#{rv.to_s(16)}\r\n", buf, "\r\n")
+ rv
+ end
+
+ def finish
+ @sock.write("0\r\n\r\n")
+ end
+ end
+
+ def send_request_with_body(sock, ver, path, body)
+ self.content_length = body.bytesize
+ delete 'Transfer-Encoding'
+ supply_default_content_type
+ write_header sock, ver, path
+ wait_for_continue sock, ver if sock.continue_timeout
+ sock.write body
+ end
+
+ def send_request_with_body_stream(sock, ver, path, f)
+ unless content_length() or chunked?
+ raise ArgumentError,
+ "Content-Length not given and Transfer-Encoding is not `chunked'"
+ end
+ supply_default_content_type
+ write_header sock, ver, path
+ wait_for_continue sock, ver if sock.continue_timeout
+ if chunked?
+ chunker = Chunker.new(sock)
+ IO.copy_stream(f, chunker)
+ chunker.finish
+ else
+ IO.copy_stream(f, sock)
+ end
+ end
+
+ def send_request_with_body_data(sock, ver, path, params)
+ if /\Amultipart\/form-data\z/i !~ self.content_type
+ self.content_type = 'application/x-www-form-urlencoded'
+ return send_request_with_body(sock, ver, path, Gem::URI.encode_www_form(params))
+ end
+
+ opt = @form_option.dup
+ require 'securerandom' unless defined?(SecureRandom)
+ opt[:boundary] ||= SecureRandom.urlsafe_base64(40)
+ self.set_content_type(self.content_type, boundary: opt[:boundary])
+ if chunked?
+ write_header sock, ver, path
+ encode_multipart_form_data(sock, params, opt)
+ else
+ require 'tempfile'
+ file = Tempfile.new('multipart')
+ file.binmode
+ encode_multipart_form_data(file, params, opt)
+ file.rewind
+ self.content_length = file.size
+ write_header sock, ver, path
+ IO.copy_stream(file, sock)
+ file.close(true)
+ end
+ end
+
+ def encode_multipart_form_data(out, params, opt)
+ charset = opt[:charset]
+ boundary = opt[:boundary]
+ require 'securerandom' unless defined?(SecureRandom)
+ boundary ||= SecureRandom.urlsafe_base64(40)
+ chunked_p = chunked?
+
+ buf = +''
+ params.each do |key, value, h={}|
+ key = quote_string(key, charset)
+ filename =
+ h.key?(:filename) ? h[:filename] :
+ value.respond_to?(:to_path) ? File.basename(value.to_path) :
+ nil
+
+ buf << "--#{boundary}\r\n"
+ if filename
+ filename = quote_string(filename, charset)
+ type = h[:content_type] || 'application/octet-stream'
+ buf << "Content-Disposition: form-data; " \
+ "name=\"#{key}\"; filename=\"#{filename}\"\r\n" \
+ "Content-Type: #{type}\r\n\r\n"
+ if !out.respond_to?(:write) || !value.respond_to?(:read)
+ # if +out+ is not an IO or +value+ is not an IO
+ buf << (value.respond_to?(:read) ? value.read : value)
+ elsif value.respond_to?(:size) && chunked_p
+ # if +out+ is an IO and +value+ is a File, use IO.copy_stream
+ flush_buffer(out, buf, chunked_p)
+ out << "%x\r\n" % value.size if chunked_p
+ IO.copy_stream(value, out)
+ out << "\r\n" if chunked_p
+ else
+ # +out+ is an IO, and +value+ is not a File but an IO
+ flush_buffer(out, buf, chunked_p)
+ 1 while flush_buffer(out, value.read(4096), chunked_p)
+ end
+ else
+ # non-file field:
+ # HTML5 says, "The parts of the generated multipart/form-data
+ # resource that correspond to non-file fields must not have a
+ # Content-Type header specified."
+ buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
+ buf << (value.respond_to?(:read) ? value.read : value)
+ end
+ buf << "\r\n"
+ end
+ buf << "--#{boundary}--\r\n"
+ flush_buffer(out, buf, chunked_p)
+ out << "0\r\n\r\n" if chunked_p
+ end
+
+ def quote_string(str, charset)
+ str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset
+ str.gsub(/[\\"]/, '\\\\\&')
+ end
+
+ def flush_buffer(out, buf, chunked_p)
+ return unless buf
+ out << "%x\r\n"%buf.bytesize if chunked_p
+ out << buf
+ out << "\r\n" if chunked_p
+ buf.clear
+ end
+
+ def supply_default_content_type
+ return if content_type()
+ warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE
+ set_content_type 'application/x-www-form-urlencoded'
+ end
+
+ ##
+ # Waits up to the continue timeout for a response from the server provided
+ # we're speaking HTTP 1.1 and are expecting a 100-continue response.
+
+ def wait_for_continue(sock, ver)
+ if ver >= '1.1' and @header['expect'] and
+ @header['expect'].include?('100-continue')
+ if sock.io.to_io.wait_readable(sock.continue_timeout)
+ res = Gem::Net::HTTPResponse.read_new(sock)
+ unless res.kind_of?(Gem::Net::HTTPContinue)
+ res.decode_content = @decode_content
+ throw :response, res
+ end
+ end
+ end
+ end
+
+ def write_header(sock, ver, path)
+ reqline = "#{@method} #{path} HTTP/#{ver}"
+ if /[\r\n]/ =~ reqline
+ raise ArgumentError, "A Request-Line must not contain CR or LF"
+ end
+ buf = +''
+ buf << reqline << "\r\n"
+ each_capitalized do |k,v|
+ buf << "#{k}: #{v}\r\n"
+ end
+ buf << "\r\n"
+ sock.write buf
+ end
+
+end
+
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/header.rb b/lib/rubygems/vendor/net-http/lib/net/http/header.rb
new file mode 100644
index 0000000000..1488e60068
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/header.rb
@@ -0,0 +1,981 @@
+# frozen_string_literal: true
+#
+# The \HTTPHeader module provides access to \HTTP headers.
+#
+# The module is included in:
+#
+# - Gem::Net::HTTPGenericRequest (and therefore Gem::Net::HTTPRequest).
+# - Gem::Net::HTTPResponse.
+#
+# The headers are a hash-like collection of key/value pairs called _fields_.
+#
+# == Request and Response Fields
+#
+# Headers may be included in:
+#
+# - A Gem::Net::HTTPRequest object:
+# the object's headers will be sent with the request.
+# Any fields may be defined in the request;
+# see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters].
+# - A Gem::Net::HTTPResponse object:
+# the objects headers are usually those returned from the host.
+# Fields may be retrieved from the object;
+# see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters]
+# and {Iterators}[rdoc-ref:Gem::Net::HTTPHeader@Iterators].
+#
+# Exactly which fields should be sent or expected depends on the host;
+# see:
+#
+# - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields].
+# - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields].
+#
+# == About the Examples
+#
+# :include: doc/net-http/examples.rdoc
+#
+# == Fields
+#
+# A header field is a key/value pair.
+#
+# === Field Keys
+#
+# A field key may be:
+#
+# - A string: Key <tt>'Accept'</tt> is treated as if it were
+# <tt>'Accept'.downcase</tt>; i.e., <tt>'accept'</tt>.
+# - A symbol: Key <tt>:Accept</tt> is treated as if it were
+# <tt>:Accept.to_s.downcase</tt>; i.e., <tt>'accept'</tt>.
+#
+# Examples:
+#
+# req = Gem::Net::HTTP::Get.new(uri)
+# req[:accept] # => "*/*"
+# req['Accept'] # => "*/*"
+# req['ACCEPT'] # => "*/*"
+#
+# req['accept'] = 'text/html'
+# req[:accept] = 'text/html'
+# req['ACCEPT'] = 'text/html'
+#
+# === Field Values
+#
+# A field value may be returned as an array of strings or as a string:
+#
+# - These methods return field values as arrays:
+#
+# - #get_fields: Returns the array value for the given key,
+# or +nil+ if it does not exist.
+# - #to_hash: Returns a hash of all header fields:
+# each key is a field name; its value is the array value for the field.
+#
+# - These methods return field values as string;
+# the string value for a field is equivalent to
+# <tt>self[key.downcase.to_s].join(', '))</tt>:
+#
+# - #[]: Returns the string value for the given key,
+# or +nil+ if it does not exist.
+# - #fetch: Like #[], but accepts a default value
+# to be returned if the key does not exist.
+#
+# The field value may be set:
+#
+# - #[]=: Sets the value for the given key;
+# the given value may be a string, a symbol, an array, or a hash.
+# - #add_field: Adds a given value to a value for the given key
+# (not overwriting the existing value).
+# - #delete: Deletes the field for the given key.
+#
+# Example field values:
+#
+# - \String:
+#
+# req['Accept'] = 'text/html' # => "text/html"
+# req['Accept'] # => "text/html"
+# req.get_fields('Accept') # => ["text/html"]
+#
+# - \Symbol:
+#
+# req['Accept'] = :text # => :text
+# req['Accept'] # => "text"
+# req.get_fields('Accept') # => ["text"]
+#
+# - Simple array:
+#
+# req[:foo] = %w[bar baz bat]
+# req[:foo] # => "bar, baz, bat"
+# req.get_fields(:foo) # => ["bar", "baz", "bat"]
+#
+# - Simple hash:
+#
+# req[:foo] = {bar: 0, baz: 1, bat: 2}
+# req[:foo] # => "bar, 0, baz, 1, bat, 2"
+# req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"]
+#
+# - Nested:
+#
+# req[:foo] = [%w[bar baz], {bat: 0, bam: 1}]
+# req[:foo] # => "bar, baz, bat, 0, bam, 1"
+# req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"]
+#
+# req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}}
+# req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1"
+# req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"]
+#
+# == Convenience Methods
+#
+# Various convenience methods retrieve values, set values, query values,
+# set form values, or iterate over fields.
+#
+# === Setters
+#
+# \Method #[]= can set any field, but does little to validate the new value;
+# some of the other setter methods provide some validation:
+#
+# - #[]=: Sets the string or array value for the given key.
+# - #add_field: Creates or adds to the array value for the given key.
+# - #basic_auth: Sets the string authorization header for <tt>'Authorization'</tt>.
+# - #content_length=: Sets the integer length for field <tt>'Content-Length</tt>.
+# - #content_type=: Sets the string value for field <tt>'Content-Type'</tt>.
+# - #proxy_basic_auth: Sets the string authorization header for <tt>'Proxy-Authorization'</tt>.
+# - #set_range: Sets the value for field <tt>'Range'</tt>.
+#
+# === Form Setters
+#
+# - #set_form: Sets an HTML form data set.
+# - #set_form_data: Sets header fields and a body from HTML form data.
+#
+# === Getters
+#
+# \Method #[] can retrieve the value of any field that exists,
+# but always as a string;
+# some of the other getter methods return something different
+# from the simple string value:
+#
+# - #[]: Returns the string field value for the given key.
+# - #content_length: Returns the integer value of field <tt>'Content-Length'</tt>.
+# - #content_range: Returns the Range value of field <tt>'Content-Range'</tt>.
+# - #content_type: Returns the string value of field <tt>'Content-Type'</tt>.
+# - #fetch: Returns the string field value for the given key.
+# - #get_fields: Returns the array field value for the given +key+.
+# - #main_type: Returns first part of the string value of field <tt>'Content-Type'</tt>.
+# - #sub_type: Returns second part of the string value of field <tt>'Content-Type'</tt>.
+# - #range: Returns an array of Range objects of field <tt>'Range'</tt>, or +nil+.
+# - #range_length: Returns the integer length of the range given in field <tt>'Content-Range'</tt>.
+# - #type_params: Returns the string parameters for <tt>'Content-Type'</tt>.
+#
+# === Queries
+#
+# - #chunked?: Returns whether field <tt>'Transfer-Encoding'</tt> is set to <tt>'chunked'</tt>.
+# - #connection_close?: Returns whether field <tt>'Connection'</tt> is set to <tt>'close'</tt>.
+# - #connection_keep_alive?: Returns whether field <tt>'Connection'</tt> is set to <tt>'keep-alive'</tt>.
+# - #key?: Returns whether a given key exists.
+#
+# === Iterators
+#
+# - #each_capitalized: Passes each field capitalized-name/value pair to the block.
+# - #each_capitalized_name: Passes each capitalized field name to the block.
+# - #each_header: Passes each field name/value pair to the block.
+# - #each_name: Passes each field name to the block.
+# - #each_value: Passes each string field value to the block.
+#
+module Gem::Net::HTTPHeader
+ MAX_KEY_LENGTH = 1024
+ MAX_FIELD_LENGTH = 65536
+
+ def initialize_http_header(initheader) #:nodoc:
+ @header = {}
+ return unless initheader
+ initheader.each do |key, value|
+ warn "net/http: duplicated HTTP header: #{key}", uplevel: 3 if key?(key) and $VERBOSE
+ if value.nil?
+ warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE
+ else
+ value = value.strip # raise error for invalid byte sequences
+ if key.to_s.bytesize > MAX_KEY_LENGTH
+ raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..."
+ end
+ if value.to_s.bytesize > MAX_FIELD_LENGTH
+ raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}"
+ end
+ if value.count("\r\n") > 0
+ raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF"
+ end
+ @header[key.downcase.to_s] = [value]
+ end
+ end
+ end
+
+ def size #:nodoc: obsolete
+ @header.size
+ end
+
+ alias length size #:nodoc: obsolete
+
+ # Returns the string field value for the case-insensitive field +key+,
+ # or +nil+ if there is no such key;
+ # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['Connection'] # => "keep-alive"
+ # res['Nosuch'] # => nil
+ #
+ # Note that some field values may be retrieved via convenience methods;
+ # see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters].
+ def [](key)
+ a = @header[key.downcase.to_s] or return nil
+ a.join(', ')
+ end
+
+ # Sets the value for the case-insensitive +key+ to +val+,
+ # overwriting the previous value if the field exists;
+ # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req['Accept'] # => "*/*"
+ # req['Accept'] = 'text/html'
+ # req['Accept'] # => "text/html"
+ #
+ # Note that some field values may be set via convenience methods;
+ # see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters].
+ def []=(key, val)
+ unless val
+ @header.delete key.downcase.to_s
+ return val
+ end
+ set_field(key, val)
+ end
+
+ # Adds value +val+ to the value array for field +key+ if the field exists;
+ # creates the field with the given +key+ and +val+ if it does not exist.
+ # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req.add_field('Foo', 'bar')
+ # req['Foo'] # => "bar"
+ # req.add_field('Foo', 'baz')
+ # req['Foo'] # => "bar, baz"
+ # req.add_field('Foo', %w[baz bam])
+ # req['Foo'] # => "bar, baz, baz, bam"
+ # req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"]
+ #
+ def add_field(key, val)
+ stringified_downcased_key = key.downcase.to_s
+ if @header.key?(stringified_downcased_key)
+ append_field_value(@header[stringified_downcased_key], val)
+ else
+ set_field(key, val)
+ end
+ end
+
+ private def set_field(key, val)
+ case val
+ when Enumerable
+ ary = []
+ append_field_value(ary, val)
+ @header[key.downcase.to_s] = ary
+ else
+ val = val.to_s # for compatibility use to_s instead of to_str
+ if val.b.count("\r\n") > 0
+ raise ArgumentError, 'header field value cannot include CR/LF'
+ end
+ @header[key.downcase.to_s] = [val]
+ end
+ end
+
+ private def append_field_value(ary, val)
+ case val
+ when Enumerable
+ val.each{|x| append_field_value(ary, x)}
+ else
+ val = val.to_s
+ if /[\r\n]/n.match?(val.b)
+ raise ArgumentError, 'header field value cannot include CR/LF'
+ end
+ ary.push val
+ end
+ end
+
+ # Returns the array field value for the given +key+,
+ # or +nil+ if there is no such field;
+ # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res.get_fields('Connection') # => ["keep-alive"]
+ # res.get_fields('Nosuch') # => nil
+ #
+ def get_fields(key)
+ stringified_downcased_key = key.downcase.to_s
+ return nil unless @header[stringified_downcased_key]
+ @header[stringified_downcased_key].dup
+ end
+
+ # call-seq:
+ # fetch(key, default_val = nil) {|key| ... } -> object
+ # fetch(key, default_val = nil) -> value or default_val
+ #
+ # With a block, returns the string value for +key+ if it exists;
+ # otherwise returns the value of the block;
+ # ignores the +default_val+;
+ # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ #
+ # # Field exists; block not called.
+ # res.fetch('Connection') do |value|
+ # fail 'Cannot happen'
+ # end # => "keep-alive"
+ #
+ # # Field does not exist; block called.
+ # res.fetch('Nosuch') do |value|
+ # value.downcase
+ # end # => "nosuch"
+ #
+ # With no block, returns the string value for +key+ if it exists;
+ # otherwise, returns +default_val+ if it was given;
+ # otherwise raises an exception:
+ #
+ # res.fetch('Connection', 'Foo') # => "keep-alive"
+ # res.fetch('Nosuch', 'Foo') # => "Foo"
+ # res.fetch('Nosuch') # Raises KeyError.
+ #
+ def fetch(key, *args, &block) #:yield: +key+
+ a = @header.fetch(key.downcase.to_s, *args, &block)
+ a.kind_of?(Array) ? a.join(', ') : a
+ end
+
+ # Calls the block with each key/value pair:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res.each_header do |key, value|
+ # p [key, value] if key.start_with?('c')
+ # end
+ #
+ # Output:
+ #
+ # ["content-type", "application/json; charset=utf-8"]
+ # ["connection", "keep-alive"]
+ # ["cache-control", "max-age=43200"]
+ # ["cf-cache-status", "HIT"]
+ # ["cf-ray", "771d17e9bc542cf5-ORD"]
+ #
+ # Returns an enumerator if no block is given.
+ #
+ # Gem::Net::HTTPHeader#each is an alias for Gem::Net::HTTPHeader#each_header.
+ def each_header #:yield: +key+, +value+
+ block_given? or return enum_for(__method__) { @header.size }
+ @header.each do |k,va|
+ yield k, va.join(', ')
+ end
+ end
+
+ alias each each_header
+
+ # Calls the block with each field key:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res.each_key do |key|
+ # p key if key.start_with?('c')
+ # end
+ #
+ # Output:
+ #
+ # "content-type"
+ # "connection"
+ # "cache-control"
+ # "cf-cache-status"
+ # "cf-ray"
+ #
+ # Returns an enumerator if no block is given.
+ #
+ # Gem::Net::HTTPHeader#each_name is an alias for Gem::Net::HTTPHeader#each_key.
+ def each_name(&block) #:yield: +key+
+ block_given? or return enum_for(__method__) { @header.size }
+ @header.each_key(&block)
+ end
+
+ alias each_key each_name
+
+ # Calls the block with each capitalized field name:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res.each_capitalized_name do |key|
+ # p key if key.start_with?('C')
+ # end
+ #
+ # Output:
+ #
+ # "Content-Type"
+ # "Connection"
+ # "Cache-Control"
+ # "Cf-Cache-Status"
+ # "Cf-Ray"
+ #
+ # The capitalization is system-dependent;
+ # see {Case Mapping}[https://docs.ruby-lang.org/en/master/case_mapping_rdoc.html].
+ #
+ # Returns an enumerator if no block is given.
+ def each_capitalized_name #:yield: +key+
+ block_given? or return enum_for(__method__) { @header.size }
+ @header.each_key do |k|
+ yield capitalize(k)
+ end
+ end
+
+ # Calls the block with each string field value:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res.each_value do |value|
+ # p value if value.start_with?('c')
+ # end
+ #
+ # Output:
+ #
+ # "chunked"
+ # "cf-q-config;dur=6.0000002122251e-06"
+ # "cloudflare"
+ #
+ # Returns an enumerator if no block is given.
+ def each_value #:yield: +value+
+ block_given? or return enum_for(__method__) { @header.size }
+ @header.each_value do |va|
+ yield va.join(', ')
+ end
+ end
+
+ # Removes the header for the given case-insensitive +key+
+ # (see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]);
+ # returns the deleted value, or +nil+ if no such field exists:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req.delete('Accept') # => ["*/*"]
+ # req.delete('Nosuch') # => nil
+ #
+ def delete(key)
+ @header.delete(key.downcase.to_s)
+ end
+
+ # Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req.key?('Accept') # => true
+ # req.key?('Nosuch') # => false
+ #
+ def key?(key)
+ @header.key?(key.downcase.to_s)
+ end
+
+ # Returns a hash of the key/value pairs:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req.to_hash
+ # # =>
+ # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
+ # "accept"=>["*/*"],
+ # "user-agent"=>["Ruby"],
+ # "host"=>["jsonplaceholder.typicode.com"]}
+ #
+ def to_hash
+ @header.dup
+ end
+
+ # Like #each_header, but the keys are returned in capitalized form.
+ #
+ # Gem::Net::HTTPHeader#canonical_each is an alias for Gem::Net::HTTPHeader#each_capitalized.
+ def each_capitalized
+ block_given? or return enum_for(__method__) { @header.size }
+ @header.each do |k,v|
+ yield capitalize(k), v.join(', ')
+ end
+ end
+
+ alias canonical_each each_capitalized
+
+ def capitalize(name)
+ name.to_s.split(/-/).map {|s| s.capitalize }.join('-')
+ end
+ private :capitalize
+
+ # Returns an array of Range objects that represent
+ # the value of field <tt>'Range'</tt>,
+ # or +nil+ if there is no such field;
+ # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req['Range'] = 'bytes=0-99,200-299,400-499'
+ # req.range # => [0..99, 200..299, 400..499]
+ # req.delete('Range')
+ # req.range # # => nil
+ #
+ def range
+ return nil unless @header['range']
+
+ value = self['Range']
+ # byte-range-set = *( "," OWS ) ( byte-range-spec / suffix-byte-range-spec )
+ # *( OWS "," [ OWS ( byte-range-spec / suffix-byte-range-spec ) ] )
+ # corrected collected ABNF
+ # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#section-5.4.1
+ # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#appendix-C
+ # http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-19#section-3.2.5
+ unless /\Abytes=((?:,[ \t]*)*(?:\d+-\d*|-\d+)(?:[ \t]*,(?:[ \t]*\d+-\d*|-\d+)?)*)\z/ =~ value
+ raise Gem::Net::HTTPHeaderSyntaxError, "invalid syntax for byte-ranges-specifier: '#{value}'"
+ end
+
+ byte_range_set = $1
+ result = byte_range_set.split(/,/).map {|spec|
+ m = /(\d+)?\s*-\s*(\d+)?/i.match(spec) or
+ raise Gem::Net::HTTPHeaderSyntaxError, "invalid byte-range-spec: '#{spec}'"
+ d1 = m[1].to_i
+ d2 = m[2].to_i
+ if m[1] and m[2]
+ if d1 > d2
+ raise Gem::Net::HTTPHeaderSyntaxError, "last-byte-pos MUST greater than or equal to first-byte-pos but '#{spec}'"
+ end
+ d1..d2
+ elsif m[1]
+ d1..-1
+ elsif m[2]
+ -d2..-1
+ else
+ raise Gem::Net::HTTPHeaderSyntaxError, 'range is not specified'
+ end
+ }
+ # if result.empty?
+ # byte-range-set must include at least one byte-range-spec or suffix-byte-range-spec
+ # but above regexp already denies it.
+ if result.size == 1 && result[0].begin == 0 && result[0].end == -1
+ raise Gem::Net::HTTPHeaderSyntaxError, 'only one suffix-byte-range-spec with zero suffix-length'
+ end
+ result
+ end
+
+ # call-seq:
+ # set_range(length) -> length
+ # set_range(offset, length) -> range
+ # set_range(begin..length) -> range
+ #
+ # Sets the value for field <tt>'Range'</tt>;
+ # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
+ #
+ # With argument +length+:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req.set_range(100) # => 100
+ # req['Range'] # => "bytes=0-99"
+ #
+ # With arguments +offset+ and +length+:
+ #
+ # req.set_range(100, 100) # => 100...200
+ # req['Range'] # => "bytes=100-199"
+ #
+ # With argument +range+:
+ #
+ # req.set_range(100..199) # => 100..199
+ # req['Range'] # => "bytes=100-199"
+ #
+ # Gem::Net::HTTPHeader#range= is an alias for Gem::Net::HTTPHeader#set_range.
+ def set_range(r, e = nil)
+ unless r
+ @header.delete 'range'
+ return r
+ end
+ r = (r...r+e) if e
+ case r
+ when Numeric
+ n = r.to_i
+ rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}")
+ when Range
+ first = r.first
+ last = r.end
+ last -= 1 if r.exclude_end?
+ if last == -1
+ rangestr = (first > 0 ? "#{first}-" : "-#{-first}")
+ else
+ raise Gem::Net::HTTPHeaderSyntaxError, 'range.first is negative' if first < 0
+ raise Gem::Net::HTTPHeaderSyntaxError, 'range.last is negative' if last < 0
+ raise Gem::Net::HTTPHeaderSyntaxError, 'must be .first < .last' if first > last
+ rangestr = "#{first}-#{last}"
+ end
+ else
+ raise TypeError, 'Range/Integer is required'
+ end
+ @header['range'] = ["bytes=#{rangestr}"]
+ r
+ end
+
+ alias range= set_range
+
+ # Returns the value of field <tt>'Content-Length'</tt> as an integer,
+ # or +nil+ if there is no such field;
+ # see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/nosuch/1')
+ # res.content_length # => 2
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res.content_length # => nil
+ #
+ def content_length
+ return nil unless key?('Content-Length')
+ len = self['Content-Length'].slice(/\d+/) or
+ raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Length format'
+ len.to_i
+ end
+
+ # Sets the value of field <tt>'Content-Length'</tt> to the given numeric;
+ # see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]:
+ #
+ # _uri = uri.dup
+ # hostname = _uri.hostname # => "jsonplaceholder.typicode.com"
+ # _uri.path = '/posts' # => "/posts"
+ # req = Gem::Net::HTTP::Post.new(_uri) # => #<Gem::Net::HTTP::Post POST>
+ # req.body = '{"title": "foo","body": "bar","userId": 1}'
+ # req.content_length = req.body.size # => 42
+ # req.content_type = 'application/json'
+ # res = Gem::Net::HTTP.start(hostname) do |http|
+ # http.request(req)
+ # end # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
+ #
+ def content_length=(len)
+ unless len
+ @header.delete 'content-length'
+ return nil
+ end
+ @header['content-length'] = [len.to_i.to_s]
+ end
+
+ # Returns +true+ if field <tt>'Transfer-Encoding'</tt>
+ # exists and has value <tt>'chunked'</tt>,
+ # +false+ otherwise;
+ # see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['Transfer-Encoding'] # => "chunked"
+ # res.chunked? # => true
+ #
+ def chunked?
+ return false unless @header['transfer-encoding']
+ field = self['Transfer-Encoding']
+ (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false
+ end
+
+ # Returns a Range object representing the value of field
+ # <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
+ # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['Content-Range'] # => nil
+ # res['Content-Range'] = 'bytes 0-499/1000'
+ # res['Content-Range'] # => "bytes 0-499/1000"
+ # res.content_range # => 0..499
+ #
+ def content_range
+ return nil unless @header['content-range']
+ m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or
+ raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Range format'
+ return unless m[1] == 'bytes'
+ m[2].to_i .. m[3].to_i
+ end
+
+ # Returns the integer representing length of the value of field
+ # <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
+ # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['Content-Range'] # => nil
+ # res['Content-Range'] = 'bytes 0-499/1000'
+ # res.range_length # => 500
+ #
+ def range_length
+ r = content_range() or return nil
+ r.end - r.begin + 1
+ end
+
+ # Returns the {media type}[https://en.wikipedia.org/wiki/Media_type]
+ # from the value of field <tt>'Content-Type'</tt>,
+ # or +nil+ if no such field exists;
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['content-type'] # => "application/json; charset=utf-8"
+ # res.content_type # => "application/json"
+ #
+ def content_type
+ main = main_type()
+ return nil unless main
+
+ sub = sub_type()
+ if sub
+ "#{main}/#{sub}"
+ else
+ main
+ end
+ end
+
+ # Returns the leading ('type') part of the
+ # {media type}[https://en.wikipedia.org/wiki/Media_type]
+ # from the value of field <tt>'Content-Type'</tt>,
+ # or +nil+ if no such field exists;
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['content-type'] # => "application/json; charset=utf-8"
+ # res.main_type # => "application"
+ #
+ def main_type
+ return nil unless @header['content-type']
+ self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
+ end
+
+ # Returns the trailing ('subtype') part of the
+ # {media type}[https://en.wikipedia.org/wiki/Media_type]
+ # from the value of field <tt>'Content-Type'</tt>,
+ # or +nil+ if no such field exists;
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['content-type'] # => "application/json; charset=utf-8"
+ # res.sub_type # => "json"
+ #
+ def sub_type
+ return nil unless @header['content-type']
+ _, sub = *self['Content-Type'].split(';').first.to_s.split('/')
+ return nil unless sub
+ sub.strip
+ end
+
+ # Returns the trailing ('parameters') part of the value of field <tt>'Content-Type'</tt>,
+ # or +nil+ if no such field exists;
+ # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
+ #
+ # res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
+ # res['content-type'] # => "application/json; charset=utf-8"
+ # res.type_params # => {"charset"=>"utf-8"}
+ #
+ def type_params
+ result = {}
+ list = self['Content-Type'].to_s.split(';')
+ list.shift
+ list.each do |param|
+ k, v = *param.split('=', 2)
+ result[k.strip] = v.strip
+ end
+ result
+ end
+
+ # Sets the value of field <tt>'Content-Type'</tt>;
+ # returns the new value;
+ # see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]:
+ #
+ # req = Gem::Net::HTTP::Get.new(uri)
+ # req.set_content_type('application/json') # => ["application/json"]
+ #
+ # Gem::Net::HTTPHeader#content_type= is an alias for Gem::Net::HTTPHeader#set_content_type.
+ def set_content_type(type, params = {})
+ @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
+ end
+
+ alias content_type= set_content_type
+
+ # Sets the request body to a URL-encoded string derived from argument +params+,
+ # and sets request header field <tt>'Content-Type'</tt>
+ # to <tt>'application/x-www-form-urlencoded'</tt>.
+ #
+ # The resulting request is suitable for HTTP request +POST+ or +PUT+.
+ #
+ # Argument +params+ must be suitable for use as argument +enum+ to
+ # {Gem::URI.encode_www_form}[https://docs.ruby-lang.org/en/master/Gem::URI.html#method-c-encode_www_form].
+ #
+ # With only argument +params+ given,
+ # sets the body to a URL-encoded string with the default separator <tt>'&'</tt>:
+ #
+ # req = Gem::Net::HTTP::Post.new('example.com')
+ #
+ # req.set_form_data(q: 'ruby', lang: 'en')
+ # req.body # => "q=ruby&lang=en"
+ # req['Content-Type'] # => "application/x-www-form-urlencoded"
+ #
+ # req.set_form_data([['q', 'ruby'], ['lang', 'en']])
+ # req.body # => "q=ruby&lang=en"
+ #
+ # req.set_form_data(q: ['ruby', 'perl'], lang: 'en')
+ # req.body # => "q=ruby&q=perl&lang=en"
+ #
+ # req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']])
+ # req.body # => "q=ruby&q=perl&lang=en"
+ #
+ # With string argument +sep+ also given,
+ # uses that string as the separator:
+ #
+ # req.set_form_data({q: 'ruby', lang: 'en'}, '|')
+ # req.body # => "q=ruby|lang=en"
+ #
+ # Gem::Net::HTTPHeader#form_data= is an alias for Gem::Net::HTTPHeader#set_form_data.
+ def set_form_data(params, sep = '&')
+ query = Gem::URI.encode_www_form(params)
+ query.gsub!(/&/, sep) if sep != '&'
+ self.body = query
+ self.content_type = 'application/x-www-form-urlencoded'
+ end
+
+ alias form_data= set_form_data
+
+ # Stores form data to be used in a +POST+ or +PUT+ request.
+ #
+ # The form data given in +params+ consists of zero or more fields;
+ # each field is:
+ #
+ # - A scalar value.
+ # - A name/value pair.
+ # - An IO stream opened for reading.
+ #
+ # Argument +params+ should be an
+ # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes]
+ # (method <tt>params.map</tt> will be called),
+ # and is often an array or hash.
+ #
+ # First, we set up a request:
+ #
+ # _uri = uri.dup
+ # _uri.path ='/posts'
+ # req = Gem::Net::HTTP::Post.new(_uri)
+ #
+ # <b>Argument +params+ As an Array</b>
+ #
+ # When +params+ is an array,
+ # each of its elements is a subarray that defines a field;
+ # the subarray may contain:
+ #
+ # - One string:
+ #
+ # req.set_form([['foo'], ['bar'], ['baz']])
+ #
+ # - Two strings:
+ #
+ # req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]])
+ #
+ # - When argument +enctype+ (see below) is given as
+ # <tt>'multipart/form-data'</tt>:
+ #
+ # - A string name and an IO stream opened for reading:
+ #
+ # require 'stringio'
+ # req.set_form([['file', StringIO.new('Ruby is cool.')]])
+ #
+ # - A string name, an IO stream opened for reading,
+ # and an options hash, which may contain these entries:
+ #
+ # - +:filename+: The name of the file to use.
+ # - +:content_type+: The content type of the uploaded file.
+ #
+ # Example:
+ #
+ # req.set_form([['file', file, {filename: "other-filename.foo"}]]
+ #
+ # The various forms may be mixed:
+ #
+ # req.set_form(['foo', %w[bar 1], ['file', file]])
+ #
+ # <b>Argument +params+ As a Hash</b>
+ #
+ # When +params+ is a hash,
+ # each of its entries is a name/value pair that defines a field:
+ #
+ # - The name is a string.
+ # - The value may be:
+ #
+ # - +nil+.
+ # - Another string.
+ # - An IO stream opened for reading
+ # (only when argument +enctype+ -- see below -- is given as
+ # <tt>'multipart/form-data'</tt>).
+ #
+ # Examples:
+ #
+ # # Nil-valued fields.
+ # req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil})
+ #
+ # # String-valued fields.
+ # req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2})
+ #
+ # # IO-valued field.
+ # require 'stringio'
+ # req.set_form({'file' => StringIO.new('Ruby is cool.')})
+ #
+ # # Mixture of fields.
+ # req.set_form({'foo' => nil, 'bar' => 1, 'file' => file})
+ #
+ # Optional argument +enctype+ specifies the value to be given
+ # to field <tt>'Content-Type'</tt>, and must be one of:
+ #
+ # - <tt>'application/x-www-form-urlencoded'</tt> (the default).
+ # - <tt>'multipart/form-data'</tt>;
+ # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578].
+ #
+ # Optional argument +formopt+ is a hash of options
+ # (applicable only when argument +enctype+
+ # is <tt>'multipart/form-data'</tt>)
+ # that may include the following entries:
+ #
+ # - +:boundary+: The value is the boundary string for the multipart message.
+ # If not given, the boundary is a random string.
+ # See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1].
+ # - +:charset+: Value is the character set for the form submission.
+ # Field names and values of non-file fields should be encoded with this charset.
+ #
+ def set_form(params, enctype='application/x-www-form-urlencoded', formopt={})
+ @body_data = params
+ @body = nil
+ @body_stream = nil
+ @form_option = formopt
+ case enctype
+ when /\Aapplication\/x-www-form-urlencoded\z/i,
+ /\Amultipart\/form-data\z/i
+ self.content_type = enctype
+ else
+ raise ArgumentError, "invalid enctype: #{enctype}"
+ end
+ end
+
+ # Sets header <tt>'Authorization'</tt> using the given
+ # +account+ and +password+ strings:
+ #
+ # req.basic_auth('my_account', 'my_password')
+ # req['Authorization']
+ # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
+ #
+ def basic_auth(account, password)
+ @header['authorization'] = [basic_encode(account, password)]
+ end
+
+ # Sets header <tt>'Proxy-Authorization'</tt> using the given
+ # +account+ and +password+ strings:
+ #
+ # req.proxy_basic_auth('my_account', 'my_password')
+ # req['Proxy-Authorization']
+ # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
+ #
+ def proxy_basic_auth(account, password)
+ @header['proxy-authorization'] = [basic_encode(account, password)]
+ end
+
+ def basic_encode(account, password)
+ 'Basic ' + ["#{account}:#{password}"].pack('m0')
+ end
+ private :basic_encode
+
+# Returns whether the HTTP session is to be closed.
+ def connection_close?
+ token = /(?:\A|,)\s*close\s*(?:\z|,)/i
+ @header['connection']&.grep(token) {return true}
+ @header['proxy-connection']&.grep(token) {return true}
+ false
+ end
+
+# Returns whether the HTTP session is to be kept alive.
+ def connection_keep_alive?
+ token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i
+ @header['connection']&.grep(token) {return true}
+ @header['proxy-connection']&.grep(token) {return true}
+ false
+ end
+
+end
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/proxy_delta.rb b/lib/rubygems/vendor/net-http/lib/net/http/proxy_delta.rb
new file mode 100644
index 0000000000..137295a883
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/proxy_delta.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+module Gem::Net::HTTP::ProxyDelta #:nodoc: internal use only
+ private
+
+ def conn_address
+ proxy_address()
+ end
+
+ def conn_port
+ proxy_port()
+ end
+
+ def edit_path(path)
+ use_ssl? ? path : "http://#{addr_port()}#{path}"
+ end
+end
+
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/request.rb b/lib/rubygems/vendor/net-http/lib/net/http/request.rb
new file mode 100644
index 0000000000..495ec9be54
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/request.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+# This class is the base class for \Gem::Net::HTTP request classes.
+# The class should not be used directly;
+# instead you should use its subclasses, listed below.
+#
+# == Creating a Request
+#
+# An request object may be created with either a Gem::URI or a string hostname:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('https://jsonplaceholder.typicode.com/')
+# req = Gem::Net::HTTP::Get.new(uri) # => #<Gem::Net::HTTP::Get GET>
+# req = Gem::Net::HTTP::Get.new(uri.hostname) # => #<Gem::Net::HTTP::Get GET>
+#
+# And with any of the subclasses:
+#
+# req = Gem::Net::HTTP::Head.new(uri) # => #<Gem::Net::HTTP::Head HEAD>
+# req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
+# req = Gem::Net::HTTP::Put.new(uri) # => #<Gem::Net::HTTP::Put PUT>
+# # ...
+#
+# The new instance is suitable for use as the argument to Gem::Net::HTTP#request.
+#
+# == Request Headers
+#
+# A new request object has these header fields by default:
+#
+# req.to_hash
+# # =>
+# {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
+# "accept"=>["*/*"],
+# "user-agent"=>["Ruby"],
+# "host"=>["jsonplaceholder.typicode.com"]}
+#
+# See:
+#
+# - {Request header Accept-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Accept-Encoding]
+# and {Compression and Decompression}[rdoc-ref:Gem::Net::HTTP@Compression+and+Decompression].
+# - {Request header Accept}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#accept-request-header].
+# - {Request header User-Agent}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#user-agent-request-header].
+# - {Request header Host}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#host-request-header].
+#
+# You can add headers or override default headers:
+#
+# # res = Gem::Net::HTTP::Get.new(uri, {'foo' => '0', 'bar' => '1'})
+#
+# This class (and therefore its subclasses) also includes (indirectly)
+# module Gem::Net::HTTPHeader, which gives access to its
+# {methods for setting headers}[rdoc-ref:Gem::Net::HTTPHeader@Setters].
+#
+# == Request Subclasses
+#
+# Subclasses for HTTP requests:
+#
+# - Gem::Net::HTTP::Get
+# - Gem::Net::HTTP::Head
+# - Gem::Net::HTTP::Post
+# - Gem::Net::HTTP::Put
+# - Gem::Net::HTTP::Delete
+# - Gem::Net::HTTP::Options
+# - Gem::Net::HTTP::Trace
+# - Gem::Net::HTTP::Patch
+#
+# Subclasses for WebDAV requests:
+#
+# - Gem::Net::HTTP::Propfind
+# - Gem::Net::HTTP::Proppatch
+# - Gem::Net::HTTP::Mkcol
+# - Gem::Net::HTTP::Copy
+# - Gem::Net::HTTP::Move
+# - Gem::Net::HTTP::Lock
+# - Gem::Net::HTTP::Unlock
+#
+class Gem::Net::HTTPRequest < Gem::Net::HTTPGenericRequest
+ # Creates an HTTP request object for +path+.
+ #
+ # +initheader+ are the default headers to use. Gem::Net::HTTP adds
+ # Accept-Encoding to enable compression of the response body unless
+ # Accept-Encoding or Range are supplied in +initheader+.
+
+ def initialize(path, initheader = nil)
+ super self.class::METHOD,
+ self.class::REQUEST_HAS_BODY,
+ self.class::RESPONSE_HAS_BODY,
+ path, initheader
+ end
+end
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb
new file mode 100644
index 0000000000..1a57ddc7c2
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb
@@ -0,0 +1,425 @@
+# frozen_string_literal: true
+
+# HTTP/1.1 methods --- RFC2616
+
+# \Class for representing
+# {HTTP method GET}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#GET_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Get.new(uri) # => #<Gem::Net::HTTP::Get GET>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: optional.
+# - Response body: yes.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
+#
+# Related:
+#
+# - Gem::Net::HTTP.get: sends +GET+ request, returns response body.
+# - Gem::Net::HTTP#get: sends +GET+ request, returns response object.
+#
+class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest
+ METHOD = 'GET'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {HTTP method HEAD}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#HEAD_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Head.new(uri) # => #<Gem::Net::HTTP::Head HEAD>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: optional.
+# - Response body: no.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
+#
+# Related:
+#
+# - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object.
+#
+class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest
+ METHOD = 'HEAD'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = false
+end
+
+# \Class for representing
+# {HTTP method POST}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#POST_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# uri.path = '/posts'
+# req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
+# req.body = '{"title": "foo","body": "bar","userId": 1}'
+# req.content_type = 'application/json'
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: yes.
+# - Response body: yes.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
+#
+# Related:
+#
+# - Gem::Net::HTTP.post: sends +POST+ request, returns response object.
+# - Gem::Net::HTTP#post: sends +POST+ request, returns response object.
+#
+class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest
+ METHOD = 'POST'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {HTTP method PUT}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PUT_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# uri.path = '/posts'
+# req = Gem::Net::HTTP::Put.new(uri) # => #<Gem::Net::HTTP::Put PUT>
+# req.body = '{"title": "foo","body": "bar","userId": 1}'
+# req.content_type = 'application/json'
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: yes.
+# - Response body: yes.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
+#
+class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest
+ METHOD = 'PUT'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {HTTP method DELETE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#DELETE_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# uri.path = '/posts/1'
+# req = Gem::Net::HTTP::Delete.new(uri) # => #<Gem::Net::HTTP::Delete DELETE>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: optional.
+# - Response body: yes.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
+#
+# Related:
+#
+# - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object.
+#
+class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest
+ METHOD = 'DELETE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {HTTP method OPTIONS}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#OPTIONS_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Options.new(uri) # => #<Gem::Net::HTTP::Options OPTIONS>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: optional.
+# - Response body: yes.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
+#
+# Related:
+#
+# - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object.
+#
+class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest
+ METHOD = 'OPTIONS'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {HTTP method TRACE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#TRACE_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Trace.new(uri) # => #<Gem::Net::HTTP::Trace TRACE>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: no.
+# - Response body: yes.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
+#
+# Related:
+#
+# - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object.
+#
+class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest
+ METHOD = 'TRACE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {HTTP method PATCH}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PATCH_method]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# uri.path = '/posts'
+# req = Gem::Net::HTTP::Patch.new(uri) # => #<Gem::Net::HTTP::Patch PATCH>
+# req.body = '{"title": "foo","body": "bar","userId": 1}'
+# req.content_type = 'application/json'
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Properties:
+#
+# - Request body: yes.
+# - Response body: yes.
+# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
+# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no.
+# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
+#
+# Related:
+#
+# - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object.
+#
+class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest
+ METHOD = 'PATCH'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
+#
+# WebDAV methods --- RFC2518
+#
+
+# \Class for representing
+# {WebDAV method PROPFIND}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Propfind.new(uri) # => #<Gem::Net::HTTP::Propfind PROPFIND>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Related:
+#
+# - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object.
+#
+class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest
+ METHOD = 'PROPFIND'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {WebDAV method PROPPATCH}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Proppatch.new(uri) # => #<Gem::Net::HTTP::Proppatch PROPPATCH>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Related:
+#
+# - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object.
+#
+class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest
+ METHOD = 'PROPPATCH'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {WebDAV method MKCOL}[http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Mkcol.new(uri) # => #<Gem::Net::HTTP::Mkcol MKCOL>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Related:
+#
+# - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object.
+#
+class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest
+ METHOD = 'MKCOL'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {WebDAV method COPY}[http://www.webdav.org/specs/rfc4918.html#METHOD_COPY]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Copy.new(uri) # => #<Gem::Net::HTTP::Copy COPY>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Related:
+#
+# - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object.
+#
+class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest
+ METHOD = 'COPY'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {WebDAV method MOVE}[http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Move.new(uri) # => #<Gem::Net::HTTP::Move MOVE>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Related:
+#
+# - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object.
+#
+class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest
+ METHOD = 'MOVE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {WebDAV method LOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Lock.new(uri) # => #<Gem::Net::HTTP::Lock LOCK>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Related:
+#
+# - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object.
+#
+class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest
+ METHOD = 'LOCK'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
+# \Class for representing
+# {WebDAV method UNLOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK]:
+#
+# require 'rubygems/vendor/net-http/lib/net/http'
+# uri = Gem::URI('http://example.com')
+# hostname = uri.hostname # => "example.com"
+# req = Gem::Net::HTTP::Unlock.new(uri) # => #<Gem::Net::HTTP::Unlock UNLOCK>
+# res = Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end
+#
+# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
+#
+# Related:
+#
+# - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object.
+#
+class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest
+ METHOD = 'UNLOCK'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+end
+
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/response.rb b/lib/rubygems/vendor/net-http/lib/net/http/response.rb
new file mode 100644
index 0000000000..cbbd191d87
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/response.rb
@@ -0,0 +1,738 @@
+# frozen_string_literal: true
+
+# This class is the base class for \Gem::Net::HTTP response classes.
+#
+# == About the Examples
+#
+# :include: doc/net-http/examples.rdoc
+#
+# == Returned Responses
+#
+# \Method Gem::Net::HTTP.get_response returns
+# an instance of one of the subclasses of \Gem::Net::HTTPResponse:
+#
+# Gem::Net::HTTP.get_response(uri)
+# # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+# Gem::Net::HTTP.get_response(hostname, '/nosuch')
+# # => #<Gem::Net::HTTPNotFound 404 Not Found readbody=true>
+#
+# As does method Gem::Net::HTTP#request:
+#
+# req = Gem::Net::HTTP::Get.new(uri)
+# Gem::Net::HTTP.start(hostname) do |http|
+# http.request(req)
+# end # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+#
+# \Class \Gem::Net::HTTPResponse includes module Gem::Net::HTTPHeader,
+# which provides access to response header values via (among others):
+#
+# - \Hash-like method <tt>[]</tt>.
+# - Specific reader methods, such as +content_type+.
+#
+# Examples:
+#
+# res = Gem::Net::HTTP.get_response(uri) # => #<Gem::Net::HTTPOK 200 OK readbody=true>
+# res['Content-Type'] # => "text/html; charset=UTF-8"
+# res.content_type # => "text/html"
+#
+# == Response Subclasses
+#
+# \Class \Gem::Net::HTTPResponse has a subclass for each
+# {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes].
+# You can look up the response class for a given code:
+#
+# Gem::Net::HTTPResponse::CODE_TO_OBJ['200'] # => Gem::Net::HTTPOK
+# Gem::Net::HTTPResponse::CODE_TO_OBJ['400'] # => Gem::Net::HTTPBadRequest
+# Gem::Net::HTTPResponse::CODE_TO_OBJ['404'] # => Gem::Net::HTTPNotFound
+#
+# And you can retrieve the status code for a response object:
+#
+# Gem::Net::HTTP.get_response(uri).code # => "200"
+# Gem::Net::HTTP.get_response(hostname, '/nosuch').code # => "404"
+#
+# The response subclasses (indentation shows class hierarchy):
+#
+# - Gem::Net::HTTPUnknownResponse (for unhandled \HTTP extensions).
+#
+# - Gem::Net::HTTPInformation:
+#
+# - Gem::Net::HTTPContinue (100)
+# - Gem::Net::HTTPSwitchProtocol (101)
+# - Gem::Net::HTTPProcessing (102)
+# - Gem::Net::HTTPEarlyHints (103)
+#
+# - Gem::Net::HTTPSuccess:
+#
+# - Gem::Net::HTTPOK (200)
+# - Gem::Net::HTTPCreated (201)
+# - Gem::Net::HTTPAccepted (202)
+# - Gem::Net::HTTPNonAuthoritativeInformation (203)
+# - Gem::Net::HTTPNoContent (204)
+# - Gem::Net::HTTPResetContent (205)
+# - Gem::Net::HTTPPartialContent (206)
+# - Gem::Net::HTTPMultiStatus (207)
+# - Gem::Net::HTTPAlreadyReported (208)
+# - Gem::Net::HTTPIMUsed (226)
+#
+# - Gem::Net::HTTPRedirection:
+#
+# - Gem::Net::HTTPMultipleChoices (300)
+# - Gem::Net::HTTPMovedPermanently (301)
+# - Gem::Net::HTTPFound (302)
+# - Gem::Net::HTTPSeeOther (303)
+# - Gem::Net::HTTPNotModified (304)
+# - Gem::Net::HTTPUseProxy (305)
+# - Gem::Net::HTTPTemporaryRedirect (307)
+# - Gem::Net::HTTPPermanentRedirect (308)
+#
+# - Gem::Net::HTTPClientError:
+#
+# - Gem::Net::HTTPBadRequest (400)
+# - Gem::Net::HTTPUnauthorized (401)
+# - Gem::Net::HTTPPaymentRequired (402)
+# - Gem::Net::HTTPForbidden (403)
+# - Gem::Net::HTTPNotFound (404)
+# - Gem::Net::HTTPMethodNotAllowed (405)
+# - Gem::Net::HTTPNotAcceptable (406)
+# - Gem::Net::HTTPProxyAuthenticationRequired (407)
+# - Gem::Net::HTTPRequestTimeOut (408)
+# - Gem::Net::HTTPConflict (409)
+# - Gem::Net::HTTPGone (410)
+# - Gem::Net::HTTPLengthRequired (411)
+# - Gem::Net::HTTPPreconditionFailed (412)
+# - Gem::Net::HTTPRequestEntityTooLarge (413)
+# - Gem::Net::HTTPRequestURITooLong (414)
+# - Gem::Net::HTTPUnsupportedMediaType (415)
+# - Gem::Net::HTTPRequestedRangeNotSatisfiable (416)
+# - Gem::Net::HTTPExpectationFailed (417)
+# - Gem::Net::HTTPMisdirectedRequest (421)
+# - Gem::Net::HTTPUnprocessableEntity (422)
+# - Gem::Net::HTTPLocked (423)
+# - Gem::Net::HTTPFailedDependency (424)
+# - Gem::Net::HTTPUpgradeRequired (426)
+# - Gem::Net::HTTPPreconditionRequired (428)
+# - Gem::Net::HTTPTooManyRequests (429)
+# - Gem::Net::HTTPRequestHeaderFieldsTooLarge (431)
+# - Gem::Net::HTTPUnavailableForLegalReasons (451)
+#
+# - Gem::Net::HTTPServerError:
+#
+# - Gem::Net::HTTPInternalServerError (500)
+# - Gem::Net::HTTPNotImplemented (501)
+# - Gem::Net::HTTPBadGateway (502)
+# - Gem::Net::HTTPServiceUnavailable (503)
+# - Gem::Net::HTTPGatewayTimeOut (504)
+# - Gem::Net::HTTPVersionNotSupported (505)
+# - Gem::Net::HTTPVariantAlsoNegotiates (506)
+# - Gem::Net::HTTPInsufficientStorage (507)
+# - Gem::Net::HTTPLoopDetected (508)
+# - Gem::Net::HTTPNotExtended (510)
+# - Gem::Net::HTTPNetworkAuthenticationRequired (511)
+#
+# There is also the Gem::Net::HTTPBadResponse exception which is raised when
+# there is a protocol error.
+#
+class Gem::Net::HTTPResponse
+ class << self
+ # true if the response has a body.
+ def body_permitted?
+ self::HAS_BODY
+ end
+
+ def exception_type # :nodoc: internal use only
+ self::EXCEPTION_TYPE
+ end
+
+ def read_new(sock) #:nodoc: internal use only
+ httpv, code, msg = read_status_line(sock)
+ res = response_class(code).new(httpv, code, msg)
+ each_response_header(sock) do |k,v|
+ res.add_field k, v
+ end
+ res
+ end
+
+ private
+
+ def read_status_line(sock)
+ str = sock.readline
+ m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\z/in.match(str) or
+ raise Gem::Net::HTTPBadResponse, "wrong status line: #{str.dump}"
+ m.captures
+ end
+
+ def response_class(code)
+ CODE_TO_OBJ[code] or
+ CODE_CLASS_TO_OBJ[code[0,1]] or
+ Gem::Net::HTTPUnknownResponse
+ end
+
+ def each_response_header(sock)
+ key = value = nil
+ while true
+ line = sock.readuntil("\n", true).sub(/\s+\z/, '')
+ break if line.empty?
+ if line[0] == ?\s or line[0] == ?\t and value
+ value << ' ' unless value.empty?
+ value << line.strip
+ else
+ yield key, value if key
+ key, value = line.strip.split(/\s*:\s*/, 2)
+ raise Gem::Net::HTTPBadResponse, 'wrong header line format' if value.nil?
+ end
+ end
+ yield key, value if key
+ end
+ end
+
+ # next is to fix bug in RDoc, where the private inside class << self
+ # spills out.
+ public
+
+ include Gem::Net::HTTPHeader
+
+ def initialize(httpv, code, msg) #:nodoc: internal use only
+ @http_version = httpv
+ @code = code
+ @message = msg
+ initialize_http_header nil
+ @body = nil
+ @read = false
+ @uri = nil
+ @decode_content = false
+ @body_encoding = false
+ @ignore_eof = true
+ end
+
+ # The HTTP version supported by the server.
+ attr_reader :http_version
+
+ # The HTTP result code string. For example, '302'. You can also
+ # determine the response type by examining which response subclass
+ # the response object is an instance of.
+ attr_reader :code
+
+ # The HTTP result message sent by the server. For example, 'Not Found'.
+ attr_reader :message
+ alias msg message # :nodoc: obsolete
+
+ # The Gem::URI used to fetch this response. The response Gem::URI is only available
+ # if a Gem::URI was used to create the request.
+ attr_reader :uri
+
+ # Set to true automatically when the request did not contain an
+ # Accept-Encoding header from the user.
+ attr_accessor :decode_content
+
+ # Returns the value set by body_encoding=, or +false+ if none;
+ # see #body_encoding=.
+ attr_reader :body_encoding
+
+ # Sets the encoding that should be used when reading the body:
+ #
+ # - If the given value is an Encoding object, that encoding will be used.
+ # - Otherwise if the value is a string, the value of
+ # {Encoding#find(value)}[https://docs.ruby-lang.org/en/master/Encoding.html#method-c-find]
+ # will be used.
+ # - Otherwise an encoding will be deduced from the body itself.
+ #
+ # Examples:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # req = Gem::Net::HTTP::Get.new('/')
+ #
+ # http.request(req) do |res|
+ # p res.body.encoding # => #<Encoding:ASCII-8BIT>
+ # end
+ #
+ # http.request(req) do |res|
+ # res.body_encoding = "UTF-8"
+ # p res.body.encoding # => #<Encoding:UTF-8>
+ # end
+ #
+ def body_encoding=(value)
+ value = Encoding.find(value) if value.is_a?(String)
+ @body_encoding = value
+ end
+
+ # Whether to ignore EOF when reading bodies with a specified Content-Length
+ # header.
+ attr_accessor :ignore_eof
+
+ def inspect
+ "#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
+ end
+
+ #
+ # response <-> exception relationship
+ #
+
+ def code_type #:nodoc:
+ self.class
+ end
+
+ def error! #:nodoc:
+ message = @code
+ message = "#{message} #{@message.dump}" if @message
+ raise error_type().new(message, self)
+ end
+
+ def error_type #:nodoc:
+ self.class::EXCEPTION_TYPE
+ end
+
+ # Raises an HTTP error if the response is not 2xx (success).
+ def value
+ error! unless self.kind_of?(Gem::Net::HTTPSuccess)
+ end
+
+ def uri= uri # :nodoc:
+ @uri = uri.dup if uri
+ end
+
+ #
+ # header (for backward compatibility only; DO NOT USE)
+ #
+
+ def response #:nodoc:
+ warn "Gem::Net::HTTPResponse#response is obsolete", uplevel: 1 if $VERBOSE
+ self
+ end
+
+ def header #:nodoc:
+ warn "Gem::Net::HTTPResponse#header is obsolete", uplevel: 1 if $VERBOSE
+ self
+ end
+
+ def read_header #:nodoc:
+ warn "Gem::Net::HTTPResponse#read_header is obsolete", uplevel: 1 if $VERBOSE
+ self
+ end
+
+ #
+ # body
+ #
+
+ def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only
+ @socket = sock
+ @body_exist = reqmethodallowbody && self.class.body_permitted?
+ begin
+ yield
+ self.body # ensure to read body
+ ensure
+ @socket = nil
+ end
+ end
+
+ # Gets the entity body returned by the remote HTTP server.
+ #
+ # If a block is given, the body is passed to the block, and
+ # the body is provided in fragments, as it is read in from the socket.
+ #
+ # If +dest+ argument is given, response is read into that variable,
+ # with <code>dest#<<</code> method (it could be String or IO, or any
+ # other object responding to <code><<</code>).
+ #
+ # Calling this method a second or subsequent time for the same
+ # HTTPResponse object will return the value already read.
+ #
+ # http.request_get('/index.html') {|res|
+ # puts res.read_body
+ # }
+ #
+ # http.request_get('/index.html') {|res|
+ # p res.read_body.object_id # 538149362
+ # p res.read_body.object_id # 538149362
+ # }
+ #
+ # # using iterator
+ # http.request_get('/index.html') {|res|
+ # res.read_body do |segment|
+ # print segment
+ # end
+ # }
+ #
+ def read_body(dest = nil, &block)
+ if @read
+ raise IOError, "#{self.class}\#read_body called twice" if dest or block
+ return @body
+ end
+ to = procdest(dest, block)
+ stream_check
+ if @body_exist
+ read_body_0 to
+ @body = to
+ else
+ @body = nil
+ end
+ @read = true
+ return if @body.nil?
+
+ case enc = @body_encoding
+ when Encoding, false, nil
+ # Encoding: force given encoding
+ # false/nil: do not force encoding
+ else
+ # other value: detect encoding from body
+ enc = detect_encoding(@body)
+ end
+
+ @body.force_encoding(enc) if enc
+
+ @body
+ end
+
+ # Returns the string response body;
+ # note that repeated calls for the unmodified body return a cached string:
+ #
+ # path = '/todos/1'
+ # Gem::Net::HTTP.start(hostname) do |http|
+ # res = http.get(path)
+ # p res.body
+ # p http.head(path).body # No body.
+ # end
+ #
+ # Output:
+ #
+ # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}"
+ # nil
+ #
+ def body
+ read_body()
+ end
+
+ # Sets the body of the response to the given value.
+ def body=(value)
+ @body = value
+ end
+
+ alias entity body #:nodoc: obsolete
+
+ private
+
+ # :nodoc:
+ def detect_encoding(str, encoding=nil)
+ if encoding
+ elsif encoding = type_params['charset']
+ elsif encoding = check_bom(str)
+ else
+ encoding = case content_type&.downcase
+ when %r{text/x(?:ht)?ml|application/(?:[^+]+\+)?xml}
+ /\A<xml[ \t\r\n]+
+ version[ \t\r\n]*=[ \t\r\n]*(?:"[0-9.]+"|'[0-9.]*')[ \t\r\n]+
+ encoding[ \t\r\n]*=[ \t\r\n]*
+ (?:"([A-Za-z][\-A-Za-z0-9._]*)"|'([A-Za-z][\-A-Za-z0-9._]*)')/x =~ str
+ encoding = $1 || $2 || Encoding::UTF_8
+ when %r{text/html.*}
+ sniff_encoding(str)
+ end
+ end
+ return encoding
+ end
+
+ # :nodoc:
+ def sniff_encoding(str, encoding=nil)
+ # the encoding sniffing algorithm
+ # http://www.w3.org/TR/html5/parsing.html#determining-the-character-encoding
+ if enc = scanning_meta(str)
+ enc
+ # 6. last visited page or something
+ # 7. frequency
+ elsif str.ascii_only?
+ Encoding::US_ASCII
+ elsif str.dup.force_encoding(Encoding::UTF_8).valid_encoding?
+ Encoding::UTF_8
+ end
+ # 8. implementation-defined or user-specified
+ end
+
+ # :nodoc:
+ def check_bom(str)
+ case str.byteslice(0, 2)
+ when "\xFE\xFF"
+ return Encoding::UTF_16BE
+ when "\xFF\xFE"
+ return Encoding::UTF_16LE
+ end
+ if "\xEF\xBB\xBF" == str.byteslice(0, 3)
+ return Encoding::UTF_8
+ end
+ nil
+ end
+
+ # :nodoc:
+ def scanning_meta(str)
+ require 'strscan'
+ ss = StringScanner.new(str)
+ if ss.scan_until(/<meta[\t\n\f\r ]*/)
+ attrs = {} # attribute_list
+ got_pragma = false
+ need_pragma = nil
+ charset = nil
+
+ # step: Attributes
+ while attr = get_attribute(ss)
+ name, value = *attr
+ next if attrs[name]
+ attrs[name] = true
+ case name
+ when 'http-equiv'
+ got_pragma = true if value == 'content-type'
+ when 'content'
+ encoding = extracting_encodings_from_meta_elements(value)
+ unless charset
+ charset = encoding
+ end
+ need_pragma = true
+ when 'charset'
+ need_pragma = false
+ charset = value
+ end
+ end
+
+ # step: Processing
+ return if need_pragma.nil?
+ return if need_pragma && !got_pragma
+
+ charset = Encoding.find(charset) rescue nil
+ return unless charset
+ charset = Encoding::UTF_8 if charset == Encoding::UTF_16
+ return charset # tentative
+ end
+ nil
+ end
+
+ def get_attribute(ss)
+ ss.scan(/[\t\n\f\r \/]*/)
+ if ss.peek(1) == '>'
+ ss.getch
+ return nil
+ end
+ name = ss.scan(/[^=\t\n\f\r \/>]*/)
+ name.downcase!
+ raise if name.empty?
+ ss.skip(/[\t\n\f\r ]*/)
+ if ss.getch != '='
+ value = ''
+ return [name, value]
+ end
+ ss.skip(/[\t\n\f\r ]*/)
+ case ss.peek(1)
+ when '"'
+ ss.getch
+ value = ss.scan(/[^"]+/)
+ value.downcase!
+ ss.getch
+ when "'"
+ ss.getch
+ value = ss.scan(/[^']+/)
+ value.downcase!
+ ss.getch
+ when '>'
+ value = ''
+ else
+ value = ss.scan(/[^\t\n\f\r >]+/)
+ value.downcase!
+ end
+ [name, value]
+ end
+
+ def extracting_encodings_from_meta_elements(value)
+ # http://dev.w3.org/html5/spec/fetching-resources.html#algorithm-for-extracting-an-encoding-from-a-meta-element
+ if /charset[\t\n\f\r ]*=(?:"([^"]*)"|'([^']*)'|["']|\z|([^\t\n\f\r ;]+))/i =~ value
+ return $1 || $2 || $3
+ end
+ return nil
+ end
+
+ ##
+ # Checks for a supported Content-Encoding header and yields an Inflate
+ # wrapper for this response's socket when zlib is present. If the
+ # Content-Encoding is not supported or zlib is missing, the plain socket is
+ # yielded.
+ #
+ # If a Content-Range header is present, a plain socket is yielded as the
+ # bytes in the range may not be a complete deflate block.
+
+ def inflater # :nodoc:
+ return yield @socket unless Gem::Net::HTTP::HAVE_ZLIB
+ return yield @socket unless @decode_content
+ return yield @socket if self['content-range']
+
+ v = self['content-encoding']
+ case v&.downcase
+ when 'deflate', 'gzip', 'x-gzip' then
+ self.delete 'content-encoding'
+
+ inflate_body_io = Inflater.new(@socket)
+
+ begin
+ yield inflate_body_io
+ success = true
+ ensure
+ begin
+ inflate_body_io.finish
+ if self['content-length']
+ self['content-length'] = inflate_body_io.bytes_inflated.to_s
+ end
+ rescue => err
+ # Ignore #finish's error if there is an exception from yield
+ raise err if success
+ end
+ end
+ when 'none', 'identity' then
+ self.delete 'content-encoding'
+
+ yield @socket
+ else
+ yield @socket
+ end
+ end
+
+ def read_body_0(dest)
+ inflater do |inflate_body_io|
+ if chunked?
+ read_chunked dest, inflate_body_io
+ return
+ end
+
+ @socket = inflate_body_io
+
+ clen = content_length()
+ if clen
+ @socket.read clen, dest, @ignore_eof
+ return
+ end
+ clen = range_length()
+ if clen
+ @socket.read clen, dest
+ return
+ end
+ @socket.read_all dest
+ end
+ end
+
+ ##
+ # read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF,
+ # etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip
+ # encoded.
+ #
+ # See RFC 2616 section 3.6.1 for definitions
+
+ def read_chunked(dest, chunk_data_io) # :nodoc:
+ total = 0
+ while true
+ line = @socket.readline
+ hexlen = line.slice(/[0-9a-fA-F]+/) or
+ raise Gem::Net::HTTPBadResponse, "wrong chunk size line: #{line}"
+ len = hexlen.hex
+ break if len == 0
+ begin
+ chunk_data_io.read len, dest
+ ensure
+ total += len
+ @socket.read 2 # \r\n
+ end
+ end
+ until @socket.readline.empty?
+ # none
+ end
+ end
+
+ def stream_check
+ raise IOError, 'attempt to read body out of block' if @socket.nil? || @socket.closed?
+ end
+
+ def procdest(dest, block)
+ raise ArgumentError, 'both arg and block given for HTTP method' if
+ dest and block
+ if block
+ Gem::Net::ReadAdapter.new(block)
+ else
+ dest || +''
+ end
+ end
+
+ ##
+ # Inflater is a wrapper around Gem::Net::BufferedIO that transparently inflates
+ # zlib and gzip streams.
+
+ class Inflater # :nodoc:
+
+ ##
+ # Creates a new Inflater wrapping +socket+
+
+ def initialize socket
+ @socket = socket
+ # zlib with automatic gzip detection
+ @inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
+ end
+
+ ##
+ # Finishes the inflate stream.
+
+ def finish
+ return if @inflate.total_in == 0
+ @inflate.finish
+ end
+
+ ##
+ # The number of bytes inflated, used to update the Content-Length of
+ # the response.
+
+ def bytes_inflated
+ @inflate.total_out
+ end
+
+ ##
+ # Returns a Gem::Net::ReadAdapter that inflates each read chunk into +dest+.
+ #
+ # This allows a large response body to be inflated without storing the
+ # entire body in memory.
+
+ def inflate_adapter(dest)
+ if dest.respond_to?(:set_encoding)
+ dest.set_encoding(Encoding::ASCII_8BIT)
+ elsif dest.respond_to?(:force_encoding)
+ dest.force_encoding(Encoding::ASCII_8BIT)
+ end
+ block = proc do |compressed_chunk|
+ @inflate.inflate(compressed_chunk) do |chunk|
+ compressed_chunk.clear
+ dest << chunk
+ end
+ end
+
+ Gem::Net::ReadAdapter.new(block)
+ end
+
+ ##
+ # Reads +clen+ bytes from the socket, inflates them, then writes them to
+ # +dest+. +ignore_eof+ is passed down to Gem::Net::BufferedIO#read
+ #
+ # Unlike Gem::Net::BufferedIO#read, this method returns more than +clen+ bytes.
+ # At this time there is no way for a user of Gem::Net::HTTPResponse to read a
+ # specific number of bytes from the HTTP response body, so this internal
+ # API does not return the same number of bytes as were requested.
+ #
+ # See https://bugs.ruby-lang.org/issues/6492 for further discussion.
+
+ def read clen, dest, ignore_eof = false
+ temp_dest = inflate_adapter(dest)
+
+ @socket.read clen, temp_dest, ignore_eof
+ end
+
+ ##
+ # Reads the rest of the socket, inflates it, then writes it to +dest+.
+
+ def read_all dest
+ temp_dest = inflate_adapter(dest)
+
+ @socket.read_all temp_dest
+ end
+
+ end
+
+end
+
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb
new file mode 100644
index 0000000000..0f26ae6c26
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb
@@ -0,0 +1,1174 @@
+# frozen_string_literal: true
+#--
+# https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+
+module Gem::Net
+
+ class HTTPUnknownResponse < HTTPResponse
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPError #
+ end
+
+ # Parent class for informational (1xx) HTTP response classes.
+ #
+ # An informational response indicates that the request was received and understood.
+ #
+ # References:
+ #
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.1xx].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response].
+ #
+ class HTTPInformation < HTTPResponse
+ HAS_BODY = false
+ EXCEPTION_TYPE = HTTPError #
+ end
+
+ # Parent class for success (2xx) HTTP response classes.
+ #
+ # A success response indicates the action requested by the client
+ # was received, understood, and accepted.
+ #
+ # References:
+ #
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.2xx].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success].
+ #
+ class HTTPSuccess < HTTPResponse
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPError #
+ end
+
+ # Parent class for redirection (3xx) HTTP response classes.
+ #
+ # A redirection response indicates the client must take additional action
+ # to complete the request.
+ #
+ # References:
+ #
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.3xx].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection].
+ #
+ class HTTPRedirection < HTTPResponse
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPRetriableError #
+ end
+
+ # Parent class for client error (4xx) HTTP response classes.
+ #
+ # A client error response indicates that the client may have caused an error.
+ #
+ # References:
+ #
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.4xx].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors].
+ #
+ class HTTPClientError < HTTPResponse
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPClientException #
+ end
+
+ # Parent class for server error (5xx) HTTP response classes.
+ #
+ # A server error response indicates that the server failed to fulfill a request.
+ #
+ # References:
+ #
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.5xx].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors].
+ #
+ class HTTPServerError < HTTPResponse
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPFatalError #
+ end
+
+ # Response class for +Continue+ responses (status code 100).
+ #
+ # A +Continue+ response indicates that the server has received the request headers.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-100-continue].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100].
+ #
+ class HTTPContinue < HTTPInformation
+ HAS_BODY = false
+ end
+
+ # Response class for <tt>Switching Protocol</tt> responses (status code 101).
+ #
+ # The <tt>Switching Protocol<tt> response indicates that the server has received
+ # a request to switch protocols, and has agreed to do so.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101].
+ #
+ class HTTPSwitchProtocol < HTTPInformation
+ HAS_BODY = false
+ end
+
+ # Response class for +Processing+ responses (status code 102).
+ #
+ # The +Processing+ response indicates that the server has received
+ # and is processing the request, but no response is available yet.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 2518}[https://www.rfc-editor.org/rfc/rfc2518#section-10.1].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102].
+ #
+ class HTTPProcessing < HTTPInformation
+ HAS_BODY = false
+ end
+
+ # Response class for <tt>Early Hints</tt> responses (status code 103).
+ #
+ # The <tt>Early Hints</tt> indicates that the server has received
+ # and is processing the request, and contains certain headers;
+ # the final response is not available yet.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103].
+ # - {RFC 8297}[https://www.rfc-editor.org/rfc/rfc8297.html#section-2].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103].
+ #
+ class HTTPEarlyHints < HTTPInformation
+ HAS_BODY = false
+ end
+
+ # Response class for +OK+ responses (status code 200).
+ #
+ # The +OK+ response indicates that the server has received
+ # a request and has responded successfully.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200].
+ #
+ class HTTPOK < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for +Created+ responses (status code 201).
+ #
+ # The +Created+ response indicates that the server has received
+ # and has fulfilled a request to create a new resource.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-201-created].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201].
+ #
+ class HTTPCreated < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for +Accepted+ responses (status code 202).
+ #
+ # The +Accepted+ response indicates that the server has received
+ # and is processing a request, but the processing has not yet been completed.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-202-accepted].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202].
+ #
+ class HTTPAccepted < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Non-Authoritative Information</tt> responses (status code 203).
+ #
+ # The <tt>Non-Authoritative Information</tt> response indicates that the server
+ # is a transforming proxy (such as a Web accelerator)
+ # that received a 200 OK response from its origin,
+ # and is returning a modified version of the origin's response.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/203].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-203-non-authoritative-infor].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203].
+ #
+ class HTTPNonAuthoritativeInformation < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>No Content</tt> responses (status code 204).
+ #
+ # The <tt>No Content</tt> response indicates that the server
+ # successfully processed the request, and is not returning any content.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-204-no-content].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204].
+ #
+ class HTTPNoContent < HTTPSuccess
+ HAS_BODY = false
+ end
+
+ # Response class for <tt>Reset Content</tt> responses (status code 205).
+ #
+ # The <tt>Reset Content</tt> response indicates that the server
+ # successfully processed the request,
+ # asks that the client reset its document view, and is not returning any content.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/205].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-205-reset-content].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205].
+ #
+ class HTTPResetContent < HTTPSuccess
+ HAS_BODY = false
+ end
+
+ # Response class for <tt>Partial Content</tt> responses (status code 206).
+ #
+ # The <tt>Partial Content</tt> response indicates that the server is delivering
+ # only part of the resource (byte serving)
+ # due to a Range header in the request.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-206-partial-content].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206].
+ #
+ class HTTPPartialContent < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Multi-Status (WebDAV)</tt> responses (status code 207).
+ #
+ # The <tt>Multi-Status (WebDAV)</tt> response indicates that the server
+ # has received the request,
+ # and that the message body can contain a number of separate response codes.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 4818}[https://www.rfc-editor.org/rfc/rfc4918#section-11.1].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207].
+ #
+ class HTTPMultiStatus < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Already Reported (WebDAV)</tt> responses (status code 208).
+ #
+ # The <tt>Already Reported (WebDAV)</tt> response indicates that the server
+ # has received the request,
+ # and that the members of a DAV binding have already been enumerated
+ # in a preceding part of the (multi-status) response,
+ # and are not being included again.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 5842}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.1].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208].
+ #
+ class HTTPAlreadyReported < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>IM Used</tt> responses (status code 226).
+ #
+ # The <tt>IM Used</tt> response indicates that the server has fulfilled a request
+ # for the resource, and the response is a representation of the result
+ # of one or more instance-manipulations applied to the current instance.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 3229}[https://www.rfc-editor.org/rfc/rfc3229.html#section-10.4.1].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226].
+ #
+ class HTTPIMUsed < HTTPSuccess
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Multiple Choices</tt> responses (status code 300).
+ #
+ # The <tt>Multiple Choices</tt> response indicates that the server
+ # offers multiple options for the resource from which the client may choose.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/300].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-300-multiple-choices].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300].
+ #
+ class HTTPMultipleChoices < HTTPRedirection
+ HAS_BODY = true
+ end
+ HTTPMultipleChoice = HTTPMultipleChoices
+
+ # Response class for <tt>Moved Permanently</tt> responses (status code 301).
+ #
+ # The <tt>Moved Permanently</tt> response indicates that links or records
+ # returning this response should be updated to use the given URL.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-301-moved-permanently].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301].
+ #
+ class HTTPMovedPermanently < HTTPRedirection
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Found</tt> responses (status code 302).
+ #
+ # The <tt>Found</tt> response indicates that the client
+ # should look at (browse to) another URL.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-302-found].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302].
+ #
+ class HTTPFound < HTTPRedirection
+ HAS_BODY = true
+ end
+ HTTPMovedTemporarily = HTTPFound
+
+ # Response class for <tt>See Other</tt> responses (status code 303).
+ #
+ # The response to the request can be found under another Gem::URI using the GET method.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-303-see-other].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303].
+ #
+ class HTTPSeeOther < HTTPRedirection
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Not Modified</tt> responses (status code 304).
+ #
+ # Indicates that the resource has not been modified since the version
+ # specified by the request headers.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304].
+ #
+ class HTTPNotModified < HTTPRedirection
+ HAS_BODY = false
+ end
+
+ # Response class for <tt>Use Proxy</tt> responses (status code 305).
+ #
+ # The requested resource is available only through a proxy,
+ # whose address is provided in the response.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-305-use-proxy].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305].
+ #
+ class HTTPUseProxy < HTTPRedirection
+ HAS_BODY = false
+ end
+
+ # Response class for <tt>Temporary Redirect</tt> responses (status code 307).
+ #
+ # The request should be repeated with another Gem::URI;
+ # however, future requests should still use the original Gem::URI.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-307-temporary-redirect].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307].
+ #
+ class HTTPTemporaryRedirect < HTTPRedirection
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Permanent Redirect</tt> responses (status code 308).
+ #
+ # This and all future requests should be directed to the given Gem::URI.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-308-permanent-redirect].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308].
+ #
+ class HTTPPermanentRedirect < HTTPRedirection
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Bad Request</tt> responses (status code 400).
+ #
+ # The server cannot or will not process the request due to an apparent client error.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400].
+ #
+ class HTTPBadRequest < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Unauthorized</tt> responses (status code 401).
+ #
+ # Authentication is required, but either was not provided or failed.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-401-unauthorized].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401].
+ #
+ class HTTPUnauthorized < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Payment Required</tt> responses (status code 402).
+ #
+ # Reserved for future use.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-402-payment-required].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402].
+ #
+ class HTTPPaymentRequired < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Forbidden</tt> responses (status code 403).
+ #
+ # The request contained valid data and was understood by the server,
+ # but the server is refusing action.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403].
+ #
+ class HTTPForbidden < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Not Found</tt> responses (status code 404).
+ #
+ # The requested resource could not be found but may be available in the future.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-404-not-found].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404].
+ #
+ class HTTPNotFound < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Method Not Allowed</tt> responses (status code 405).
+ #
+ # The request method is not supported for the requested resource.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-405-method-not-allowed].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405].
+ #
+ class HTTPMethodNotAllowed < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Not Acceptable</tt> responses (status code 406).
+ #
+ # The requested resource is capable of generating only content
+ # that not acceptable according to the Accept headers sent in the request.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-406-not-acceptable].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406].
+ #
+ class HTTPNotAcceptable < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Proxy Authentication Required</tt> responses (status code 407).
+ #
+ # The client must first authenticate itself with the proxy.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-407-proxy-authentication-re].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407].
+ #
+ class HTTPProxyAuthenticationRequired < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Request Gem::Timeout</tt> responses (status code 408).
+ #
+ # The server timed out waiting for the request.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-408-request-timeout].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408].
+ #
+ class HTTPRequestTimeout < HTTPClientError
+ HAS_BODY = true
+ end
+ HTTPRequestTimeOut = HTTPRequestTimeout
+
+ # Response class for <tt>Conflict</tt> responses (status code 409).
+ #
+ # The request could not be processed because of conflict in the current state of the resource.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409].
+ #
+ class HTTPConflict < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Gone</tt> responses (status code 410).
+ #
+ # The resource requested was previously in use but is no longer available
+ # and will not be available again.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-410-gone].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410].
+ #
+ class HTTPGone < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Length Required</tt> responses (status code 411).
+ #
+ # The request did not specify the length of its content,
+ # which is required by the requested resource.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-411-length-required].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411].
+ #
+ class HTTPLengthRequired < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Precondition Failed</tt> responses (status code 412).
+ #
+ # The server does not meet one of the preconditions
+ # specified in the request headers.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-412-precondition-failed].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412].
+ #
+ class HTTPPreconditionFailed < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Payload Too Large</tt> responses (status code 413).
+ #
+ # The request is larger than the server is willing or able to process.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413].
+ #
+ class HTTPPayloadTooLarge < HTTPClientError
+ HAS_BODY = true
+ end
+ HTTPRequestEntityTooLarge = HTTPPayloadTooLarge
+
+ # Response class for <tt>Gem::URI Too Long</tt> responses (status code 414).
+ #
+ # The Gem::URI provided was too long for the server to process.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-414-uri-too-long].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414].
+ #
+ class HTTPURITooLong < HTTPClientError
+ HAS_BODY = true
+ end
+ HTTPRequestURITooLong = HTTPURITooLong
+ HTTPRequestURITooLarge = HTTPRequestURITooLong
+
+ # Response class for <tt>Unsupported Media Type</tt> responses (status code 415).
+ #
+ # The request entity has a media type which the server or resource does not support.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-415-unsupported-media-type].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415].
+ #
+ class HTTPUnsupportedMediaType < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Range Not Satisfiable</tt> responses (status code 416).
+ #
+ # The request entity has a media type which the server or resource does not support.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-416-range-not-satisfiable].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416].
+ #
+ class HTTPRangeNotSatisfiable < HTTPClientError
+ HAS_BODY = true
+ end
+ HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable
+
+ # Response class for <tt>Expectation Failed</tt> responses (status code 417).
+ #
+ # The server cannot meet the requirements of the Expect request-header field.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-417-expectation-failed].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417].
+ #
+ class HTTPExpectationFailed < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # 418 I'm a teapot - RFC 2324; a joke RFC
+ # See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#418.
+
+ # 420 Enhance Your Calm - Twitter
+
+ # Response class for <tt>Misdirected Request</tt> responses (status code 421).
+ #
+ # The request was directed at a server that is not able to produce a response.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-421-misdirected-request].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421].
+ #
+ class HTTPMisdirectedRequest < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Unprocessable Entity</tt> responses (status code 422).
+ #
+ # The request was well-formed but had semantic errors.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-422-unprocessable-content].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422].
+ #
+ class HTTPUnprocessableEntity < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Locked (WebDAV)</tt> responses (status code 423).
+ #
+ # The requested resource is locked.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.3].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423].
+ #
+ class HTTPLocked < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Failed Dependency (WebDAV)</tt> responses (status code 424).
+ #
+ # The request failed because it depended on another request and that request failed.
+ # See {424 Failed Dependency (WebDAV)}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424].
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.4].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424].
+ #
+ class HTTPFailedDependency < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # 425 Too Early
+ # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#425.
+
+ # Response class for <tt>Upgrade Required</tt> responses (status code 426).
+ #
+ # The client should switch to the protocol given in the Upgrade header field.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-426-upgrade-required].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426].
+ #
+ class HTTPUpgradeRequired < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Precondition Required</tt> responses (status code 428).
+ #
+ # The origin server requires the request to be conditional.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/428].
+ # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-3].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428].
+ #
+ class HTTPPreconditionRequired < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Too Many Requests</tt> responses (status code 429).
+ #
+ # The user has sent too many requests in a given amount of time.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429].
+ # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-4].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429].
+ #
+ class HTTPTooManyRequests < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Request Header Fields Too Large</tt> responses (status code 431).
+ #
+ # An individual header field is too large,
+ # or all the header fields collectively, are too large.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431].
+ # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-5].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431].
+ #
+ class HTTPRequestHeaderFieldsTooLarge < HTTPClientError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Unavailable For Legal Reasons</tt> responses (status code 451).
+ #
+ # A server operator has received a legal demand to deny access to a resource or to a set of resources
+ # that includes the requested resource.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/451].
+ # - {RFC 7725}[https://www.rfc-editor.org/rfc/rfc7725.html#section-3].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451].
+ #
+ class HTTPUnavailableForLegalReasons < HTTPClientError
+ HAS_BODY = true
+ end
+ # 444 No Response - Nginx
+ # 449 Retry With - Microsoft
+ # 450 Blocked by Windows Parental Controls - Microsoft
+ # 499 Client Closed Request - Nginx
+
+ # Response class for <tt>Internal Server Error</tt> responses (status code 500).
+ #
+ # An unexpected condition was encountered and no more specific message is suitable.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-500-internal-server-error].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500].
+ #
+ class HTTPInternalServerError < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Not Implemented</tt> responses (status code 501).
+ #
+ # The server either does not recognize the request method,
+ # or it lacks the ability to fulfil the request.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-501-not-implemented].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501].
+ #
+ class HTTPNotImplemented < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Bad Gateway</tt> responses (status code 502).
+ #
+ # The server was acting as a gateway or proxy
+ # and received an invalid response from the upstream server.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502].
+ #
+ class HTTPBadGateway < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Service Unavailable</tt> responses (status code 503).
+ #
+ # The server cannot handle the request
+ # (because it is overloaded or down for maintenance).
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503].
+ #
+ class HTTPServiceUnavailable < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Gateway Gem::Timeout</tt> responses (status code 504).
+ #
+ # The server was acting as a gateway or proxy
+ # and did not receive a timely response from the upstream server.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504].
+ #
+ class HTTPGatewayTimeout < HTTPServerError
+ HAS_BODY = true
+ end
+ HTTPGatewayTimeOut = HTTPGatewayTimeout
+
+ # Response class for <tt>HTTP Version Not Supported</tt> responses (status code 505).
+ #
+ # The server does not support the HTTP version used in the request.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505].
+ # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-505-http-version-not-suppor].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505].
+ #
+ class HTTPVersionNotSupported < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Variant Also Negotiates</tt> responses (status code 506).
+ #
+ # Transparent content negotiation for the request results in a circular reference.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/506].
+ # - {RFC 2295}[https://www.rfc-editor.org/rfc/rfc2295#section-8.1].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506].
+ #
+ class HTTPVariantAlsoNegotiates < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Insufficient Storage (WebDAV)</tt> responses (status code 507).
+ #
+ # The server is unable to store the representation needed to complete the request.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/507].
+ # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.5].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507].
+ #
+ class HTTPInsufficientStorage < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Loop Detected (WebDAV)</tt> responses (status code 508).
+ #
+ # The server detected an infinite loop while processing the request.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508].
+ # - {RFC 5942}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.2].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508].
+ #
+ class HTTPLoopDetected < HTTPServerError
+ HAS_BODY = true
+ end
+ # 509 Bandwidth Limit Exceeded - Apache bw/limited extension
+
+ # Response class for <tt>Not Extended</tt> responses (status code 510).
+ #
+ # Further extensions to the request are required for the server to fulfill it.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/510].
+ # - {RFC 2774}[https://www.rfc-editor.org/rfc/rfc2774.html#section-7].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510].
+ #
+ class HTTPNotExtended < HTTPServerError
+ HAS_BODY = true
+ end
+
+ # Response class for <tt>Network Authentication Required</tt> responses (status code 511).
+ #
+ # The client needs to authenticate to gain network access.
+ #
+ # :include: doc/net-http/included_getters.rdoc
+ #
+ # References:
+ #
+ # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511].
+ # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-6].
+ # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511].
+ #
+ class HTTPNetworkAuthenticationRequired < HTTPServerError
+ HAS_BODY = true
+ end
+
+end
+
+class Gem::Net::HTTPResponse
+ CODE_CLASS_TO_OBJ = {
+ '1' => Gem::Net::HTTPInformation,
+ '2' => Gem::Net::HTTPSuccess,
+ '3' => Gem::Net::HTTPRedirection,
+ '4' => Gem::Net::HTTPClientError,
+ '5' => Gem::Net::HTTPServerError
+ }
+ CODE_TO_OBJ = {
+ '100' => Gem::Net::HTTPContinue,
+ '101' => Gem::Net::HTTPSwitchProtocol,
+ '102' => Gem::Net::HTTPProcessing,
+ '103' => Gem::Net::HTTPEarlyHints,
+
+ '200' => Gem::Net::HTTPOK,
+ '201' => Gem::Net::HTTPCreated,
+ '202' => Gem::Net::HTTPAccepted,
+ '203' => Gem::Net::HTTPNonAuthoritativeInformation,
+ '204' => Gem::Net::HTTPNoContent,
+ '205' => Gem::Net::HTTPResetContent,
+ '206' => Gem::Net::HTTPPartialContent,
+ '207' => Gem::Net::HTTPMultiStatus,
+ '208' => Gem::Net::HTTPAlreadyReported,
+ '226' => Gem::Net::HTTPIMUsed,
+
+ '300' => Gem::Net::HTTPMultipleChoices,
+ '301' => Gem::Net::HTTPMovedPermanently,
+ '302' => Gem::Net::HTTPFound,
+ '303' => Gem::Net::HTTPSeeOther,
+ '304' => Gem::Net::HTTPNotModified,
+ '305' => Gem::Net::HTTPUseProxy,
+ '307' => Gem::Net::HTTPTemporaryRedirect,
+ '308' => Gem::Net::HTTPPermanentRedirect,
+
+ '400' => Gem::Net::HTTPBadRequest,
+ '401' => Gem::Net::HTTPUnauthorized,
+ '402' => Gem::Net::HTTPPaymentRequired,
+ '403' => Gem::Net::HTTPForbidden,
+ '404' => Gem::Net::HTTPNotFound,
+ '405' => Gem::Net::HTTPMethodNotAllowed,
+ '406' => Gem::Net::HTTPNotAcceptable,
+ '407' => Gem::Net::HTTPProxyAuthenticationRequired,
+ '408' => Gem::Net::HTTPRequestTimeout,
+ '409' => Gem::Net::HTTPConflict,
+ '410' => Gem::Net::HTTPGone,
+ '411' => Gem::Net::HTTPLengthRequired,
+ '412' => Gem::Net::HTTPPreconditionFailed,
+ '413' => Gem::Net::HTTPPayloadTooLarge,
+ '414' => Gem::Net::HTTPURITooLong,
+ '415' => Gem::Net::HTTPUnsupportedMediaType,
+ '416' => Gem::Net::HTTPRangeNotSatisfiable,
+ '417' => Gem::Net::HTTPExpectationFailed,
+ '421' => Gem::Net::HTTPMisdirectedRequest,
+ '422' => Gem::Net::HTTPUnprocessableEntity,
+ '423' => Gem::Net::HTTPLocked,
+ '424' => Gem::Net::HTTPFailedDependency,
+ '426' => Gem::Net::HTTPUpgradeRequired,
+ '428' => Gem::Net::HTTPPreconditionRequired,
+ '429' => Gem::Net::HTTPTooManyRequests,
+ '431' => Gem::Net::HTTPRequestHeaderFieldsTooLarge,
+ '451' => Gem::Net::HTTPUnavailableForLegalReasons,
+
+ '500' => Gem::Net::HTTPInternalServerError,
+ '501' => Gem::Net::HTTPNotImplemented,
+ '502' => Gem::Net::HTTPBadGateway,
+ '503' => Gem::Net::HTTPServiceUnavailable,
+ '504' => Gem::Net::HTTPGatewayTimeout,
+ '505' => Gem::Net::HTTPVersionNotSupported,
+ '506' => Gem::Net::HTTPVariantAlsoNegotiates,
+ '507' => Gem::Net::HTTPInsufficientStorage,
+ '508' => Gem::Net::HTTPLoopDetected,
+ '510' => Gem::Net::HTTPNotExtended,
+ '511' => Gem::Net::HTTPNetworkAuthenticationRequired,
+ }
+end
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/status.rb b/lib/rubygems/vendor/net-http/lib/net/http/status.rb
new file mode 100644
index 0000000000..9110b108b8
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/http/status.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require_relative '../http'
+
+if $0 == __FILE__
+ require 'open-uri'
+ File.foreach(__FILE__) do |line|
+ puts line
+ break if line.start_with?('end')
+ end
+ puts
+ puts "Gem::Net::HTTP::STATUS_CODES = {"
+ url = "https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv"
+ Gem::URI(url).read.each_line do |line|
+ code, mes, = line.split(',')
+ next if ['(Unused)', 'Unassigned', 'Description'].include?(mes)
+ puts " #{code} => '#{mes}',"
+ end
+ puts "} # :nodoc:"
+end
+
+Gem::Net::HTTP::STATUS_CODES = {
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 103 => 'Early Hints',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 208 => 'Already Reported',
+ 226 => 'IM Used',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Content Too Large',
+ 414 => 'URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 421 => 'Misdirected Request',
+ 422 => 'Unprocessable Content',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Too Early',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 510 => 'Not Extended (OBSOLETED)',
+ 511 => 'Network Authentication Required',
+} # :nodoc:
diff --git a/lib/rubygems/vendor/net-http/lib/net/https.rb b/lib/rubygems/vendor/net-http/lib/net/https.rb
new file mode 100644
index 0000000000..d2784f0be0
--- /dev/null
+++ b/lib/rubygems/vendor/net-http/lib/net/https.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+=begin
+
+= net/https -- SSL/TLS enhancement for Gem::Net::HTTP.
+
+ This file has been merged with net/http. There is no longer any need to
+ require 'rubygems/vendor/net-http/lib/net/https' to use HTTPS.
+
+ See Gem::Net::HTTP for details on how to make HTTPS connections.
+
+== Info
+ 'OpenSSL for Ruby 2' project
+ Copyright (C) 2001 GOTOU Yuuzou <gotoyuzo@notwork.org>
+ All rights reserved.
+
+== Licence
+ This program is licensed under the same licence as Ruby.
+ (See the file 'LICENCE'.)
+
+=end
+
+require_relative 'http'
+require 'openssl'
diff --git a/lib/rubygems/vendor/net-protocol/.document b/lib/rubygems/vendor/net-protocol/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/net-protocol/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/vendor/net-protocol/lib/net/protocol.rb b/lib/rubygems/vendor/net-protocol/lib/net/protocol.rb
new file mode 100644
index 0000000000..53d34d8d98
--- /dev/null
+++ b/lib/rubygems/vendor/net-protocol/lib/net/protocol.rb
@@ -0,0 +1,544 @@
+# frozen_string_literal: true
+#
+# = net/protocol.rb
+#
+#--
+# Copyright (c) 1999-2004 Yukihiro Matsumoto
+# Copyright (c) 1999-2004 Minero Aoki
+#
+# written and maintained by Minero Aoki <aamine@loveruby.net>
+#
+# This program is free software. You can re-distribute and/or
+# modify this program under the same terms as Ruby itself,
+# Ruby Distribute License or GNU General Public License.
+#
+# $Id$
+#++
+#
+# WARNING: This file is going to remove.
+# Do not rely on the implementation written in this file.
+#
+
+require 'socket'
+require_relative '../../../timeout/lib/timeout'
+require 'io/wait'
+
+module Gem::Net # :nodoc:
+
+ class Protocol #:nodoc: internal use only
+ VERSION = "0.2.2"
+
+ private
+ def Protocol.protocol_param(name, val)
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
+ def #{name}
+ #{val}
+ end
+ End
+ end
+
+ def ssl_socket_connect(s, timeout)
+ if timeout
+ while true
+ raise Gem::Net::OpenTimeout if timeout <= 0
+ start = Process.clock_gettime Process::CLOCK_MONOTONIC
+ # to_io is required because SSLSocket doesn't have wait_readable yet
+ case s.connect_nonblock(exception: false)
+ when :wait_readable; s.to_io.wait_readable(timeout)
+ when :wait_writable; s.to_io.wait_writable(timeout)
+ else; break
+ end
+ timeout -= Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
+ end
+ else
+ s.connect
+ end
+ end
+ end
+
+
+ class ProtocolError < StandardError; end
+ class ProtoSyntaxError < ProtocolError; end
+ class ProtoFatalError < ProtocolError; end
+ class ProtoUnknownError < ProtocolError; end
+ class ProtoServerError < ProtocolError; end
+ class ProtoAuthError < ProtocolError; end
+ class ProtoCommandError < ProtocolError; end
+ class ProtoRetriableError < ProtocolError; end
+ ProtocRetryError = ProtoRetriableError
+
+ ##
+ # OpenTimeout, a subclass of Gem::Timeout::Error, is raised if a connection cannot
+ # be created within the open_timeout.
+
+ class OpenTimeout < Gem::Timeout::Error; end
+
+ ##
+ # ReadTimeout, a subclass of Gem::Timeout::Error, is raised if a chunk of the
+ # response cannot be read within the read_timeout.
+
+ class ReadTimeout < Gem::Timeout::Error
+ def initialize(io = nil)
+ @io = io
+ end
+ attr_reader :io
+
+ def message
+ msg = super
+ if @io
+ msg = "#{msg} with #{@io.inspect}"
+ end
+ msg
+ end
+ end
+
+ ##
+ # WriteTimeout, a subclass of Gem::Timeout::Error, is raised if a chunk of the
+ # response cannot be written within the write_timeout. Not raised on Windows.
+
+ class WriteTimeout < Gem::Timeout::Error
+ def initialize(io = nil)
+ @io = io
+ end
+ attr_reader :io
+
+ def message
+ msg = super
+ if @io
+ msg = "#{msg} with #{@io.inspect}"
+ end
+ msg
+ end
+ end
+
+
+ class BufferedIO #:nodoc: internal use only
+ def initialize(io, read_timeout: 60, write_timeout: 60, continue_timeout: nil, debug_output: nil)
+ @io = io
+ @read_timeout = read_timeout
+ @write_timeout = write_timeout
+ @continue_timeout = continue_timeout
+ @debug_output = debug_output
+ @rbuf = ''.b
+ @rbuf_empty = true
+ @rbuf_offset = 0
+ end
+
+ attr_reader :io
+ attr_accessor :read_timeout
+ attr_accessor :write_timeout
+ attr_accessor :continue_timeout
+ attr_accessor :debug_output
+
+ def inspect
+ "#<#{self.class} io=#{@io}>"
+ end
+
+ def eof?
+ @io.eof?
+ end
+
+ def closed?
+ @io.closed?
+ end
+
+ def close
+ @io.close
+ end
+
+ #
+ # Read
+ #
+
+ public
+
+ def read(len, dest = ''.b, ignore_eof = false)
+ LOG "reading #{len} bytes..."
+ read_bytes = 0
+ begin
+ while read_bytes + rbuf_size < len
+ if s = rbuf_consume_all
+ read_bytes += s.bytesize
+ dest << s
+ end
+ rbuf_fill
+ end
+ s = rbuf_consume(len - read_bytes)
+ read_bytes += s.bytesize
+ dest << s
+ rescue EOFError
+ raise unless ignore_eof
+ end
+ LOG "read #{read_bytes} bytes"
+ dest
+ end
+
+ def read_all(dest = ''.b)
+ LOG 'reading all...'
+ read_bytes = 0
+ begin
+ while true
+ if s = rbuf_consume_all
+ read_bytes += s.bytesize
+ dest << s
+ end
+ rbuf_fill
+ end
+ rescue EOFError
+ ;
+ end
+ LOG "read #{read_bytes} bytes"
+ dest
+ end
+
+ def readuntil(terminator, ignore_eof = false)
+ offset = @rbuf_offset
+ begin
+ until idx = @rbuf.index(terminator, offset)
+ offset = @rbuf.bytesize
+ rbuf_fill
+ end
+ return rbuf_consume(idx + terminator.bytesize - @rbuf_offset)
+ rescue EOFError
+ raise unless ignore_eof
+ return rbuf_consume
+ end
+ end
+
+ def readline
+ readuntil("\n").chop
+ end
+
+ private
+
+ BUFSIZE = 1024 * 16
+
+ def rbuf_fill
+ tmp = @rbuf_empty ? @rbuf : nil
+ case rv = @io.read_nonblock(BUFSIZE, tmp, exception: false)
+ when String
+ @rbuf_empty = false
+ if rv.equal?(tmp)
+ @rbuf_offset = 0
+ else
+ @rbuf << rv
+ rv.clear
+ end
+ return
+ when :wait_readable
+ (io = @io.to_io).wait_readable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io)
+ # continue looping
+ when :wait_writable
+ # OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
+ # http://www.openssl.org/support/faq.html#PROG10
+ (io = @io.to_io).wait_writable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io)
+ # continue looping
+ when nil
+ raise EOFError, 'end of file reached'
+ end while true
+ end
+
+ def rbuf_flush
+ if @rbuf_empty
+ @rbuf.clear
+ @rbuf_offset = 0
+ end
+ nil
+ end
+
+ def rbuf_size
+ @rbuf.bytesize - @rbuf_offset
+ end
+
+ def rbuf_consume_all
+ rbuf_consume if rbuf_size > 0
+ end
+
+ def rbuf_consume(len = nil)
+ if @rbuf_offset == 0 && (len.nil? || len == @rbuf.bytesize)
+ s = @rbuf
+ @rbuf = ''.b
+ @rbuf_offset = 0
+ @rbuf_empty = true
+ elsif len.nil?
+ s = @rbuf.byteslice(@rbuf_offset..-1)
+ @rbuf = ''.b
+ @rbuf_offset = 0
+ @rbuf_empty = true
+ else
+ s = @rbuf.byteslice(@rbuf_offset, len)
+ @rbuf_offset += len
+ @rbuf_empty = @rbuf_offset == @rbuf.bytesize
+ rbuf_flush
+ end
+
+ @debug_output << %Q[-> #{s.dump}\n] if @debug_output
+ s
+ end
+
+ #
+ # Write
+ #
+
+ public
+
+ def write(*strs)
+ writing {
+ write0(*strs)
+ }
+ end
+
+ alias << write
+
+ def writeline(str)
+ writing {
+ write0 str + "\r\n"
+ }
+ end
+
+ private
+
+ def writing
+ @written_bytes = 0
+ @debug_output << '<- ' if @debug_output
+ yield
+ @debug_output << "\n" if @debug_output
+ bytes = @written_bytes
+ @written_bytes = nil
+ bytes
+ end
+
+ def write0(*strs)
+ @debug_output << strs.map(&:dump).join if @debug_output
+ orig_written_bytes = @written_bytes
+ strs.each_with_index do |str, i|
+ need_retry = true
+ case len = @io.write_nonblock(str, exception: false)
+ when Integer
+ @written_bytes += len
+ len -= str.bytesize
+ if len == 0
+ if strs.size == i+1
+ return @written_bytes - orig_written_bytes
+ else
+ need_retry = false
+ # next string
+ end
+ elsif len < 0
+ str = str.byteslice(len, -len)
+ else # len > 0
+ need_retry = false
+ # next string
+ end
+ # continue looping
+ when :wait_writable
+ (io = @io.to_io).wait_writable(@write_timeout) or raise Gem::Net::WriteTimeout.new(io)
+ # continue looping
+ end while need_retry
+ end
+ end
+
+ #
+ # Logging
+ #
+
+ private
+
+ def LOG_off
+ @save_debug_out = @debug_output
+ @debug_output = nil
+ end
+
+ def LOG_on
+ @debug_output = @save_debug_out
+ end
+
+ def LOG(msg)
+ return unless @debug_output
+ @debug_output << msg + "\n"
+ end
+ end
+
+
+ class InternetMessageIO < BufferedIO #:nodoc: internal use only
+ def initialize(*, **)
+ super
+ @wbuf = nil
+ end
+
+ #
+ # Read
+ #
+
+ def each_message_chunk
+ LOG 'reading message...'
+ LOG_off()
+ read_bytes = 0
+ while (line = readuntil("\r\n")) != ".\r\n"
+ read_bytes += line.size
+ yield line.delete_prefix('.')
+ end
+ LOG_on()
+ LOG "read message (#{read_bytes} bytes)"
+ end
+
+ # *library private* (cannot handle 'break')
+ def each_list_item
+ while (str = readuntil("\r\n")) != ".\r\n"
+ yield str.chop
+ end
+ end
+
+ def write_message_0(src)
+ prev = @written_bytes
+ each_crlf_line(src) do |line|
+ write0 dot_stuff(line)
+ end
+ @written_bytes - prev
+ end
+
+ #
+ # Write
+ #
+
+ def write_message(src)
+ LOG "writing message from #{src.class}"
+ LOG_off()
+ len = writing {
+ using_each_crlf_line {
+ write_message_0 src
+ }
+ }
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
+ end
+
+ def write_message_by_block(&block)
+ LOG 'writing message from block'
+ LOG_off()
+ len = writing {
+ using_each_crlf_line {
+ begin
+ block.call(WriteAdapter.new(self.method(:write_message_0)))
+ rescue LocalJumpError
+ # allow `break' from writer block
+ end
+ }
+ }
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
+ end
+
+ private
+
+ def dot_stuff(s)
+ s.sub(/\A\./, '..')
+ end
+
+ def using_each_crlf_line
+ @wbuf = ''.b
+ yield
+ if not @wbuf.empty? # unterminated last line
+ write0 dot_stuff(@wbuf.chomp) + "\r\n"
+ elsif @written_bytes == 0 # empty src
+ write0 "\r\n"
+ end
+ write0 ".\r\n"
+ @wbuf = nil
+ end
+
+ def each_crlf_line(src)
+ buffer_filling(@wbuf, src) do
+ while line = @wbuf.slice!(/\A[^\r\n]*(?:\n|\r(?:\n|(?!\z)))/)
+ yield line.chomp("\n") + "\r\n"
+ end
+ end
+ end
+
+ def buffer_filling(buf, src)
+ case src
+ when String # for speeding up.
+ 0.step(src.size - 1, 1024) do |i|
+ buf << src[i, 1024]
+ yield
+ end
+ when File # for speeding up.
+ while s = src.read(1024)
+ buf << s
+ yield
+ end
+ else # generic reader
+ src.each do |str|
+ buf << str
+ yield if buf.size > 1024
+ end
+ yield unless buf.empty?
+ end
+ end
+ end
+
+
+ #
+ # The writer adapter class
+ #
+ class WriteAdapter
+ def initialize(writer)
+ @writer = writer
+ end
+
+ def inspect
+ "#<#{self.class} writer=#{@writer.inspect}>"
+ end
+
+ def write(str)
+ @writer.call(str)
+ end
+
+ alias print write
+
+ def <<(str)
+ write str
+ self
+ end
+
+ def puts(str = '')
+ write str.chomp("\n") + "\n"
+ end
+
+ def printf(*args)
+ write sprintf(*args)
+ end
+ end
+
+
+ class ReadAdapter #:nodoc: internal use only
+ def initialize(block)
+ @block = block
+ end
+
+ def inspect
+ "#<#{self.class}>"
+ end
+
+ def <<(str)
+ call_block(str, &@block) if @block
+ end
+
+ private
+
+ # This method is needed because @block must be called by yield,
+ # not Proc#call. You can see difference when using `break' in
+ # the block.
+ def call_block(str)
+ yield str
+ end
+ end
+
+
+ module NetPrivate #:nodoc: obsolete
+ Socket = ::Gem::Net::InternetMessageIO
+ end
+
+end # module Gem::Net
diff --git a/lib/rubygems/vendor/optparse/.document b/lib/rubygems/vendor/optparse/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/vendor/optparse/lib/optionparser.rb b/lib/rubygems/vendor/optparse/lib/optionparser.rb
new file mode 100644
index 0000000000..4b9b40d82a
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optionparser.rb
@@ -0,0 +1,2 @@
+# frozen_string_literal: false
+require_relative 'optparse'
diff --git a/lib/rubygems/vendor/optparse/lib/optparse.rb b/lib/rubygems/vendor/optparse/lib/optparse.rb
new file mode 100644
index 0000000000..5937431720
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse.rb
@@ -0,0 +1,2330 @@
+# frozen_string_literal: true
+#
+# optparse.rb - command-line option analysis with the Gem::OptionParser class.
+#
+# Author:: Nobu Nakada
+# Documentation:: Nobu Nakada and Gavin Sinclair.
+#
+# See Gem::OptionParser for documentation.
+#
+
+
+#--
+# == Developer Documentation (not for RDoc output)
+#
+# === Class tree
+#
+# - Gem::OptionParser:: front end
+# - Gem::OptionParser::Switch:: each switches
+# - Gem::OptionParser::List:: options list
+# - Gem::OptionParser::ParseError:: errors on parsing
+# - Gem::OptionParser::AmbiguousOption
+# - Gem::OptionParser::NeedlessArgument
+# - Gem::OptionParser::MissingArgument
+# - Gem::OptionParser::InvalidOption
+# - Gem::OptionParser::InvalidArgument
+# - Gem::OptionParser::AmbiguousArgument
+#
+# === Object relationship diagram
+#
+# +--------------+
+# | Gem::OptionParser |<>-----+
+# +--------------+ | +--------+
+# | ,-| Switch |
+# on_head -------->+---------------+ / +--------+
+# accept/reject -->| List |<|>-
+# | |<|>- +----------+
+# on ------------->+---------------+ `-| argument |
+# : : | class |
+# +---------------+ |==========|
+# on_tail -------->| | |pattern |
+# +---------------+ |----------|
+# Gem::OptionParser.accept ->| DefaultList | |converter |
+# reject |(shared between| +----------+
+# | all instances)|
+# +---------------+
+#
+#++
+#
+# == Gem::OptionParser
+#
+# === New to +Gem::OptionParser+?
+#
+# See the {Tutorial}[optparse/tutorial.rdoc].
+#
+# === Introduction
+#
+# Gem::OptionParser is a class for command-line option analysis. It is much more
+# advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented
+# solution.
+#
+# === Features
+#
+# 1. The argument specification and the code to handle it are written in the
+# same place.
+# 2. It can output an option summary; you don't need to maintain this string
+# separately.
+# 3. Optional and mandatory arguments are specified very gracefully.
+# 4. Arguments can be automatically converted to a specified class.
+# 5. Arguments can be restricted to a certain set.
+#
+# All of these features are demonstrated in the examples below. See
+# #make_switch for full documentation.
+#
+# === Minimal example
+#
+# require 'rubygems/vendor/optparse/lib/optparse'
+#
+# options = {}
+# Gem::OptionParser.new do |parser|
+# parser.banner = "Usage: example.rb [options]"
+#
+# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
+# options[:verbose] = v
+# end
+# end.parse!
+#
+# p options
+# p ARGV
+#
+# === Generating Help
+#
+# Gem::OptionParser can be used to automatically generate help for the commands you
+# write:
+#
+# require 'rubygems/vendor/optparse/lib/optparse'
+#
+# Options = Struct.new(:name)
+#
+# class Parser
+# def self.parse(options)
+# args = Options.new("world")
+#
+# opt_parser = Gem::OptionParser.new do |parser|
+# parser.banner = "Usage: example.rb [options]"
+#
+# parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n|
+# args.name = n
+# end
+#
+# parser.on("-h", "--help", "Prints this help") do
+# puts parser
+# exit
+# end
+# end
+#
+# opt_parser.parse!(options)
+# return args
+# end
+# end
+# options = Parser.parse %w[--help]
+#
+# #=>
+# # Usage: example.rb [options]
+# # -n, --name=NAME Name to say hello to
+# # -h, --help Prints this help
+#
+# === Required Arguments
+#
+# For options that require an argument, option specification strings may include an
+# option name in all caps. If an option is used without the required argument,
+# an exception will be raised.
+#
+# require 'rubygems/vendor/optparse/lib/optparse'
+#
+# options = {}
+# Gem::OptionParser.new do |parser|
+# parser.on("-r", "--require LIBRARY",
+# "Require the LIBRARY before executing your script") do |lib|
+# puts "You required #{lib}!"
+# end
+# end.parse!
+#
+# Used:
+#
+# $ ruby optparse-test.rb -r
+# optparse-test.rb:9:in `<main>': missing argument: -r (Gem::OptionParser::MissingArgument)
+# $ ruby optparse-test.rb -r my-library
+# You required my-library!
+#
+# === Type Coercion
+#
+# Gem::OptionParser supports the ability to coerce command line arguments
+# into objects for us.
+#
+# Gem::OptionParser comes with a few ready-to-use kinds of type
+# coercion. They are:
+#
+# - Date -- Anything accepted by +Date.parse+ (need to require +optparse/date+)
+# - DateTime -- Anything accepted by +DateTime.parse+ (need to require +optparse/date+)
+# - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+ (need to require +optparse/time+)
+# - URI -- Anything accepted by +Gem::URI.parse+ (need to require +optparse/uri+)
+# - Shellwords -- Anything accepted by +Shellwords.shellwords+ (need to require +optparse/shellwords+)
+# - String -- Any non-empty string
+# - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040)
+# - Float -- Any float. (e.g. 10, 3.14, -100E+13)
+# - Numeric -- Any integer, float, or rational (1, 3.4, 1/3)
+# - DecimalInteger -- Like +Integer+, but no octal format.
+# - OctalInteger -- Like +Integer+, but no decimal format.
+# - DecimalNumeric -- Decimal integer or float.
+# - TrueClass -- Accepts '+, yes, true, -, no, false' and
+# defaults as +true+
+# - FalseClass -- Same as +TrueClass+, but defaults to +false+
+# - Array -- Strings separated by ',' (e.g. 1,2,3)
+# - Regexp -- Regular expressions. Also includes options.
+#
+# We can also add our own coercions, which we will cover below.
+#
+# ==== Using Built-in Conversions
+#
+# As an example, the built-in +Time+ conversion is used. The other built-in
+# conversions behave in the same way.
+# Gem::OptionParser will attempt to parse the argument
+# as a +Time+. If it succeeds, that time will be passed to the
+# handler block. Otherwise, an exception will be raised.
+#
+# require 'rubygems/vendor/optparse/lib/optparse'
+# require 'rubygems/vendor/optparse/lib/optparse/time'
+# Gem::OptionParser.new do |parser|
+# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
+# p time
+# end
+# end.parse!
+#
+# Used:
+#
+# $ ruby optparse-test.rb -t nonsense
+# ... invalid argument: -t nonsense (Gem::OptionParser::InvalidArgument)
+# $ ruby optparse-test.rb -t 10-11-12
+# 2010-11-12 00:00:00 -0500
+# $ ruby optparse-test.rb -t 9:30
+# 2014-08-13 09:30:00 -0400
+#
+# ==== Creating Custom Conversions
+#
+# The +accept+ method on Gem::OptionParser may be used to create converters.
+# It specifies which conversion block to call whenever a class is specified.
+# The example below uses it to fetch a +User+ object before the +on+ handler receives it.
+#
+# require 'rubygems/vendor/optparse/lib/optparse'
+#
+# User = Struct.new(:id, :name)
+#
+# def find_user id
+# not_found = ->{ raise "No User Found for id #{id}" }
+# [ User.new(1, "Sam"),
+# User.new(2, "Gandalf") ].find(not_found) do |u|
+# u.id == id
+# end
+# end
+#
+# op = Gem::OptionParser.new
+# op.accept(User) do |user_id|
+# find_user user_id.to_i
+# end
+#
+# op.on("--user ID", User) do |user|
+# puts user
+# end
+#
+# op.parse!
+#
+# Used:
+#
+# $ ruby optparse-test.rb --user 1
+# #<struct User id=1, name="Sam">
+# $ ruby optparse-test.rb --user 2
+# #<struct User id=2, name="Gandalf">
+# $ ruby optparse-test.rb --user 3
+# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError)
+#
+# === Store options to a Hash
+#
+# The +into+ option of +order+, +parse+ and so on methods stores command line options into a Hash.
+#
+# require 'rubygems/vendor/optparse/lib/optparse'
+#
+# options = {}
+# Gem::OptionParser.new do |parser|
+# parser.on('-a')
+# parser.on('-b NUM', Integer)
+# parser.on('-v', '--verbose')
+# end.parse!(into: options)
+#
+# p options
+#
+# Used:
+#
+# $ ruby optparse-test.rb -a
+# {:a=>true}
+# $ ruby optparse-test.rb -a -v
+# {:a=>true, :verbose=>true}
+# $ ruby optparse-test.rb -a -b 100
+# {:a=>true, :b=>100}
+#
+# === Complete example
+#
+# The following example is a complete Ruby program. You can run it and see the
+# effect of specifying various options. This is probably the best way to learn
+# the features of +optparse+.
+#
+# require 'rubygems/vendor/optparse/lib/optparse'
+# require 'rubygems/vendor/optparse/lib/optparse/time'
+# require 'ostruct'
+# require 'pp'
+#
+# class OptparseExample
+# Version = '1.0.0'
+#
+# CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
+# CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
+#
+# class ScriptOptions
+# attr_accessor :library, :inplace, :encoding, :transfer_type,
+# :verbose, :extension, :delay, :time, :record_separator,
+# :list
+#
+# def initialize
+# self.library = []
+# self.inplace = false
+# self.encoding = "utf8"
+# self.transfer_type = :auto
+# self.verbose = false
+# end
+#
+# def define_options(parser)
+# parser.banner = "Usage: example.rb [options]"
+# parser.separator ""
+# parser.separator "Specific options:"
+#
+# # add additional options
+# perform_inplace_option(parser)
+# delay_execution_option(parser)
+# execute_at_time_option(parser)
+# specify_record_separator_option(parser)
+# list_example_option(parser)
+# specify_encoding_option(parser)
+# optional_option_argument_with_keyword_completion_option(parser)
+# boolean_verbose_option(parser)
+#
+# parser.separator ""
+# parser.separator "Common options:"
+# # No argument, shows at tail. This will print an options summary.
+# # Try it and see!
+# parser.on_tail("-h", "--help", "Show this message") do
+# puts parser
+# exit
+# end
+# # Another typical switch to print the version.
+# parser.on_tail("--version", "Show version") do
+# puts Version
+# exit
+# end
+# end
+#
+# def perform_inplace_option(parser)
+# # Specifies an optional option argument
+# parser.on("-i", "--inplace [EXTENSION]",
+# "Edit ARGV files in place",
+# "(make backup if EXTENSION supplied)") do |ext|
+# self.inplace = true
+# self.extension = ext || ''
+# self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot.
+# end
+# end
+#
+# def delay_execution_option(parser)
+# # Cast 'delay' argument to a Float.
+# parser.on("--delay N", Float, "Delay N seconds before executing") do |n|
+# self.delay = n
+# end
+# end
+#
+# def execute_at_time_option(parser)
+# # Cast 'time' argument to a Time object.
+# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
+# self.time = time
+# end
+# end
+#
+# def specify_record_separator_option(parser)
+# # Cast to octal integer.
+# parser.on("-F", "--irs [OCTAL]", Gem::OptionParser::OctalInteger,
+# "Specify record separator (default \\0)") do |rs|
+# self.record_separator = rs
+# end
+# end
+#
+# def list_example_option(parser)
+# # List of arguments.
+# parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list|
+# self.list = list
+# end
+# end
+#
+# def specify_encoding_option(parser)
+# # Keyword completion. We are specifying a specific set of arguments (CODES
+# # and CODE_ALIASES - notice the latter is a Hash), and the user may provide
+# # the shortest unambiguous text.
+# code_list = (CODE_ALIASES.keys + CODES).join(', ')
+# parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding",
+# "(#{code_list})") do |encoding|
+# self.encoding = encoding
+# end
+# end
+#
+# def optional_option_argument_with_keyword_completion_option(parser)
+# # Optional '--type' option argument with keyword completion.
+# parser.on("--type [TYPE]", [:text, :binary, :auto],
+# "Select transfer type (text, binary, auto)") do |t|
+# self.transfer_type = t
+# end
+# end
+#
+# def boolean_verbose_option(parser)
+# # Boolean switch.
+# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
+# self.verbose = v
+# end
+# end
+# end
+#
+# #
+# # Return a structure describing the options.
+# #
+# def parse(args)
+# # The options specified on the command line will be collected in
+# # *options*.
+#
+# @options = ScriptOptions.new
+# @args = Gem::OptionParser.new do |parser|
+# @options.define_options(parser)
+# parser.parse!(args)
+# end
+# @options
+# end
+#
+# attr_reader :parser, :options
+# end # class OptparseExample
+#
+# example = OptparseExample.new
+# options = example.parse(ARGV)
+# pp options # example.options
+# pp ARGV
+#
+# === Shell Completion
+#
+# For modern shells (e.g. bash, zsh, etc.), you can use shell
+# completion for command line options.
+#
+# === Further documentation
+#
+# The above examples, along with the accompanying
+# {Tutorial}[optparse/tutorial.rdoc],
+# should be enough to learn how to use this class.
+# If you have any questions, file a ticket at http://bugs.ruby-lang.org.
+#
+class Gem::OptionParser
+ Gem::OptionParser::Version = "0.4.0"
+
+ # :stopdoc:
+ NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
+ RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze
+ OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze
+ # :startdoc:
+
+ #
+ # Keyword completion module. This allows partial arguments to be specified
+ # and resolved against a list of acceptable values.
+ #
+ module Completion
+ def self.regexp(key, icase)
+ Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase)
+ end
+
+ def self.candidate(key, icase = false, pat = nil, &block)
+ pat ||= Completion.regexp(key, icase)
+ candidates = []
+ block.call do |k, *v|
+ (if Regexp === k
+ kn = ""
+ k === key
+ else
+ kn = defined?(k.id2name) ? k.id2name : k
+ pat === kn
+ end) or next
+ v << k if v.empty?
+ candidates << [k, v, kn]
+ end
+ candidates
+ end
+
+ def candidate(key, icase = false, pat = nil)
+ Completion.candidate(key, icase, pat, &method(:each))
+ end
+
+ public
+ def complete(key, icase = false, pat = nil)
+ candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size}
+ if candidates.size == 1
+ canon, sw, * = candidates[0]
+ elsif candidates.size > 1
+ canon, sw, cn = candidates.shift
+ candidates.each do |k, v, kn|
+ next if sw == v
+ if String === cn and String === kn
+ if cn.rindex(kn, 0)
+ canon, sw, cn = k, v, kn
+ next
+ elsif kn.rindex(cn, 0)
+ next
+ end
+ end
+ throw :ambiguous, key
+ end
+ end
+ if canon
+ block_given? or return key, *sw
+ yield(key, *sw)
+ end
+ end
+
+ def convert(opt = nil, val = nil, *)
+ val
+ end
+ end
+
+
+ #
+ # Map from option/keyword string to object with completion.
+ #
+ class OptionMap < Hash
+ include Completion
+ end
+
+
+ #
+ # Individual switch class. Not important to the user.
+ #
+ # Defined within Switch are several Switch-derived classes: NoArgument,
+ # RequiredArgument, etc.
+ #
+ class Switch
+ attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block
+
+ #
+ # Guesses argument style from +arg+. Returns corresponding
+ # Gem::OptionParser::Switch class (OptionalArgument, etc.).
+ #
+ def self.guess(arg)
+ case arg
+ when ""
+ t = self
+ when /\A=?\[/
+ t = Switch::OptionalArgument
+ when /\A\s+\[/
+ t = Switch::PlacedArgument
+ else
+ t = Switch::RequiredArgument
+ end
+ self >= t or incompatible_argument_styles(arg, t)
+ t
+ end
+
+ def self.incompatible_argument_styles(arg, t)
+ raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}",
+ ParseError.filter_backtrace(caller(2)))
+ end
+
+ def self.pattern
+ NilClass
+ end
+
+ def initialize(pattern = nil, conv = nil,
+ short = nil, long = nil, arg = nil,
+ desc = ([] if short or long), block = nil, &_block)
+ raise if Array === pattern
+ block ||= _block
+ @pattern, @conv, @short, @long, @arg, @desc, @block =
+ pattern, conv, short, long, arg, desc, block
+ end
+
+ #
+ # Parses +arg+ and returns rest of +arg+ and matched portion to the
+ # argument pattern. Yields when the pattern doesn't match substring.
+ #
+ def parse_arg(arg) # :nodoc:
+ pattern or return nil, [arg]
+ unless m = pattern.match(arg)
+ yield(InvalidArgument, arg)
+ return arg, []
+ end
+ if String === m
+ m = [s = m]
+ else
+ m = m.to_a
+ s = m[0]
+ return nil, m unless String === s
+ end
+ raise InvalidArgument, arg unless arg.rindex(s, 0)
+ return nil, m if s.length == arg.length
+ yield(InvalidArgument, arg) # didn't match whole arg
+ return arg[s.length..-1], m
+ end
+ private :parse_arg
+
+ #
+ # Parses argument, converts and returns +arg+, +block+ and result of
+ # conversion. Yields at semi-error condition instead of raising an
+ # exception.
+ #
+ def conv_arg(arg, val = []) # :nodoc:
+ if conv
+ val = conv.call(*val)
+ else
+ val = proc {|v| v}.call(*val)
+ end
+ return arg, block, val
+ end
+ private :conv_arg
+
+ #
+ # Produces the summary text. Each line of the summary is yielded to the
+ # block (without newline).
+ #
+ # +sdone+:: Already summarized short style options keyed hash.
+ # +ldone+:: Already summarized long style options keyed hash.
+ # +width+:: Width of left side (option part). In other words, the right
+ # side (description part) starts after +width+ columns.
+ # +max+:: Maximum width of left side -> the options are filled within
+ # +max+ columns.
+ # +indent+:: Prefix string indents all summarized lines.
+ #
+ def summarize(sdone = {}, ldone = {}, width = 1, max = width - 1, indent = "")
+ sopts, lopts = [], [], nil
+ @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
+ @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
+ return if sopts.empty? and lopts.empty? # completely hidden
+
+ left = [sopts.join(', ')]
+ right = desc.dup
+
+ while s = lopts.shift
+ l = left[-1].length + s.length
+ l += arg.length if left.size == 1 && arg
+ l < max or sopts.empty? or left << +''
+ left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s
+ end
+
+ if arg
+ left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg)
+ end
+ mlen = left.collect {|ss| ss.length}.max.to_i
+ while mlen > width and l = left.shift
+ mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen
+ if l.length < width and (r = right[0]) and !r.empty?
+ l = l.to_s.ljust(width) + ' ' + r
+ right.shift
+ end
+ yield(indent + l)
+ end
+
+ while begin l = left.shift; r = right.shift; l or r end
+ l = l.to_s.ljust(width) + ' ' + r if r and !r.empty?
+ yield(indent + l)
+ end
+
+ self
+ end
+
+ def add_banner(to) # :nodoc:
+ unless @short or @long
+ s = desc.join
+ to << " [" + s + "]..." unless s.empty?
+ end
+ to
+ end
+
+ def match_nonswitch?(str) # :nodoc:
+ @pattern =~ str unless @short or @long
+ end
+
+ #
+ # Main name of the switch.
+ #
+ def switch_name
+ (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '')
+ end
+
+ def compsys(sdone, ldone) # :nodoc:
+ sopts, lopts = [], []
+ @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
+ @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
+ return if sopts.empty? and lopts.empty? # completely hidden
+
+ (sopts+lopts).each do |opt|
+ # "(-x -c -r)-l[left justify]"
+ if /^--\[no-\](.+)$/ =~ opt
+ o = $1
+ yield("--#{o}", desc.join(""))
+ yield("--no-#{o}", desc.join(""))
+ else
+ yield("#{opt}", desc.join(""))
+ end
+ end
+ end
+
+ def pretty_print_contents(q) # :nodoc:
+ if @block
+ q.text ":" + @block.source_location.join(":") + ":"
+ first = false
+ else
+ first = true
+ end
+ [@short, @long].each do |list|
+ list.each do |opt|
+ if first
+ q.text ":"
+ first = false
+ end
+ q.breakable
+ q.text opt
+ end
+ end
+ end
+
+ def pretty_print(q) # :nodoc:
+ q.object_group(self) {pretty_print_contents(q)}
+ end
+
+ #
+ # Switch that takes no arguments.
+ #
+ class NoArgument < self
+
+ #
+ # Raises an exception if any arguments given.
+ #
+ def parse(arg, argv)
+ yield(NeedlessArgument, arg) if arg
+ conv_arg(arg)
+ end
+
+ def self.incompatible_argument_styles(*)
+ end
+
+ def self.pattern
+ Object
+ end
+
+ def pretty_head # :nodoc:
+ "NoArgument"
+ end
+ end
+
+ #
+ # Switch that takes an argument.
+ #
+ class RequiredArgument < self
+
+ #
+ # Raises an exception if argument is not present.
+ #
+ def parse(arg, argv)
+ unless arg
+ raise MissingArgument if argv.empty?
+ arg = argv.shift
+ end
+ conv_arg(*parse_arg(arg, &method(:raise)))
+ end
+
+ def pretty_head # :nodoc:
+ "Required"
+ end
+ end
+
+ #
+ # Switch that can omit argument.
+ #
+ class OptionalArgument < self
+
+ #
+ # Parses argument if given, or uses default value.
+ #
+ def parse(arg, argv, &error)
+ if arg
+ conv_arg(*parse_arg(arg, &error))
+ else
+ conv_arg(arg)
+ end
+ end
+
+ def pretty_head # :nodoc:
+ "Optional"
+ end
+ end
+
+ #
+ # Switch that takes an argument, which does not begin with '-' or is '-'.
+ #
+ class PlacedArgument < self
+
+ #
+ # Returns nil if argument is not present or begins with '-' and is not '-'.
+ #
+ def parse(arg, argv, &error)
+ if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0]))
+ return nil, block, nil
+ end
+ opt = (val = parse_arg(val, &error))[1]
+ val = conv_arg(*val)
+ if opt and !arg
+ argv.shift
+ else
+ val[0] = nil
+ end
+ val
+ end
+
+ def pretty_head # :nodoc:
+ "Placed"
+ end
+ end
+ end
+
+ #
+ # Simple option list providing mapping from short and/or long option
+ # string to Gem::OptionParser::Switch and mapping from acceptable argument to
+ # matching pattern and converter pair. Also provides summary feature.
+ #
+ class List
+ # Map from acceptable argument types to pattern and converter pairs.
+ attr_reader :atype
+
+ # Map from short style option switches to actual switch objects.
+ attr_reader :short
+
+ # Map from long style option switches to actual switch objects.
+ attr_reader :long
+
+ # List of all switches and summary string.
+ attr_reader :list
+
+ #
+ # Just initializes all instance variables.
+ #
+ def initialize
+ @atype = {}
+ @short = OptionMap.new
+ @long = OptionMap.new
+ @list = []
+ end
+
+ def pretty_print(q) # :nodoc:
+ q.group(1, "(", ")") do
+ @list.each do |sw|
+ next unless Switch === sw
+ q.group(1, "(" + sw.pretty_head, ")") do
+ sw.pretty_print_contents(q)
+ end
+ end
+ end
+ end
+
+ #
+ # See Gem::OptionParser.accept.
+ #
+ def accept(t, pat = /.*/m, &block)
+ if pat
+ pat.respond_to?(:match) or
+ raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2))
+ else
+ pat = t if t.respond_to?(:match)
+ end
+ unless block
+ block = pat.method(:convert).to_proc if pat.respond_to?(:convert)
+ end
+ @atype[t] = [pat, block]
+ end
+
+ #
+ # See Gem::OptionParser.reject.
+ #
+ def reject(t)
+ @atype.delete(t)
+ end
+
+ #
+ # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+.
+ #
+ # +sw+:: Gem::OptionParser::Switch instance to be added.
+ # +sopts+:: Short style option list.
+ # +lopts+:: Long style option list.
+ # +nlopts+:: Negated long style options list.
+ #
+ def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc:
+ sopts.each {|o| @short[o] = sw} if sopts
+ lopts.each {|o| @long[o] = sw} if lopts
+ nlopts.each {|o| @long[o] = nsw} if nsw and nlopts
+ used = @short.invert.update(@long.invert)
+ @list.delete_if {|o| Switch === o and !used[o]}
+ end
+ private :update
+
+ #
+ # Inserts +switch+ at the head of the list, and associates short, long
+ # and negated long options. Arguments are:
+ #
+ # +switch+:: Gem::OptionParser::Switch instance to be inserted.
+ # +short_opts+:: List of short style options.
+ # +long_opts+:: List of long style options.
+ # +nolong_opts+:: List of long style options with "no-" prefix.
+ #
+ # prepend(switch, short_opts, long_opts, nolong_opts)
+ #
+ def prepend(*args)
+ update(*args)
+ @list.unshift(args[0])
+ end
+
+ #
+ # Appends +switch+ at the tail of the list, and associates short, long
+ # and negated long options. Arguments are:
+ #
+ # +switch+:: Gem::OptionParser::Switch instance to be inserted.
+ # +short_opts+:: List of short style options.
+ # +long_opts+:: List of long style options.
+ # +nolong_opts+:: List of long style options with "no-" prefix.
+ #
+ # append(switch, short_opts, long_opts, nolong_opts)
+ #
+ def append(*args)
+ update(*args)
+ @list.push(args[0])
+ end
+
+ #
+ # Searches +key+ in +id+ list. The result is returned or yielded if a
+ # block is given. If it isn't found, nil is returned.
+ #
+ def search(id, key)
+ if list = __send__(id)
+ val = list.fetch(key) {return nil}
+ block_given? ? yield(val) : val
+ end
+ end
+
+ #
+ # Searches list +id+ for +opt+ and the optional patterns for completion
+ # +pat+. If +icase+ is true, the search is case insensitive. The result
+ # is returned or yielded if a block is given. If it isn't found, nil is
+ # returned.
+ #
+ def complete(id, opt, icase = false, *pat, &block)
+ __send__(id).complete(opt, icase, *pat, &block)
+ end
+
+ def get_candidates(id)
+ yield __send__(id).keys
+ end
+
+ #
+ # Iterates over each option, passing the option to the +block+.
+ #
+ def each_option(&block)
+ list.each(&block)
+ end
+
+ #
+ # Creates the summary table, passing each line to the +block+ (without
+ # newline). The arguments +args+ are passed along to the summarize
+ # method which is called on every option.
+ #
+ def summarize(*args, &block)
+ sum = []
+ list.reverse_each do |opt|
+ if opt.respond_to?(:summarize) # perhaps Gem::OptionParser::Switch
+ s = []
+ opt.summarize(*args) {|l| s << l}
+ sum.concat(s.reverse)
+ elsif !opt or opt.empty?
+ sum << ""
+ elsif opt.respond_to?(:each_line)
+ sum.concat([*opt.each_line].reverse)
+ else
+ sum.concat([*opt.each].reverse)
+ end
+ end
+ sum.reverse_each(&block)
+ end
+
+ def add_banner(to) # :nodoc:
+ list.each do |opt|
+ if opt.respond_to?(:add_banner)
+ opt.add_banner(to)
+ end
+ end
+ to
+ end
+
+ def compsys(*args, &block) # :nodoc:
+ list.each do |opt|
+ if opt.respond_to?(:compsys)
+ opt.compsys(*args, &block)
+ end
+ end
+ end
+ end
+
+ #
+ # Hash with completion search feature. See Gem::OptionParser::Completion.
+ #
+ class CompletingHash < Hash
+ include Completion
+
+ #
+ # Completion for hash key.
+ #
+ def match(key)
+ *values = fetch(key) {
+ raise AmbiguousArgument, catch(:ambiguous) {return complete(key)}
+ }
+ return key, *values
+ end
+ end
+
+ # :stopdoc:
+
+ #
+ # Enumeration of acceptable argument styles. Possible values are:
+ #
+ # NO_ARGUMENT:: The switch takes no arguments. (:NONE)
+ # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED)
+ # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL)
+ #
+ # Use like --switch=argument (long style) or -Xargument (short style). For
+ # short style, only portion matched to argument pattern is treated as
+ # argument.
+ #
+ ArgumentStyle = {}
+ NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument}
+ RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument}
+ OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument}
+ ArgumentStyle.freeze
+
+ #
+ # Switches common used such as '--', and also provides default
+ # argument classes
+ #
+ DefaultList = List.new
+ DefaultList.short['-'] = Switch::NoArgument.new {}
+ DefaultList.long[''] = Switch::NoArgument.new {throw :terminate}
+
+
+ COMPSYS_HEADER = <<'XXX' # :nodoc:
+
+typeset -A opt_args
+local context state line
+
+_arguments -s -S \
+XXX
+
+ def compsys(to, name = File.basename($0)) # :nodoc:
+ to << "#compdef #{name}\n"
+ to << COMPSYS_HEADER
+ visit(:compsys, {}, {}) {|o, d|
+ to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n]
+ }
+ to << " '*:file:_files' && return 0\n"
+ end
+
+ #
+ # Default options for ARGV, which never appear in option summary.
+ #
+ Officious = {}
+
+ #
+ # --help
+ # Shows option summary.
+ #
+ Officious['help'] = proc do |parser|
+ Switch::NoArgument.new do |arg|
+ puts parser.help
+ exit
+ end
+ end
+
+ #
+ # --*-completion-bash=WORD
+ # Shows candidates for command line completion.
+ #
+ Officious['*-completion-bash'] = proc do |parser|
+ Switch::RequiredArgument.new do |arg|
+ puts parser.candidate(arg)
+ exit
+ end
+ end
+
+ #
+ # --*-completion-zsh[=NAME:FILE]
+ # Creates zsh completion file.
+ #
+ Officious['*-completion-zsh'] = proc do |parser|
+ Switch::OptionalArgument.new do |arg|
+ parser.compsys(STDOUT, arg)
+ exit
+ end
+ end
+
+ #
+ # --version
+ # Shows version string if Version is defined.
+ #
+ Officious['version'] = proc do |parser|
+ Switch::OptionalArgument.new do |pkg|
+ if pkg
+ begin
+ require 'rubygems/vendor/optparse/lib/optparse/version'
+ rescue LoadError
+ else
+ show_version(*pkg.split(/,/)) or
+ abort("#{parser.program_name}: no version found in package #{pkg}")
+ exit
+ end
+ end
+ v = parser.ver or abort("#{parser.program_name}: version unknown")
+ puts v
+ exit
+ end
+ end
+
+ # :startdoc:
+
+ #
+ # Class methods
+ #
+
+ #
+ # Initializes a new instance and evaluates the optional block in context
+ # of the instance. Arguments +args+ are passed to #new, see there for
+ # description of parameters.
+ #
+ # This method is *deprecated*, its behavior corresponds to the older #new
+ # method.
+ #
+ def self.with(*args, &block)
+ opts = new(*args)
+ opts.instance_eval(&block)
+ opts
+ end
+
+ #
+ # Returns an incremented value of +default+ according to +arg+.
+ #
+ def self.inc(arg, default = nil)
+ case arg
+ when Integer
+ arg.nonzero?
+ when nil
+ default.to_i + 1
+ end
+ end
+ def inc(*args)
+ self.class.inc(*args)
+ end
+
+ #
+ # Initializes the instance and yields itself if called with a block.
+ #
+ # +banner+:: Banner message.
+ # +width+:: Summary width.
+ # +indent+:: Summary indent.
+ #
+ def initialize(banner = nil, width = 32, indent = ' ' * 4)
+ @stack = [DefaultList, List.new, List.new]
+ @program_name = nil
+ @banner = banner
+ @summary_width = width
+ @summary_indent = indent
+ @default_argv = ARGV
+ @require_exact = false
+ @raise_unknown = true
+ add_officious
+ yield self if block_given?
+ end
+
+ def add_officious # :nodoc:
+ list = base()
+ Officious.each do |opt, block|
+ list.long[opt] ||= block.call(self)
+ end
+ end
+
+ #
+ # Terminates option parsing. Optional parameter +arg+ is a string pushed
+ # back to be the first non-option argument.
+ #
+ def terminate(arg = nil)
+ self.class.terminate(arg)
+ end
+ def self.terminate(arg = nil)
+ throw :terminate, arg
+ end
+
+ @stack = [DefaultList]
+ def self.top() DefaultList end
+
+ #
+ # Directs to accept specified class +t+. The argument string is passed to
+ # the block in which it should be converted to the desired class.
+ #
+ # +t+:: Argument class specifier, any object including Class.
+ # +pat+:: Pattern for argument, defaults to +t+ if it responds to match.
+ #
+ # accept(t, pat, &block)
+ #
+ def accept(*args, &blk) top.accept(*args, &blk) end
+ #
+ # See #accept.
+ #
+ def self.accept(*args, &blk) top.accept(*args, &blk) end
+
+ #
+ # Directs to reject specified class argument.
+ #
+ # +t+:: Argument class specifier, any object including Class.
+ #
+ # reject(t)
+ #
+ def reject(*args, &blk) top.reject(*args, &blk) end
+ #
+ # See #reject.
+ #
+ def self.reject(*args, &blk) top.reject(*args, &blk) end
+
+ #
+ # Instance methods
+ #
+
+ # Heading banner preceding summary.
+ attr_writer :banner
+
+ # Program name to be emitted in error message and default banner,
+ # defaults to $0.
+ attr_writer :program_name
+
+ # Width for option list portion of summary. Must be Numeric.
+ attr_accessor :summary_width
+
+ # Indentation for summary. Must be String (or have + String method).
+ attr_accessor :summary_indent
+
+ # Strings to be parsed in default.
+ attr_accessor :default_argv
+
+ # Whether to require that options match exactly (disallows providing
+ # abbreviated long option as short option).
+ attr_accessor :require_exact
+
+ # Whether to raise at unknown option.
+ attr_accessor :raise_unknown
+
+ #
+ # Heading banner preceding summary.
+ #
+ def banner
+ unless @banner
+ @banner = +"Usage: #{program_name} [options]"
+ visit(:add_banner, @banner)
+ end
+ @banner
+ end
+
+ #
+ # Program name to be emitted in error message and default banner, defaults
+ # to $0.
+ #
+ def program_name
+ @program_name || File.basename($0, '.*')
+ end
+
+ # for experimental cascading :-)
+ alias set_banner banner=
+ alias set_program_name program_name=
+ alias set_summary_width summary_width=
+ alias set_summary_indent summary_indent=
+
+ # Version
+ attr_writer :version
+ # Release code
+ attr_writer :release
+
+ #
+ # Version
+ #
+ def version
+ (defined?(@version) && @version) || (defined?(::Version) && ::Version)
+ end
+
+ #
+ # Release code
+ #
+ def release
+ (defined?(@release) && @release) || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE)
+ end
+
+ #
+ # Returns version string from program_name, version and release.
+ #
+ def ver
+ if v = version
+ str = +"#{program_name} #{[v].join('.')}"
+ str << " (#{v})" if v = release
+ str
+ end
+ end
+
+ def warn(mesg = $!)
+ super("#{program_name}: #{mesg}")
+ end
+
+ def abort(mesg = $!)
+ super("#{program_name}: #{mesg}")
+ end
+
+ #
+ # Subject of #on / #on_head, #accept / #reject
+ #
+ def top
+ @stack[-1]
+ end
+
+ #
+ # Subject of #on_tail.
+ #
+ def base
+ @stack[1]
+ end
+
+ #
+ # Pushes a new List.
+ #
+ def new
+ @stack.push(List.new)
+ if block_given?
+ yield self
+ else
+ self
+ end
+ end
+
+ #
+ # Removes the last List.
+ #
+ def remove
+ @stack.pop
+ end
+
+ #
+ # Puts option summary into +to+ and returns +to+. Yields each line if
+ # a block is given.
+ #
+ # +to+:: Output destination, which must have method <<. Defaults to [].
+ # +width+:: Width of left side, defaults to @summary_width.
+ # +max+:: Maximum length allowed for left side, defaults to +width+ - 1.
+ # +indent+:: Indentation, defaults to @summary_indent.
+ #
+ def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk)
+ nl = "\n"
+ blk ||= proc {|l| to << (l.index(nl, -1) ? l : l + nl)}
+ visit(:summarize, {}, {}, width, max, indent, &blk)
+ to
+ end
+
+ #
+ # Returns option summary string.
+ #
+ def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end
+ alias to_s help
+
+ def pretty_print(q) # :nodoc:
+ q.object_group(self) do
+ first = true
+ if @stack.size > 2
+ @stack.each_with_index do |s, i|
+ next if i < 2
+ next if s.list.empty?
+ if first
+ first = false
+ q.text ":"
+ end
+ q.breakable
+ s.pretty_print(q)
+ end
+ end
+ end
+ end
+
+ def inspect # :nodoc:
+ require 'pp'
+ pretty_print_inspect
+ end
+
+ #
+ # Returns option summary list.
+ #
+ def to_a; summarize("#{banner}".split(/^/)) end
+
+ #
+ # Checks if an argument is given twice, in which case an ArgumentError is
+ # raised. Called from Gem::OptionParser#switch only.
+ #
+ # +obj+:: New argument.
+ # +prv+:: Previously specified argument.
+ # +msg+:: Exception message.
+ #
+ def notwice(obj, prv, msg) # :nodoc:
+ unless !prv or prv == obj
+ raise(ArgumentError, "argument #{msg} given twice: #{obj}",
+ ParseError.filter_backtrace(caller(2)))
+ end
+ obj
+ end
+ private :notwice
+
+ SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc:
+
+ # :call-seq:
+ # make_switch(params, block = nil)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def make_switch(opts, block = nil)
+ short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], []
+ ldesc, sdesc, desc, arg = [], [], []
+ default_style = Switch::NoArgument
+ default_pattern = nil
+ klass = nil
+ q, a = nil
+ has_arg = false
+
+ opts.each do |o|
+ # argument class
+ next if search(:atype, o) do |pat, c|
+ klass = notwice(o, klass, 'type')
+ if not_style and not_style != Switch::NoArgument
+ not_pattern, not_conv = pat, c
+ else
+ default_pattern, conv = pat, c
+ end
+ end
+
+ # directly specified pattern(any object possible to match)
+ if (!(String === o || Symbol === o)) and o.respond_to?(:match)
+ pattern = notwice(o, pattern, 'pattern')
+ if pattern.respond_to?(:convert)
+ conv = pattern.method(:convert).to_proc
+ else
+ conv = SPLAT_PROC
+ end
+ next
+ end
+
+ # anything others
+ case o
+ when Proc, Method
+ block = notwice(o, block, 'block')
+ when Array, Hash
+ case pattern
+ when CompletingHash
+ when nil
+ pattern = CompletingHash.new
+ conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert)
+ else
+ raise ArgumentError, "argument pattern given twice"
+ end
+ o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}}
+ when Module
+ raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4))
+ when *ArgumentStyle.keys
+ style = notwice(ArgumentStyle[o], style, 'style')
+ when /^--no-([^\[\]=\s]*)(.+)?/
+ q, a = $1, $2
+ o = notwice(a ? Object : TrueClass, klass, 'type')
+ not_pattern, not_conv = search(:atype, o) unless not_style
+ not_style = (not_style || default_style).guess(arg = a) if a
+ default_style = Switch::NoArgument
+ default_pattern, conv = search(:atype, FalseClass) unless default_pattern
+ ldesc << "--no-#{q}"
+ (q = q.downcase).tr!('_', '-')
+ long << "no-#{q}"
+ nolong << q
+ when /^--\[no-\]([^\[\]=\s]*)(.+)?/
+ q, a = $1, $2
+ o = notwice(a ? Object : TrueClass, klass, 'type')
+ if a
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ end
+ ldesc << "--[no-]#{q}"
+ (o = q.downcase).tr!('_', '-')
+ long << o
+ not_pattern, not_conv = search(:atype, FalseClass) unless not_style
+ not_style = Switch::NoArgument
+ nolong << "no-#{o}"
+ when /^--([^\[\]=\s]*)(.+)?/
+ q, a = $1, $2
+ if a
+ o = notwice(NilClass, klass, 'type')
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ end
+ ldesc << "--#{q}"
+ (o = q.downcase).tr!('_', '-')
+ long << o
+ when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
+ q, a = $1, $2
+ o = notwice(Object, klass, 'type')
+ if a
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ else
+ has_arg = true
+ end
+ sdesc << "-#{q}"
+ short << Regexp.new(q)
+ when /^-(.)(.+)?/
+ q, a = $1, $2
+ if a
+ o = notwice(NilClass, klass, 'type')
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ end
+ sdesc << "-#{q}"
+ short << q
+ when /^=/
+ style = notwice(default_style.guess(arg = o), style, 'style')
+ default_pattern, conv = search(:atype, Object) unless default_pattern
+ else
+ desc.push(o) if o && !o.empty?
+ end
+ end
+
+ default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
+ if !(short.empty? and long.empty?)
+ if has_arg and default_style == Switch::NoArgument
+ default_style = Switch::RequiredArgument
+ end
+ s = (style || default_style).new(pattern || default_pattern,
+ conv, sdesc, ldesc, arg, desc, block)
+ elsif !block
+ if style or pattern
+ raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
+ end
+ s = desc
+ else
+ short << pattern
+ s = (style || default_style).new(pattern,
+ conv, nil, nil, arg, desc, block)
+ end
+ return s, short, long,
+ (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
+ nolong
+ end
+
+ # :call-seq:
+ # define(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def define(*opts, &block)
+ top.append(*(sw = make_switch(opts, block)))
+ sw[0]
+ end
+
+ # :call-seq:
+ # on(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def on(*opts, &block)
+ define(*opts, &block)
+ self
+ end
+ alias def_option define
+
+ # :call-seq:
+ # define_head(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def define_head(*opts, &block)
+ top.prepend(*(sw = make_switch(opts, block)))
+ sw[0]
+ end
+
+ # :call-seq:
+ # on_head(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ # The new option is added at the head of the summary.
+ #
+ def on_head(*opts, &block)
+ define_head(*opts, &block)
+ self
+ end
+ alias def_head_option define_head
+
+ # :call-seq:
+ # define_tail(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def define_tail(*opts, &block)
+ base.append(*(sw = make_switch(opts, block)))
+ sw[0]
+ end
+
+ #
+ # :call-seq:
+ # on_tail(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ # The new option is added at the tail of the summary.
+ #
+ def on_tail(*opts, &block)
+ define_tail(*opts, &block)
+ self
+ end
+ alias def_tail_option define_tail
+
+ #
+ # Add separator in summary.
+ #
+ def separator(string)
+ top.append(string, nil, nil)
+ end
+
+ #
+ # Parses command line arguments +argv+ in order. When a block is given,
+ # each non-option argument is yielded. When optional +into+ keyword
+ # argument is provided, the parsed option values are stored there via
+ # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other
+ # similar object).
+ #
+ # Returns the rest of +argv+ left unparsed.
+ #
+ def order(*argv, into: nil, &nonopt)
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
+ order!(argv, into: into, &nonopt)
+ end
+
+ #
+ # Same as #order, but removes switches destructively.
+ # Non-option arguments remain in +argv+.
+ #
+ def order!(argv = default_argv, into: nil, &nonopt)
+ setter = ->(name, val) {into[name.to_sym] = val} if into
+ parse_in_order(argv, setter, &nonopt)
+ end
+
+ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
+ opt, arg, val, rest = nil
+ nonopt ||= proc {|a| throw :terminate, a}
+ argv.unshift(arg) if arg = catch(:terminate) {
+ while arg = argv.shift
+ case arg
+ # long option
+ when /\A--([^=]*)(?:=(.*))?/m
+ opt, rest = $1, $2
+ opt.tr!('_', '-')
+ begin
+ sw, = complete(:long, opt, true)
+ if require_exact && !sw.long.include?(arg)
+ throw :terminate, arg unless raise_unknown
+ raise InvalidOption, arg
+ end
+ rescue ParseError
+ throw :terminate, arg unless raise_unknown
+ raise $!.set_option(arg, true)
+ end
+ begin
+ opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
+ val = cb.call(val) if cb
+ setter.call(sw.switch_name, val) if setter
+ rescue ParseError
+ raise $!.set_option(arg, rest)
+ end
+
+ # short option
+ when /\A-(.)((=).*|.+)?/m
+ eq, rest, opt = $3, $2, $1
+ has_arg, val = eq, rest
+ begin
+ sw, = search(:short, opt)
+ unless sw
+ begin
+ sw, = complete(:short, opt)
+ # short option matched.
+ val = arg.delete_prefix('-')
+ has_arg = true
+ rescue InvalidOption
+ raise if require_exact
+ # if no short options match, try completion with long
+ # options.
+ sw, = complete(:long, opt)
+ eq ||= !rest
+ end
+ end
+ rescue ParseError
+ throw :terminate, arg unless raise_unknown
+ raise $!.set_option(arg, true)
+ end
+ begin
+ opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
+ rescue ParseError
+ raise $!.set_option(arg, arg.length > 2)
+ else
+ raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
+ end
+ begin
+ argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
+ val = cb.call(val) if cb
+ setter.call(sw.switch_name, val) if setter
+ rescue ParseError
+ raise $!.set_option(arg, arg.length > 2)
+ end
+
+ # non-option argument
+ else
+ catch(:prune) do
+ visit(:each_option) do |sw0|
+ sw = sw0
+ sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg)
+ end
+ nonopt.call(arg)
+ end
+ end
+ end
+
+ nil
+ }
+
+ visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern}
+
+ argv
+ end
+ private :parse_in_order
+
+ #
+ # Parses command line arguments +argv+ in permutation mode and returns
+ # list of non-option arguments. When optional +into+ keyword
+ # argument is provided, the parsed option values are stored there via
+ # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other
+ # similar object).
+ #
+ def permute(*argv, into: nil)
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
+ permute!(argv, into: into)
+ end
+
+ #
+ # Same as #permute, but removes switches destructively.
+ # Non-option arguments remain in +argv+.
+ #
+ def permute!(argv = default_argv, into: nil)
+ nonopts = []
+ order!(argv, into: into, &nonopts.method(:<<))
+ argv[0, 0] = nonopts
+ argv
+ end
+
+ #
+ # Parses command line arguments +argv+ in order when environment variable
+ # POSIXLY_CORRECT is set, and in permutation mode otherwise.
+ # When optional +into+ keyword argument is provided, the parsed option
+ # values are stored there via <code>[]=</code> method (so it can be Hash,
+ # or OpenStruct, or other similar object).
+ #
+ def parse(*argv, into: nil)
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
+ parse!(argv, into: into)
+ end
+
+ #
+ # Same as #parse, but removes switches destructively.
+ # Non-option arguments remain in +argv+.
+ #
+ def parse!(argv = default_argv, into: nil)
+ if ENV.include?('POSIXLY_CORRECT')
+ order!(argv, into: into)
+ else
+ permute!(argv, into: into)
+ end
+ end
+
+ #
+ # Wrapper method for getopts.rb.
+ #
+ # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option")
+ # # params["a"] = true # -a
+ # # params["b"] = "1" # -b1
+ # # params["foo"] = "1" # --foo
+ # # params["bar"] = "x" # --bar x
+ # # params["zot"] = "z" # --zot Z
+ #
+ # Option +symbolize_names+ (boolean) specifies whether returned Hash keys should be Symbols; defaults to +false+ (use Strings).
+ #
+ # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option", symbolize_names: true)
+ # # params[:a] = true # -a
+ # # params[:b] = "1" # -b1
+ # # params[:foo] = "1" # --foo
+ # # params[:bar] = "x" # --bar x
+ # # params[:zot] = "z" # --zot Z
+ #
+ def getopts(*args, symbolize_names: false)
+ argv = Array === args.first ? args.shift : default_argv
+ single_options, *long_options = *args
+
+ result = {}
+
+ single_options.scan(/(.)(:)?/) do |opt, val|
+ if val
+ result[opt] = nil
+ define("-#{opt} VAL")
+ else
+ result[opt] = false
+ define("-#{opt}")
+ end
+ end if single_options
+
+ long_options.each do |arg|
+ arg, desc = arg.split(';', 2)
+ opt, val = arg.split(':', 2)
+ if val
+ result[opt] = val.empty? ? nil : val
+ define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact)
+ else
+ result[opt] = false
+ define("--#{opt}", *[desc].compact)
+ end
+ end
+
+ parse_in_order(argv, result.method(:[]=))
+ symbolize_names ? result.transform_keys(&:to_sym) : result
+ end
+
+ #
+ # See #getopts.
+ #
+ def self.getopts(*args, symbolize_names: false)
+ new.getopts(*args, symbolize_names: symbolize_names)
+ end
+
+ #
+ # Traverses @stack, sending each element method +id+ with +args+ and
+ # +block+.
+ #
+ def visit(id, *args, &block) # :nodoc:
+ @stack.reverse_each do |el|
+ el.__send__(id, *args, &block)
+ end
+ nil
+ end
+ private :visit
+
+ #
+ # Searches +key+ in @stack for +id+ hash and returns or yields the result.
+ #
+ def search(id, key) # :nodoc:
+ block_given = block_given?
+ visit(:search, id, key) do |k|
+ return block_given ? yield(k) : k
+ end
+ end
+ private :search
+
+ #
+ # Completes shortened long style option switch and returns pair of
+ # canonical switch and switch descriptor Gem::OptionParser::Switch.
+ #
+ # +typ+:: Searching table.
+ # +opt+:: Searching key.
+ # +icase+:: Search case insensitive if true.
+ # +pat+:: Optional pattern for completion.
+ #
+ def complete(typ, opt, icase = false, *pat) # :nodoc:
+ if pat.empty?
+ search(typ, opt) {|sw| return [sw, opt]} # exact match or...
+ end
+ ambiguous = catch(:ambiguous) {
+ visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw}
+ }
+ exc = ambiguous ? AmbiguousOption : InvalidOption
+ raise exc.new(opt, additional: self.method(:additional_message).curry[typ])
+ end
+ private :complete
+
+ #
+ # Returns additional info.
+ #
+ def additional_message(typ, opt)
+ return unless typ and opt and defined?(DidYouMean::SpellChecker)
+ all_candidates = []
+ visit(:get_candidates, typ) do |candidates|
+ all_candidates.concat(candidates)
+ end
+ all_candidates.select! {|cand| cand.is_a?(String) }
+ checker = DidYouMean::SpellChecker.new(dictionary: all_candidates)
+ DidYouMean.formatter.message_for(all_candidates & checker.correct(opt))
+ end
+
+ def candidate(word)
+ list = []
+ case word
+ when '-'
+ long = short = true
+ when /\A--/
+ word, arg = word.split(/=/, 2)
+ argpat = Completion.regexp(arg, false) if arg and !arg.empty?
+ long = true
+ when /\A-/
+ short = true
+ end
+ pat = Completion.regexp(word, long)
+ visit(:each_option) do |opt|
+ next unless Switch === opt
+ opts = (long ? opt.long : []) + (short ? opt.short : [])
+ opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat
+ if /\A=/ =~ opt.arg
+ opts.map! {|sw| sw + "="}
+ if arg and CompletingHash === opt.pattern
+ if opts = opt.pattern.candidate(arg, false, argpat)
+ opts.map!(&:last)
+ end
+ end
+ end
+ list.concat(opts)
+ end
+ list
+ end
+
+ #
+ # Loads options from file names as +filename+. Does nothing when the file
+ # is not present. Returns whether successfully loaded.
+ #
+ # +filename+ defaults to basename of the program without suffix in a
+ # directory ~/.options, then the basename with '.options' suffix
+ # under XDG and Haiku standard places.
+ #
+ # The optional +into+ keyword argument works exactly like that accepted in
+ # method #parse.
+ #
+ def load(filename = nil, into: nil)
+ unless filename
+ basename = File.basename($0, '.*')
+ return true if load(File.expand_path(basename, '~/.options'), into: into) rescue nil
+ basename << ".options"
+ return [
+ # XDG
+ ENV['XDG_CONFIG_HOME'],
+ '~/.config',
+ *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR),
+
+ # Haiku
+ '~/config/settings',
+ ].any? {|dir|
+ next if !dir or dir.empty?
+ load(File.expand_path(basename, dir), into: into) rescue nil
+ }
+ end
+ begin
+ parse(*File.readlines(filename, chomp: true), into: into)
+ true
+ rescue Errno::ENOENT, Errno::ENOTDIR
+ false
+ end
+ end
+
+ #
+ # Parses environment variable +env+ or its uppercase with splitting like a
+ # shell.
+ #
+ # +env+ defaults to the basename of the program.
+ #
+ def environment(env = File.basename($0, '.*'))
+ env = ENV[env] || ENV[env.upcase] or return
+ require 'shellwords'
+ parse(*Shellwords.shellwords(env))
+ end
+
+ #
+ # Acceptable argument classes
+ #
+
+ #
+ # Any string and no conversion. This is fall-back.
+ #
+ accept(Object) {|s,|s or s.nil?}
+
+ accept(NilClass) {|s,|s}
+
+ #
+ # Any non-empty string, and no conversion.
+ #
+ accept(String, /.+/m) {|s,*|s}
+
+ #
+ # Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal
+ # for 0x, and decimal for others; with optional sign prefix. Converts to
+ # Integer.
+ #
+ decimal = '\d+(?:_\d+)*'
+ binary = 'b[01]+(?:_[01]+)*'
+ hex = 'x[\da-f]+(?:_[\da-f]+)*'
+ octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?"
+ integer = "#{octal}|#{decimal}"
+
+ accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,|
+ begin
+ Integer(s)
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Float number format, and converts to Float.
+ #
+ float = "(?:#{decimal}(?=(.)?)(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?"
+ floatpat = %r"\A[-+]?#{float}\z"io
+ accept(Float, floatpat) {|s,| s.to_f if s}
+
+ #
+ # Generic numeric format, converts to Integer for integer format, Float
+ # for float format, and Rational for rational format.
+ #
+ real = "[-+]?(?:#{octal}|#{float})"
+ accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, f, n,|
+ if n
+ Rational(d, n)
+ elsif f
+ Float(s)
+ else
+ Integer(s)
+ end
+ }
+
+ #
+ # Decimal integer format, to be converted to Integer.
+ #
+ DecimalInteger = /\A[-+]?#{decimal}\z/io
+ accept(DecimalInteger, DecimalInteger) {|s,|
+ begin
+ Integer(s, 10)
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Ruby/C like octal/hexadecimal/binary integer format, to be converted to
+ # Integer.
+ #
+ OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io
+ accept(OctalInteger, OctalInteger) {|s,|
+ begin
+ Integer(s, 8)
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Decimal integer/float number format, to be converted to Integer for
+ # integer format, Float for float format.
+ #
+ DecimalNumeric = floatpat # decimal integer is allowed as float also.
+ accept(DecimalNumeric, floatpat) {|s, f|
+ begin
+ if f
+ Float(s)
+ else
+ Integer(s)
+ end
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Boolean switch, which means whether it is present or not, whether it is
+ # absent or not with prefix no-, or it takes an argument
+ # yes/no/true/false/+/-.
+ #
+ yesno = CompletingHash.new
+ %w[- no false].each {|el| yesno[el] = false}
+ %w[+ yes true].each {|el| yesno[el] = true}
+ yesno['nil'] = false # should be nil?
+ accept(TrueClass, yesno) {|arg, val| val == nil or val}
+ #
+ # Similar to TrueClass, but defaults to false.
+ #
+ accept(FalseClass, yesno) {|arg, val| val != nil and val}
+
+ #
+ # List of strings separated by ",".
+ #
+ accept(Array) do |s, |
+ if s
+ s = s.split(',').collect {|ss| ss unless ss.empty?}
+ end
+ s
+ end
+
+ #
+ # Regular expression with options.
+ #
+ accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o|
+ f = 0
+ if o
+ f |= Regexp::IGNORECASE if /i/ =~ o
+ f |= Regexp::MULTILINE if /m/ =~ o
+ f |= Regexp::EXTENDED if /x/ =~ o
+ case o = o.delete("imx")
+ when ""
+ when "u"
+ s = s.encode(Encoding::UTF_8)
+ when "e"
+ s = s.encode(Encoding::EUC_JP)
+ when "s"
+ s = s.encode(Encoding::SJIS)
+ when "n"
+ f |= Regexp::NOENCODING
+ else
+ raise Gem::OptionParser::InvalidArgument, "unknown regexp option - #{o}"
+ end
+ else
+ s ||= all
+ end
+ Regexp.new(s, f)
+ end
+
+ #
+ # Exceptions
+ #
+
+ #
+ # Base class of exceptions from Gem::OptionParser.
+ #
+ class ParseError < RuntimeError
+ # Reason which caused the error.
+ Reason = 'parse error'
+
+ def initialize(*args, additional: nil)
+ @additional = additional
+ @arg0, = args
+ @args = args
+ @reason = nil
+ end
+
+ attr_reader :args
+ attr_writer :reason
+ attr_accessor :additional
+
+ #
+ # Pushes back erred argument(s) to +argv+.
+ #
+ def recover(argv)
+ argv[0, 0] = @args
+ argv
+ end
+
+ def self.filter_backtrace(array)
+ unless $DEBUG
+ array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~))
+ end
+ array
+ end
+
+ def set_backtrace(array)
+ super(self.class.filter_backtrace(array))
+ end
+
+ def set_option(opt, eq)
+ if eq
+ @args[0] = opt
+ else
+ @args.unshift(opt)
+ end
+ self
+ end
+
+ #
+ # Returns error reason. Override this for I18N.
+ #
+ def reason
+ @reason || self.class::Reason
+ end
+
+ def inspect
+ "#<#{self.class}: #{args.join(' ')}>"
+ end
+
+ #
+ # Default stringizing method to emit standard error message.
+ #
+ def message
+ "#{reason}: #{args.join(' ')}#{additional[@arg0] if additional}"
+ end
+
+ alias to_s message
+ end
+
+ #
+ # Raises when ambiguously completable string is encountered.
+ #
+ class AmbiguousOption < ParseError
+ const_set(:Reason, 'ambiguous option')
+ end
+
+ #
+ # Raises when there is an argument for a switch which takes no argument.
+ #
+ class NeedlessArgument < ParseError
+ const_set(:Reason, 'needless argument')
+ end
+
+ #
+ # Raises when a switch with mandatory argument has no argument.
+ #
+ class MissingArgument < ParseError
+ const_set(:Reason, 'missing argument')
+ end
+
+ #
+ # Raises when switch is undefined.
+ #
+ class InvalidOption < ParseError
+ const_set(:Reason, 'invalid option')
+ end
+
+ #
+ # Raises when the given argument does not match required format.
+ #
+ class InvalidArgument < ParseError
+ const_set(:Reason, 'invalid argument')
+ end
+
+ #
+ # Raises when the given argument word can't be completed uniquely.
+ #
+ class AmbiguousArgument < InvalidArgument
+ const_set(:Reason, 'ambiguous argument')
+ end
+
+ #
+ # Miscellaneous
+ #
+
+ #
+ # Extends command line arguments array (ARGV) to parse itself.
+ #
+ module Arguable
+
+ #
+ # Sets Gem::OptionParser object, when +opt+ is +false+ or +nil+, methods
+ # Gem::OptionParser::Arguable#options and Gem::OptionParser::Arguable#options= are
+ # undefined. Thus, there is no ways to access the Gem::OptionParser object
+ # via the receiver object.
+ #
+ def options=(opt)
+ unless @optparse = opt
+ class << self
+ undef_method(:options)
+ undef_method(:options=)
+ end
+ end
+ end
+
+ #
+ # Actual Gem::OptionParser object, automatically created if nonexistent.
+ #
+ # If called with a block, yields the Gem::OptionParser object and returns the
+ # result of the block. If an Gem::OptionParser::ParseError exception occurs
+ # in the block, it is rescued, a error message printed to STDERR and
+ # +nil+ returned.
+ #
+ def options
+ @optparse ||= Gem::OptionParser.new
+ @optparse.default_argv = self
+ block_given? or return @optparse
+ begin
+ yield @optparse
+ rescue ParseError
+ @optparse.warn $!
+ nil
+ end
+ end
+
+ #
+ # Parses +self+ destructively in order and returns +self+ containing the
+ # rest arguments left unparsed.
+ #
+ def order!(&blk) options.order!(self, &blk) end
+
+ #
+ # Parses +self+ destructively in permutation mode and returns +self+
+ # containing the rest arguments left unparsed.
+ #
+ def permute!() options.permute!(self) end
+
+ #
+ # Parses +self+ destructively and returns +self+ containing the
+ # rest arguments left unparsed.
+ #
+ def parse!() options.parse!(self) end
+
+ #
+ # Substitution of getopts is possible as follows. Also see
+ # Gem::OptionParser#getopts.
+ #
+ # def getopts(*args)
+ # ($OPT = ARGV.getopts(*args)).each do |opt, val|
+ # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val"
+ # end
+ # rescue Gem::OptionParser::ParseError
+ # end
+ #
+ def getopts(*args, symbolize_names: false)
+ options.getopts(self, *args, symbolize_names: symbolize_names)
+ end
+
+ #
+ # Initializes instance variable.
+ #
+ def self.extend_object(obj)
+ super
+ obj.instance_eval {@optparse = nil}
+ end
+ def initialize(*args)
+ super
+ @optparse = nil
+ end
+ end
+
+ #
+ # Acceptable argument classes. Now contains DecimalInteger, OctalInteger
+ # and DecimalNumeric. See Acceptable argument classes (in source code).
+ #
+ module Acceptables
+ const_set(:DecimalInteger, Gem::OptionParser::DecimalInteger)
+ const_set(:OctalInteger, Gem::OptionParser::OctalInteger)
+ const_set(:DecimalNumeric, Gem::OptionParser::DecimalNumeric)
+ end
+end
+
+# ARGV is arguable by Gem::OptionParser
+ARGV.extend(Gem::OptionParser::Arguable)
diff --git a/lib/rubygems/vendor/optparse/lib/optparse/ac.rb b/lib/rubygems/vendor/optparse/lib/optparse/ac.rb
new file mode 100644
index 0000000000..e84d01bf91
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse/ac.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: false
+require_relative '../optparse'
+
+class Gem::OptionParser::AC < Gem::OptionParser
+ private
+
+ def _check_ac_args(name, block)
+ unless /\A\w[-\w]*\z/ =~ name
+ raise ArgumentError, name
+ end
+ unless block
+ raise ArgumentError, "no block given", ParseError.filter_backtrace(caller)
+ end
+ end
+
+ ARG_CONV = proc {|val| val.nil? ? true : val}
+
+ def _ac_arg_enable(prefix, name, help_string, block)
+ _check_ac_args(name, block)
+
+ sdesc = []
+ ldesc = ["--#{prefix}-#{name}"]
+ desc = [help_string]
+ q = name.downcase
+ ac_block = proc {|val| block.call(ARG_CONV.call(val))}
+ enable = Switch::PlacedArgument.new(nil, ARG_CONV, sdesc, ldesc, nil, desc, ac_block)
+ disable = Switch::NoArgument.new(nil, proc {false}, sdesc, ldesc, nil, desc, ac_block)
+ top.append(enable, [], ["enable-" + q], disable, ['disable-' + q])
+ enable
+ end
+
+ public
+
+ def ac_arg_enable(name, help_string, &block)
+ _ac_arg_enable("enable", name, help_string, block)
+ end
+
+ def ac_arg_disable(name, help_string, &block)
+ _ac_arg_enable("disable", name, help_string, block)
+ end
+
+ def ac_arg_with(name, help_string, &block)
+ _check_ac_args(name, block)
+
+ sdesc = []
+ ldesc = ["--with-#{name}"]
+ desc = [help_string]
+ q = name.downcase
+ with = Switch::PlacedArgument.new(*search(:atype, String), sdesc, ldesc, nil, desc, block)
+ without = Switch::NoArgument.new(nil, proc {}, sdesc, ldesc, nil, desc, block)
+ top.append(with, [], ["with-" + q], without, ['without-' + q])
+ with
+ end
+end
diff --git a/lib/rubygems/vendor/optparse/lib/optparse/date.rb b/lib/rubygems/vendor/optparse/lib/optparse/date.rb
new file mode 100644
index 0000000000..d9a9f4f48a
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse/date.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: false
+require_relative '../optparse'
+require 'date'
+
+Gem::OptionParser.accept(DateTime) do |s,|
+ begin
+ DateTime.parse(s) if s
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end
+end
+Gem::OptionParser.accept(Date) do |s,|
+ begin
+ Date.parse(s) if s
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end
+end
diff --git a/lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb b/lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb
new file mode 100644
index 0000000000..6987a5ed62
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+require_relative '../optparse'
+
+class Gem::OptionParser
+ # :call-seq:
+ # define_by_keywords(options, method, **params)
+ #
+ # :include: ../../doc/optparse/creates_option.rdoc
+ #
+ def define_by_keywords(options, meth, **opts)
+ meth.parameters.each do |type, name|
+ case type
+ when :key, :keyreq
+ op, cl = *(type == :key ? %w"[ ]" : ["", ""])
+ define("--#{name}=#{op}#{name.upcase}#{cl}", *opts[name]) do |o|
+ options[name] = o
+ end
+ end
+ end
+ options
+ end
+end
diff --git a/lib/rubygems/vendor/optparse/lib/optparse/shellwords.rb b/lib/rubygems/vendor/optparse/lib/optparse/shellwords.rb
new file mode 100644
index 0000000000..d47ad60255
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse/shellwords.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: false
+# -*- ruby -*-
+
+require 'shellwords'
+require_relative '../optparse'
+
+Gem::OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)}
diff --git a/lib/rubygems/vendor/optparse/lib/optparse/time.rb b/lib/rubygems/vendor/optparse/lib/optparse/time.rb
new file mode 100644
index 0000000000..c59e1e4ced
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse/time.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: false
+require_relative '../optparse'
+require 'time'
+
+Gem::OptionParser.accept(Time) do |s,|
+ begin
+ (Time.httpdate(s) rescue Time.parse(s)) if s
+ rescue
+ raise Gem::OptionParser::InvalidArgument, s
+ end
+end
diff --git a/lib/rubygems/vendor/optparse/lib/optparse/uri.rb b/lib/rubygems/vendor/optparse/lib/optparse/uri.rb
new file mode 100644
index 0000000000..398127479a
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse/uri.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: false
+# -*- ruby -*-
+
+require_relative '../optparse'
+require_relative '../../../uri/lib/uri'
+
+Gem::OptionParser.accept(Gem::URI) {|s,| Gem::URI.parse(s) if s}
diff --git a/lib/rubygems/vendor/optparse/lib/optparse/version.rb b/lib/rubygems/vendor/optparse/lib/optparse/version.rb
new file mode 100644
index 0000000000..5d79e9db44
--- /dev/null
+++ b/lib/rubygems/vendor/optparse/lib/optparse/version.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: false
+# Gem::OptionParser internal utility
+
+class << Gem::OptionParser
+ def show_version(*pkgs)
+ progname = ARGV.options.program_name
+ result = false
+ show = proc do |klass, cname, version|
+ str = "#{progname}"
+ unless klass == ::Object and cname == :VERSION
+ version = version.join(".") if Array === version
+ str << ": #{klass}" unless klass == Object
+ str << " version #{version}"
+ end
+ [:Release, :RELEASE].find do |rel|
+ if klass.const_defined?(rel)
+ str << " (#{klass.const_get(rel)})"
+ end
+ end
+ puts str
+ result = true
+ end
+ if pkgs.size == 1 and pkgs[0] == "all"
+ self.search_const(::Object, /\AV(?:ERSION|ersion)\z/) do |klass, cname, version|
+ unless cname[1] == ?e and klass.const_defined?(:Version)
+ show.call(klass, cname.intern, version)
+ end
+ end
+ else
+ pkgs.each do |pkg|
+ begin
+ pkg = pkg.split(/::|\//).inject(::Object) {|m, c| m.const_get(c)}
+ v = case
+ when pkg.const_defined?(:Version)
+ pkg.const_get(n = :Version)
+ when pkg.const_defined?(:VERSION)
+ pkg.const_get(n = :VERSION)
+ else
+ n = nil
+ "unknown"
+ end
+ show.call(pkg, n, v)
+ rescue NameError
+ end
+ end
+ end
+ result
+ end
+
+ def each_const(path, base = ::Object)
+ path.split(/::|\//).inject(base) do |klass, name|
+ raise NameError, path unless Module === klass
+ klass.constants.grep(/#{name}/i) do |c|
+ klass.const_defined?(c) or next
+ klass.const_get(c)
+ end
+ end
+ end
+
+ def search_const(klass, name)
+ klasses = [klass]
+ while klass = klasses.shift
+ klass.constants.each do |cname|
+ klass.const_defined?(cname) or next
+ const = klass.const_get(cname)
+ yield klass, cname, const if name === cname
+ klasses << const if Module === const and const != ::Object
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/vendor/resolv/.document b/lib/rubygems/vendor/resolv/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/resolv/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb
new file mode 100644
index 0000000000..ac0ba0b313
--- /dev/null
+++ b/lib/rubygems/vendor/resolv/lib/resolv.rb
@@ -0,0 +1,3442 @@
+# frozen_string_literal: true
+
+require 'socket'
+require_relative '../../timeout/lib/timeout'
+require 'io/wait'
+
+begin
+ require 'securerandom'
+rescue LoadError
+end
+
+# Gem::Resolv is a thread-aware DNS resolver library written in Ruby. Gem::Resolv can
+# handle multiple DNS requests concurrently without blocking the entire Ruby
+# interpreter.
+#
+# See also resolv-replace.rb to replace the libc resolver with Gem::Resolv.
+#
+# Gem::Resolv can look up various DNS resources using the DNS module directly.
+#
+# Examples:
+#
+# p Gem::Resolv.getaddress "www.ruby-lang.org"
+# p Gem::Resolv.getname "210.251.121.214"
+#
+# Gem::Resolv::DNS.open do |dns|
+# ress = dns.getresources "www.ruby-lang.org", Gem::Resolv::DNS::Resource::IN::A
+# p ress.map(&:address)
+# ress = dns.getresources "ruby-lang.org", Gem::Resolv::DNS::Resource::IN::MX
+# p ress.map { |r| [r.exchange.to_s, r.preference] }
+# end
+#
+#
+# == Bugs
+#
+# * NIS is not supported.
+# * /etc/nsswitch.conf is not supported.
+
+class Gem::Resolv
+
+ VERSION = "0.4.0"
+
+ ##
+ # Looks up the first IP address for +name+.
+
+ def self.getaddress(name)
+ DefaultResolver.getaddress(name)
+ end
+
+ ##
+ # Looks up all IP address for +name+.
+
+ def self.getaddresses(name)
+ DefaultResolver.getaddresses(name)
+ end
+
+ ##
+ # Iterates over all IP addresses for +name+.
+
+ def self.each_address(name, &block)
+ DefaultResolver.each_address(name, &block)
+ end
+
+ ##
+ # Looks up the hostname of +address+.
+
+ def self.getname(address)
+ DefaultResolver.getname(address)
+ end
+
+ ##
+ # Looks up all hostnames for +address+.
+
+ def self.getnames(address)
+ DefaultResolver.getnames(address)
+ end
+
+ ##
+ # Iterates over all hostnames for +address+.
+
+ def self.each_name(address, &proc)
+ DefaultResolver.each_name(address, &proc)
+ end
+
+ ##
+ # Creates a new Gem::Resolv using +resolvers+.
+
+ def initialize(resolvers=nil, use_ipv6: nil)
+ @resolvers = resolvers || [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(use_ipv6: use_ipv6))]
+ end
+
+ ##
+ # Looks up the first IP address for +name+.
+
+ def getaddress(name)
+ each_address(name) {|address| return address}
+ raise ResolvError.new("no address for #{name}")
+ end
+
+ ##
+ # Looks up all IP address for +name+.
+
+ def getaddresses(name)
+ ret = []
+ each_address(name) {|address| ret << address}
+ return ret
+ end
+
+ ##
+ # Iterates over all IP addresses for +name+.
+
+ def each_address(name)
+ if AddressRegex =~ name
+ yield name
+ return
+ end
+ yielded = false
+ @resolvers.each {|r|
+ r.each_address(name) {|address|
+ yield address.to_s
+ yielded = true
+ }
+ return if yielded
+ }
+ end
+
+ ##
+ # Looks up the hostname of +address+.
+
+ def getname(address)
+ each_name(address) {|name| return name}
+ raise ResolvError.new("no name for #{address}")
+ end
+
+ ##
+ # Looks up all hostnames for +address+.
+
+ def getnames(address)
+ ret = []
+ each_name(address) {|name| ret << name}
+ return ret
+ end
+
+ ##
+ # Iterates over all hostnames for +address+.
+
+ def each_name(address)
+ yielded = false
+ @resolvers.each {|r|
+ r.each_name(address) {|name|
+ yield name.to_s
+ yielded = true
+ }
+ return if yielded
+ }
+ end
+
+ ##
+ # Indicates a failure to resolve a name or address.
+
+ class ResolvError < StandardError; end
+
+ ##
+ # Indicates a timeout resolving a name or address.
+
+ class ResolvTimeout < Gem::Timeout::Error; end
+
+ ##
+ # Gem::Resolv::Hosts is a hostname resolver that uses the system hosts file.
+
+ class Hosts
+ if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and
+ begin
+ require 'win32/resolv'
+ DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
+ rescue LoadError
+ end
+ end
+ DefaultFileName ||= '/etc/hosts'
+
+ ##
+ # Creates a new Gem::Resolv::Hosts, using +filename+ for its data source.
+
+ def initialize(filename = DefaultFileName)
+ @filename = filename
+ @mutex = Thread::Mutex.new
+ @initialized = nil
+ end
+
+ def lazy_initialize # :nodoc:
+ @mutex.synchronize {
+ unless @initialized
+ @name2addr = {}
+ @addr2name = {}
+ File.open(@filename, 'rb') {|f|
+ f.each {|line|
+ line.sub!(/#.*/, '')
+ addr, *hostnames = line.split(/\s+/)
+ next unless addr
+ (@addr2name[addr] ||= []).concat(hostnames)
+ hostnames.each {|hostname| (@name2addr[hostname] ||= []) << addr}
+ }
+ }
+ @name2addr.each {|name, arr| arr.reverse!}
+ @initialized = true
+ end
+ }
+ self
+ end
+
+ ##
+ # Gets the IP address of +name+ from the hosts file.
+
+ def getaddress(name)
+ each_address(name) {|address| return address}
+ raise ResolvError.new("#{@filename} has no name: #{name}")
+ end
+
+ ##
+ # Gets all IP addresses for +name+ from the hosts file.
+
+ def getaddresses(name)
+ ret = []
+ each_address(name) {|address| ret << address}
+ return ret
+ end
+
+ ##
+ # Iterates over all IP addresses for +name+ retrieved from the hosts file.
+
+ def each_address(name, &proc)
+ lazy_initialize
+ @name2addr[name]&.each(&proc)
+ end
+
+ ##
+ # Gets the hostname of +address+ from the hosts file.
+
+ def getname(address)
+ each_name(address) {|name| return name}
+ raise ResolvError.new("#{@filename} has no address: #{address}")
+ end
+
+ ##
+ # Gets all hostnames for +address+ from the hosts file.
+
+ def getnames(address)
+ ret = []
+ each_name(address) {|name| ret << name}
+ return ret
+ end
+
+ ##
+ # Iterates over all hostnames for +address+ retrieved from the hosts file.
+
+ def each_name(address, &proc)
+ lazy_initialize
+ @addr2name[address]&.each(&proc)
+ end
+ end
+
+ ##
+ # Gem::Resolv::DNS is a DNS stub resolver.
+ #
+ # Information taken from the following places:
+ #
+ # * STD0013
+ # * RFC 1035
+ # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
+ # * etc.
+
+ class DNS
+
+ ##
+ # Default DNS Port
+
+ Port = 53
+
+ ##
+ # Default DNS UDP packet size
+
+ UDPSize = 512
+
+ ##
+ # Creates a new DNS resolver. See Gem::Resolv::DNS.new for argument details.
+ #
+ # Yields the created DNS resolver to the block, if given, otherwise
+ # returns it.
+
+ def self.open(*args)
+ dns = new(*args)
+ return dns unless block_given?
+ begin
+ yield dns
+ ensure
+ dns.close
+ end
+ end
+
+ ##
+ # Creates a new DNS resolver.
+ #
+ # +config_info+ can be:
+ #
+ # nil:: Uses /etc/resolv.conf.
+ # String:: Path to a file using /etc/resolv.conf's format.
+ # Hash:: Must contain :nameserver, :search and :ndots keys.
+ # :nameserver_port can be used to specify port number of nameserver address.
+ # :raise_timeout_errors can be used to raise timeout errors
+ # as exceptions instead of treating the same as an NXDOMAIN response.
+ #
+ # The value of :nameserver should be an address string or
+ # an array of address strings.
+ # - :nameserver => '8.8.8.8'
+ # - :nameserver => ['8.8.8.8', '8.8.4.4']
+ #
+ # The value of :nameserver_port should be an array of
+ # pair of nameserver address and port number.
+ # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]]
+ #
+ # Example:
+ #
+ # Gem::Resolv::DNS.new(:nameserver => ['210.251.121.21'],
+ # :search => ['ruby-lang.org'],
+ # :ndots => 1)
+
+ def initialize(config_info=nil)
+ @mutex = Thread::Mutex.new
+ @config = Config.new(config_info)
+ @initialized = nil
+ end
+
+ # Sets the resolver timeouts. This may be a single positive number
+ # or an array of positive numbers representing timeouts in seconds.
+ # If an array is specified, a DNS request will retry and wait for
+ # each successive interval in the array until a successful response
+ # is received. Specifying +nil+ reverts to the default timeouts:
+ # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ]
+ #
+ # Example:
+ #
+ # dns.timeouts = 3
+ #
+ def timeouts=(values)
+ @config.timeouts = values
+ end
+
+ def lazy_initialize # :nodoc:
+ @mutex.synchronize {
+ unless @initialized
+ @config.lazy_initialize
+ @initialized = true
+ end
+ }
+ self
+ end
+
+ ##
+ # Closes the DNS resolver.
+
+ def close
+ @mutex.synchronize {
+ if @initialized
+ @initialized = false
+ end
+ }
+ end
+
+ ##
+ # Gets the IP address of +name+ from the DNS resolver.
+ #
+ # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved address will
+ # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+
+ def getaddress(name)
+ each_address(name) {|address| return address}
+ raise ResolvError.new("DNS result has no information for #{name}")
+ end
+
+ ##
+ # Gets all IP addresses for +name+ from the DNS resolver.
+ #
+ # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will
+ # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+
+ def getaddresses(name)
+ ret = []
+ each_address(name) {|address| ret << address}
+ return ret
+ end
+
+ ##
+ # Iterates over all IP addresses for +name+ retrieved from the DNS
+ # resolver.
+ #
+ # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will
+ # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+
+ def each_address(name)
+ each_resource(name, Resource::IN::A) {|resource| yield resource.address}
+ if use_ipv6?
+ each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
+ end
+ end
+
+ def use_ipv6? # :nodoc:
+ use_ipv6 = @config.use_ipv6?
+ unless use_ipv6.nil?
+ return use_ipv6
+ end
+
+ begin
+ list = Socket.ip_address_list
+ rescue NotImplementedError
+ return true
+ end
+ list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
+ end
+ private :use_ipv6?
+
+ ##
+ # Gets the hostname for +address+ from the DNS resolver.
+ #
+ # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String. Retrieved
+ # name will be a Gem::Resolv::DNS::Name.
+
+ def getname(address)
+ each_name(address) {|name| return name}
+ raise ResolvError.new("DNS result has no information for #{address}")
+ end
+
+ ##
+ # Gets all hostnames for +address+ from the DNS resolver.
+ #
+ # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String. Retrieved
+ # names will be Gem::Resolv::DNS::Name instances.
+
+ def getnames(address)
+ ret = []
+ each_name(address) {|name| ret << name}
+ return ret
+ end
+
+ ##
+ # Iterates over all hostnames for +address+ retrieved from the DNS
+ # resolver.
+ #
+ # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String. Retrieved
+ # names will be Gem::Resolv::DNS::Name instances.
+
+ def each_name(address)
+ case address
+ when Name
+ ptr = address
+ when IPv4, IPv6
+ ptr = address.to_name
+ when IPv4::Regex
+ ptr = IPv4.create(address).to_name
+ when IPv6::Regex
+ ptr = IPv6.create(address).to_name
+ else
+ raise ResolvError.new("cannot interpret as address: #{address}")
+ end
+ each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
+ end
+
+ ##
+ # Look up the +typeclass+ DNS resource of +name+.
+ #
+ # +name+ must be a Gem::Resolv::DNS::Name or a String.
+ #
+ # +typeclass+ should be one of the following:
+ #
+ # * Gem::Resolv::DNS::Resource::IN::A
+ # * Gem::Resolv::DNS::Resource::IN::AAAA
+ # * Gem::Resolv::DNS::Resource::IN::ANY
+ # * Gem::Resolv::DNS::Resource::IN::CNAME
+ # * Gem::Resolv::DNS::Resource::IN::HINFO
+ # * Gem::Resolv::DNS::Resource::IN::MINFO
+ # * Gem::Resolv::DNS::Resource::IN::MX
+ # * Gem::Resolv::DNS::Resource::IN::NS
+ # * Gem::Resolv::DNS::Resource::IN::PTR
+ # * Gem::Resolv::DNS::Resource::IN::SOA
+ # * Gem::Resolv::DNS::Resource::IN::TXT
+ # * Gem::Resolv::DNS::Resource::IN::WKS
+ #
+ # Returned resource is represented as a Gem::Resolv::DNS::Resource instance,
+ # i.e. Gem::Resolv::DNS::Resource::IN::A.
+
+ def getresource(name, typeclass)
+ each_resource(name, typeclass) {|resource| return resource}
+ raise ResolvError.new("DNS result has no information for #{name}")
+ end
+
+ ##
+ # Looks up all +typeclass+ DNS resources for +name+. See #getresource for
+ # argument details.
+
+ def getresources(name, typeclass)
+ ret = []
+ each_resource(name, typeclass) {|resource| ret << resource}
+ return ret
+ end
+
+ ##
+ # Iterates over all +typeclass+ DNS resources for +name+. See
+ # #getresource for argument details.
+
+ def each_resource(name, typeclass, &proc)
+ fetch_resource(name, typeclass) {|reply, reply_name|
+ extract_resources(reply, reply_name, typeclass, &proc)
+ }
+ end
+
+ def fetch_resource(name, typeclass)
+ lazy_initialize
+ begin
+ requester = make_udp_requester
+ rescue Errno::EACCES
+ # fall back to TCP
+ end
+ senders = {}
+ begin
+ @config.resolv(name) {|candidate, tout, nameserver, port|
+ requester ||= make_tcp_requester(nameserver, port)
+ msg = Message.new
+ msg.rd = 1
+ msg.add_question(candidate, typeclass)
+ unless sender = senders[[candidate, nameserver, port]]
+ sender = requester.sender(msg, candidate, nameserver, port)
+ next if !sender
+ senders[[candidate, nameserver, port]] = sender
+ end
+ reply, reply_name = requester.request(sender, tout)
+ case reply.rcode
+ when RCode::NoError
+ if reply.tc == 1 and not Requester::TCP === requester
+ requester.close
+ # Retry via TCP:
+ requester = make_tcp_requester(nameserver, port)
+ senders = {}
+ # This will use TCP for all remaining candidates (assuming the
+ # current candidate does not already respond successfully via
+ # TCP). This makes sense because we already know the full
+ # response will not fit in an untruncated UDP packet.
+ redo
+ else
+ yield(reply, reply_name)
+ end
+ return
+ when RCode::NXDomain
+ raise Config::NXDomain.new(reply_name.to_s)
+ else
+ raise Config::OtherResolvError.new(reply_name.to_s)
+ end
+ }
+ ensure
+ requester&.close
+ end
+ end
+
+ def make_udp_requester # :nodoc:
+ nameserver_port = @config.nameserver_port
+ if nameserver_port.length == 1
+ Requester::ConnectedUDP.new(*nameserver_port[0])
+ else
+ Requester::UnconnectedUDP.new(*nameserver_port)
+ end
+ end
+
+ def make_tcp_requester(host, port) # :nodoc:
+ return Requester::TCP.new(host, port)
+ end
+
+ def extract_resources(msg, name, typeclass) # :nodoc:
+ if typeclass < Resource::ANY
+ n0 = Name.create(name)
+ msg.each_resource {|n, ttl, data|
+ yield data if n0 == n
+ }
+ end
+ yielded = false
+ n0 = Name.create(name)
+ msg.each_resource {|n, ttl, data|
+ if n0 == n
+ case data
+ when typeclass
+ yield data
+ yielded = true
+ when Resource::CNAME
+ n0 = data.name
+ end
+ end
+ }
+ return if yielded
+ msg.each_resource {|n, ttl, data|
+ if n0 == n
+ case data
+ when typeclass
+ yield data
+ end
+ end
+ }
+ end
+
+ if defined? SecureRandom
+ def self.random(arg) # :nodoc:
+ begin
+ SecureRandom.random_number(arg)
+ rescue NotImplementedError
+ rand(arg)
+ end
+ end
+ else
+ def self.random(arg) # :nodoc:
+ rand(arg)
+ end
+ end
+
+ RequestID = {} # :nodoc:
+ RequestIDMutex = Thread::Mutex.new # :nodoc:
+
+ def self.allocate_request_id(host, port) # :nodoc:
+ id = nil
+ RequestIDMutex.synchronize {
+ h = (RequestID[[host, port]] ||= {})
+ begin
+ id = random(0x0000..0xffff)
+ end while h[id]
+ h[id] = true
+ }
+ id
+ end
+
+ def self.free_request_id(host, port, id) # :nodoc:
+ RequestIDMutex.synchronize {
+ key = [host, port]
+ if h = RequestID[key]
+ h.delete id
+ if h.empty?
+ RequestID.delete key
+ end
+ end
+ }
+ end
+
+ def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
+ begin
+ port = random(1024..65535)
+ udpsock.bind(bind_host, port)
+ rescue Errno::EADDRINUSE, # POSIX
+ Errno::EACCES, # SunOS: See PRIV_SYS_NFS in privileges(5)
+ Errno::EPERM # FreeBSD: security.mac.portacl.port_high is configurable. See mac_portacl(4).
+ retry
+ end
+ end
+
+ class Requester # :nodoc:
+ def initialize
+ @senders = {}
+ @socks = nil
+ end
+
+ def request(sender, tout)
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ timelimit = start + tout
+ begin
+ sender.send
+ rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
+ Errno::ENETUNREACH
+ raise ResolvTimeout
+ end
+ while true
+ before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ timeout = timelimit - before_select
+ if timeout <= 0
+ raise ResolvTimeout
+ end
+ if @socks.size == 1
+ select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil
+ else
+ select_result = IO.select(@socks, nil, nil, timeout)
+ end
+ if !select_result
+ after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ next if after_select < timelimit
+ raise ResolvTimeout
+ end
+ begin
+ reply, from = recv_reply(select_result[0])
+ rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
+ Errno::ECONNRESET # Windows
+ # No name server running on the server?
+ # Don't wait anymore.
+ raise ResolvTimeout
+ end
+ begin
+ msg = Message.decode(reply)
+ rescue DecodeError
+ next # broken DNS message ignored
+ end
+ if sender == sender_for(from, msg)
+ break
+ else
+ # unexpected DNS message ignored
+ end
+ end
+ return msg, sender.data
+ end
+
+ def sender_for(addr, msg)
+ @senders[[addr,msg.id]]
+ end
+
+ def close
+ socks = @socks
+ @socks = nil
+ socks&.each(&:close)
+ end
+
+ class Sender # :nodoc:
+ def initialize(msg, data, sock)
+ @msg = msg
+ @data = data
+ @sock = sock
+ end
+ end
+
+ class UnconnectedUDP < Requester # :nodoc:
+ def initialize(*nameserver_port)
+ super()
+ @nameserver_port = nameserver_port
+ @initialized = false
+ @mutex = Thread::Mutex.new
+ end
+
+ def lazy_initialize
+ @mutex.synchronize {
+ next if @initialized
+ @initialized = true
+ @socks_hash = {}
+ @socks = []
+ @nameserver_port.each {|host, port|
+ if host.index(':')
+ bind_host = "::"
+ af = Socket::AF_INET6
+ else
+ bind_host = "0.0.0.0"
+ af = Socket::AF_INET
+ end
+ next if @socks_hash[bind_host]
+ begin
+ sock = UDPSocket.new(af)
+ rescue Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT
+ next # The kernel doesn't support the address family.
+ end
+ @socks << sock
+ @socks_hash[bind_host] = sock
+ sock.do_not_reverse_lookup = true
+ DNS.bind_random_port(sock, bind_host)
+ }
+ }
+ self
+ end
+
+ def recv_reply(readable_socks)
+ lazy_initialize
+ reply, from = readable_socks[0].recvfrom(UDPSize)
+ return reply, [from[3],from[1]]
+ end
+
+ def sender(msg, data, host, port=Port)
+ host = Addrinfo.ip(host).ip_address
+ lazy_initialize
+ sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
+ return nil if !sock
+ service = [host, port]
+ id = DNS.allocate_request_id(host, port)
+ request = msg.encode
+ request[0,2] = [id].pack('n')
+ return @senders[[service, id]] =
+ Sender.new(request, data, sock, host, port)
+ end
+
+ def close
+ @mutex.synchronize {
+ if @initialized
+ super
+ @senders.each_key {|service, id|
+ DNS.free_request_id(service[0], service[1], id)
+ }
+ @initialized = false
+ end
+ }
+ end
+
+ class Sender < Requester::Sender # :nodoc:
+ def initialize(msg, data, sock, host, port)
+ super(msg, data, sock)
+ @host = host
+ @port = port
+ end
+ attr_reader :data
+
+ def send
+ raise "@sock is nil." if @sock.nil?
+ @sock.send(@msg, 0, @host, @port)
+ end
+ end
+ end
+
+ class ConnectedUDP < Requester # :nodoc:
+ def initialize(host, port=Port)
+ super()
+ @host = host
+ @port = port
+ @mutex = Thread::Mutex.new
+ @initialized = false
+ end
+
+ def lazy_initialize
+ @mutex.synchronize {
+ next if @initialized
+ @initialized = true
+ is_ipv6 = @host.index(':')
+ sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
+ @socks = [sock]
+ sock.do_not_reverse_lookup = true
+ DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
+ sock.connect(@host, @port)
+ }
+ self
+ end
+
+ def recv_reply(readable_socks)
+ lazy_initialize
+ reply = readable_socks[0].recv(UDPSize)
+ return reply, nil
+ end
+
+ def sender(msg, data, host=@host, port=@port)
+ lazy_initialize
+ unless host == @host && port == @port
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
+ end
+ id = DNS.allocate_request_id(@host, @port)
+ request = msg.encode
+ request[0,2] = [id].pack('n')
+ return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
+ end
+
+ def close
+ @mutex.synchronize do
+ if @initialized
+ super
+ @senders.each_key {|from, id|
+ DNS.free_request_id(@host, @port, id)
+ }
+ @initialized = false
+ end
+ end
+ end
+
+ class Sender < Requester::Sender # :nodoc:
+ def send
+ raise "@sock is nil." if @sock.nil?
+ @sock.send(@msg, 0)
+ end
+ attr_reader :data
+ end
+ end
+
+ class MDNSOneShot < UnconnectedUDP # :nodoc:
+ def sender(msg, data, host, port=Port)
+ lazy_initialize
+ id = DNS.allocate_request_id(host, port)
+ request = msg.encode
+ request[0,2] = [id].pack('n')
+ sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
+ return @senders[id] =
+ UnconnectedUDP::Sender.new(request, data, sock, host, port)
+ end
+
+ def sender_for(addr, msg)
+ lazy_initialize
+ @senders[msg.id]
+ end
+ end
+
+ class TCP < Requester # :nodoc:
+ def initialize(host, port=Port)
+ super()
+ @host = host
+ @port = port
+ sock = TCPSocket.new(@host, @port)
+ @socks = [sock]
+ @senders = {}
+ end
+
+ def recv_reply(readable_socks)
+ len = readable_socks[0].read(2).unpack('n')[0]
+ reply = @socks[0].read(len)
+ return reply, nil
+ end
+
+ def sender(msg, data, host=@host, port=@port)
+ unless host == @host && port == @port
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
+ end
+ id = DNS.allocate_request_id(@host, @port)
+ request = msg.encode
+ request[0,2] = [request.length, id].pack('nn')
+ return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
+ end
+
+ class Sender < Requester::Sender # :nodoc:
+ def send
+ @sock.print(@msg)
+ @sock.flush
+ end
+ attr_reader :data
+ end
+
+ def close
+ super
+ @senders.each_key {|from,id|
+ DNS.free_request_id(@host, @port, id)
+ }
+ end
+ end
+
+ ##
+ # Indicates a problem with the DNS request.
+
+ class RequestError < StandardError
+ end
+ end
+
+ class Config # :nodoc:
+ def initialize(config_info=nil)
+ @mutex = Thread::Mutex.new
+ @config_info = config_info
+ @initialized = nil
+ @timeouts = nil
+ end
+
+ def timeouts=(values)
+ if values
+ values = Array(values)
+ values.each do |t|
+ Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric"
+ t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive"
+ end
+ @timeouts = values
+ else
+ @timeouts = nil
+ end
+ end
+
+ def Config.parse_resolv_conf(filename)
+ nameserver = []
+ search = nil
+ ndots = 1
+ File.open(filename, 'rb') {|f|
+ f.each {|line|
+ line.sub!(/[#;].*/, '')
+ keyword, *args = line.split(/\s+/)
+ next unless keyword
+ case keyword
+ when 'nameserver'
+ nameserver.concat(args)
+ when 'domain'
+ next if args.empty?
+ search = [args[0]]
+ when 'search'
+ next if args.empty?
+ search = args
+ when 'options'
+ args.each {|arg|
+ case arg
+ when /\Andots:(\d+)\z/
+ ndots = $1.to_i
+ end
+ }
+ end
+ }
+ }
+ return { :nameserver => nameserver, :search => search, :ndots => ndots }
+ end
+
+ def Config.default_config_hash(filename="/etc/resolv.conf")
+ if File.exist? filename
+ config_hash = Config.parse_resolv_conf(filename)
+ else
+ if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
+ require 'win32/resolv'
+ search, nameserver = Win32::Resolv.get_resolv_info
+ config_hash = {}
+ config_hash[:nameserver] = nameserver if nameserver
+ config_hash[:search] = [search].flatten if search
+ end
+ end
+ config_hash || {}
+ end
+
+ def lazy_initialize
+ @mutex.synchronize {
+ unless @initialized
+ @nameserver_port = []
+ @use_ipv6 = nil
+ @search = nil
+ @ndots = 1
+ case @config_info
+ when nil
+ config_hash = Config.default_config_hash
+ when String
+ config_hash = Config.parse_resolv_conf(@config_info)
+ when Hash
+ config_hash = @config_info.dup
+ if String === config_hash[:nameserver]
+ config_hash[:nameserver] = [config_hash[:nameserver]]
+ end
+ if String === config_hash[:search]
+ config_hash[:search] = [config_hash[:search]]
+ end
+ else
+ raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
+ end
+ if config_hash.include? :nameserver
+ @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] }
+ end
+ if config_hash.include? :nameserver_port
+ @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] }
+ end
+ if config_hash.include? :use_ipv6
+ @use_ipv6 = config_hash[:use_ipv6]
+ end
+ @search = config_hash[:search] if config_hash.include? :search
+ @ndots = config_hash[:ndots] if config_hash.include? :ndots
+ @raise_timeout_errors = config_hash[:raise_timeout_errors]
+
+ if @nameserver_port.empty?
+ @nameserver_port << ['0.0.0.0', Port]
+ end
+ if @search
+ @search = @search.map {|arg| Label.split(arg) }
+ else
+ hostname = Socket.gethostname
+ if /\./ =~ hostname
+ @search = [Label.split($')]
+ else
+ @search = [[]]
+ end
+ end
+
+ if !@nameserver_port.kind_of?(Array) ||
+ @nameserver_port.any? {|ns_port|
+ !(Array === ns_port) ||
+ ns_port.length != 2
+ !(String === ns_port[0]) ||
+ !(Integer === ns_port[1])
+ }
+ raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}")
+ end
+
+ if !@search.kind_of?(Array) ||
+ !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
+ raise ArgumentError.new("invalid search config: #{@search.inspect}")
+ end
+
+ if !@ndots.kind_of?(Integer)
+ raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
+ end
+
+ @initialized = true
+ end
+ }
+ self
+ end
+
+ def single?
+ lazy_initialize
+ if @nameserver_port.length == 1
+ return @nameserver_port[0]
+ else
+ return nil
+ end
+ end
+
+ def nameserver_port
+ @nameserver_port
+ end
+
+ def use_ipv6?
+ @use_ipv6
+ end
+
+ def generate_candidates(name)
+ candidates = nil
+ name = Name.create(name)
+ if name.absolute?
+ candidates = [name]
+ else
+ if @ndots <= name.length - 1
+ candidates = [Name.new(name.to_a)]
+ else
+ candidates = []
+ end
+ candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
+ fname = Name.create("#{name}.")
+ if !candidates.include?(fname)
+ candidates << fname
+ end
+ end
+ return candidates
+ end
+
+ InitialTimeout = 5
+
+ def generate_timeouts
+ ts = [InitialTimeout]
+ ts << ts[-1] * 2 / @nameserver_port.length
+ ts << ts[-1] * 2
+ ts << ts[-1] * 2
+ return ts
+ end
+
+ def resolv(name)
+ candidates = generate_candidates(name)
+ timeouts = @timeouts || generate_timeouts
+ timeout_error = false
+ begin
+ candidates.each {|candidate|
+ begin
+ timeouts.each {|tout|
+ @nameserver_port.each {|nameserver, port|
+ begin
+ yield candidate, tout, nameserver, port
+ rescue ResolvTimeout
+ end
+ }
+ }
+ timeout_error = true
+ raise ResolvError.new("DNS resolv timeout: #{name}")
+ rescue NXDomain
+ end
+ }
+ rescue ResolvError
+ raise if @raise_timeout_errors && timeout_error
+ end
+ end
+
+ ##
+ # Indicates no such domain was found.
+
+ class NXDomain < ResolvError
+ end
+
+ ##
+ # Indicates some other unhandled resolver error was encountered.
+
+ class OtherResolvError < ResolvError
+ end
+ end
+
+ module OpCode # :nodoc:
+ Query = 0
+ IQuery = 1
+ Status = 2
+ Notify = 4
+ Update = 5
+ end
+
+ module RCode # :nodoc:
+ NoError = 0
+ FormErr = 1
+ ServFail = 2
+ NXDomain = 3
+ NotImp = 4
+ Refused = 5
+ YXDomain = 6
+ YXRRSet = 7
+ NXRRSet = 8
+ NotAuth = 9
+ NotZone = 10
+ BADVERS = 16
+ BADSIG = 16
+ BADKEY = 17
+ BADTIME = 18
+ BADMODE = 19
+ BADNAME = 20
+ BADALG = 21
+ end
+
+ ##
+ # Indicates that the DNS response was unable to be decoded.
+
+ class DecodeError < StandardError
+ end
+
+ ##
+ # Indicates that the DNS request was unable to be encoded.
+
+ class EncodeError < StandardError
+ end
+
+ module Label # :nodoc:
+ def self.split(arg)
+ labels = []
+ arg.scan(/[^\.]+/) {labels << Str.new($&)}
+ return labels
+ end
+
+ class Str # :nodoc:
+ def initialize(string)
+ @string = string
+ # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
+ # This assumes @string is given in ASCII compatible encoding.
+ @downcase = string.b.downcase
+ end
+ attr_reader :string, :downcase
+
+ def to_s
+ return @string
+ end
+
+ def inspect
+ return "#<#{self.class} #{self}>"
+ end
+
+ def ==(other)
+ return self.class == other.class && @downcase == other.downcase
+ end
+
+ def eql?(other)
+ return self == other
+ end
+
+ def hash
+ return @downcase.hash
+ end
+ end
+ end
+
+ ##
+ # A representation of a DNS name.
+
+ class Name
+
+ ##
+ # Creates a new DNS name from +arg+. +arg+ can be:
+ #
+ # Name:: returns +arg+.
+ # String:: Creates a new Name.
+
+ def self.create(arg)
+ case arg
+ when Name
+ return arg
+ when String
+ return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
+ else
+ raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
+ end
+ end
+
+ def initialize(labels, absolute=true) # :nodoc:
+ labels = labels.map {|label|
+ case label
+ when String then Label::Str.new(label)
+ when Label::Str then label
+ else
+ raise ArgumentError, "unexpected label: #{label.inspect}"
+ end
+ }
+ @labels = labels
+ @absolute = absolute
+ end
+
+ def inspect # :nodoc:
+ "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>"
+ end
+
+ ##
+ # True if this name is absolute.
+
+ def absolute?
+ return @absolute
+ end
+
+ def ==(other) # :nodoc:
+ return false unless Name === other
+ return false unless @absolute == other.absolute?
+ return @labels == other.to_a
+ end
+
+ alias eql? == # :nodoc:
+
+ ##
+ # Returns true if +other+ is a subdomain.
+ #
+ # Example:
+ #
+ # domain = Gem::Resolv::DNS::Name.create("y.z")
+ # p Gem::Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
+ # p Gem::Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
+ # p Gem::Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
+ # p Gem::Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
+ # p Gem::Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
+ # p Gem::Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
+ #
+
+ def subdomain_of?(other)
+ raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
+ return false if @absolute != other.absolute?
+ other_len = other.length
+ return false if @labels.length <= other_len
+ return @labels[-other_len, other_len] == other.to_a
+ end
+
+ def hash # :nodoc:
+ return @labels.hash ^ @absolute.hash
+ end
+
+ def to_a # :nodoc:
+ return @labels
+ end
+
+ def length # :nodoc:
+ return @labels.length
+ end
+
+ def [](i) # :nodoc:
+ return @labels[i]
+ end
+
+ ##
+ # returns the domain name as a string.
+ #
+ # The domain name doesn't have a trailing dot even if the name object is
+ # absolute.
+ #
+ # Example:
+ #
+ # p Gem::Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
+ # p Gem::Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
+
+ def to_s
+ return @labels.join('.')
+ end
+ end
+
+ class Message # :nodoc:
+ @@identifier = -1
+
+ def initialize(id = (@@identifier += 1) & 0xffff)
+ @id = id
+ @qr = 0
+ @opcode = 0
+ @aa = 0
+ @tc = 0
+ @rd = 0 # recursion desired
+ @ra = 0 # recursion available
+ @rcode = 0
+ @question = []
+ @answer = []
+ @authority = []
+ @additional = []
+ end
+
+ attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
+ attr_reader :question, :answer, :authority, :additional
+
+ def ==(other)
+ return @id == other.id &&
+ @qr == other.qr &&
+ @opcode == other.opcode &&
+ @aa == other.aa &&
+ @tc == other.tc &&
+ @rd == other.rd &&
+ @ra == other.ra &&
+ @rcode == other.rcode &&
+ @question == other.question &&
+ @answer == other.answer &&
+ @authority == other.authority &&
+ @additional == other.additional
+ end
+
+ def add_question(name, typeclass)
+ @question << [Name.create(name), typeclass]
+ end
+
+ def each_question
+ @question.each {|name, typeclass|
+ yield name, typeclass
+ }
+ end
+
+ def add_answer(name, ttl, data)
+ @answer << [Name.create(name), ttl, data]
+ end
+
+ def each_answer
+ @answer.each {|name, ttl, data|
+ yield name, ttl, data
+ }
+ end
+
+ def add_authority(name, ttl, data)
+ @authority << [Name.create(name), ttl, data]
+ end
+
+ def each_authority
+ @authority.each {|name, ttl, data|
+ yield name, ttl, data
+ }
+ end
+
+ def add_additional(name, ttl, data)
+ @additional << [Name.create(name), ttl, data]
+ end
+
+ def each_additional
+ @additional.each {|name, ttl, data|
+ yield name, ttl, data
+ }
+ end
+
+ def each_resource
+ each_answer {|name, ttl, data| yield name, ttl, data}
+ each_authority {|name, ttl, data| yield name, ttl, data}
+ each_additional {|name, ttl, data| yield name, ttl, data}
+ end
+
+ def encode
+ return MessageEncoder.new {|msg|
+ msg.put_pack('nnnnnn',
+ @id,
+ (@qr & 1) << 15 |
+ (@opcode & 15) << 11 |
+ (@aa & 1) << 10 |
+ (@tc & 1) << 9 |
+ (@rd & 1) << 8 |
+ (@ra & 1) << 7 |
+ (@rcode & 15),
+ @question.length,
+ @answer.length,
+ @authority.length,
+ @additional.length)
+ @question.each {|q|
+ name, typeclass = q
+ msg.put_name(name)
+ msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
+ }
+ [@answer, @authority, @additional].each {|rr|
+ rr.each {|r|
+ name, ttl, data = r
+ msg.put_name(name)
+ msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
+ msg.put_length16 {data.encode_rdata(msg)}
+ }
+ }
+ }.to_s
+ end
+
+ class MessageEncoder # :nodoc:
+ def initialize
+ @data = ''.dup
+ @names = {}
+ yield self
+ end
+
+ def to_s
+ return @data
+ end
+
+ def put_bytes(d)
+ @data << d
+ end
+
+ def put_pack(template, *d)
+ @data << d.pack(template)
+ end
+
+ def put_length16
+ length_index = @data.length
+ @data << "\0\0"
+ data_start = @data.length
+ yield
+ data_end = @data.length
+ @data[length_index, 2] = [data_end - data_start].pack("n")
+ end
+
+ def put_string(d)
+ self.put_pack("C", d.length)
+ @data << d
+ end
+
+ def put_string_list(ds)
+ ds.each {|d|
+ self.put_string(d)
+ }
+ end
+
+ def put_name(d, compress: true)
+ put_labels(d.to_a, compress: compress)
+ end
+
+ def put_labels(d, compress: true)
+ d.each_index {|i|
+ domain = d[i..-1]
+ if compress && idx = @names[domain]
+ self.put_pack("n", 0xc000 | idx)
+ return
+ else
+ if @data.length < 0x4000
+ @names[domain] = @data.length
+ end
+ self.put_label(d[i])
+ end
+ }
+ @data << "\0"
+ end
+
+ def put_label(d)
+ self.put_string(d.to_s)
+ end
+ end
+
+ def Message.decode(m)
+ o = Message.new(0)
+ MessageDecoder.new(m) {|msg|
+ id, flag, qdcount, ancount, nscount, arcount =
+ msg.get_unpack('nnnnnn')
+ o.id = id
+ o.tc = (flag >> 9) & 1
+ o.rcode = flag & 15
+ return o unless o.tc.zero?
+
+ o.qr = (flag >> 15) & 1
+ o.opcode = (flag >> 11) & 15
+ o.aa = (flag >> 10) & 1
+ o.rd = (flag >> 8) & 1
+ o.ra = (flag >> 7) & 1
+ (1..qdcount).each {
+ name, typeclass = msg.get_question
+ o.add_question(name, typeclass)
+ }
+ (1..ancount).each {
+ name, ttl, data = msg.get_rr
+ o.add_answer(name, ttl, data)
+ }
+ (1..nscount).each {
+ name, ttl, data = msg.get_rr
+ o.add_authority(name, ttl, data)
+ }
+ (1..arcount).each {
+ name, ttl, data = msg.get_rr
+ o.add_additional(name, ttl, data)
+ }
+ }
+ return o
+ end
+
+ class MessageDecoder # :nodoc:
+ def initialize(data)
+ @data = data
+ @index = 0
+ @limit = data.bytesize
+ yield self
+ end
+
+ def inspect
+ "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>"
+ end
+
+ def get_length16
+ len, = self.get_unpack('n')
+ save_limit = @limit
+ @limit = @index + len
+ d = yield(len)
+ if @index < @limit
+ raise DecodeError.new("junk exists")
+ elsif @limit < @index
+ raise DecodeError.new("limit exceeded")
+ end
+ @limit = save_limit
+ return d
+ end
+
+ def get_bytes(len = @limit - @index)
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
+ d = @data.byteslice(@index, len)
+ @index += len
+ return d
+ end
+
+ def get_unpack(template)
+ len = 0
+ template.each_byte {|byte|
+ byte = "%c" % byte
+ case byte
+ when ?c, ?C
+ len += 1
+ when ?n
+ len += 2
+ when ?N
+ len += 4
+ else
+ raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
+ end
+ }
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
+ arr = @data.unpack("@#{@index}#{template}")
+ @index += len
+ return arr
+ end
+
+ def get_string
+ raise DecodeError.new("limit exceeded") if @limit <= @index
+ len = @data.getbyte(@index)
+ raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
+ d = @data.byteslice(@index + 1, len)
+ @index += 1 + len
+ return d
+ end
+
+ def get_string_list
+ strings = []
+ while @index < @limit
+ strings << self.get_string
+ end
+ strings
+ end
+
+ def get_list
+ [].tap do |values|
+ while @index < @limit
+ values << yield
+ end
+ end
+ end
+
+ def get_name
+ return Name.new(self.get_labels)
+ end
+
+ def get_labels
+ prev_index = @index
+ save_index = nil
+ d = []
+ while true
+ raise DecodeError.new("limit exceeded") if @limit <= @index
+ case @data.getbyte(@index)
+ when 0
+ @index += 1
+ if save_index
+ @index = save_index
+ end
+ return d
+ when 192..255
+ idx = self.get_unpack('n')[0] & 0x3fff
+ if prev_index <= idx
+ raise DecodeError.new("non-backward name pointer")
+ end
+ prev_index = idx
+ if !save_index
+ save_index = @index
+ end
+ @index = idx
+ else
+ d << self.get_label
+ end
+ end
+ end
+
+ def get_label
+ return Label::Str.new(self.get_string)
+ end
+
+ def get_question
+ name = self.get_name
+ type, klass = self.get_unpack("nn")
+ return name, Resource.get_class(type, klass)
+ end
+
+ def get_rr
+ name = self.get_name
+ type, klass, ttl = self.get_unpack('nnN')
+ typeclass = Resource.get_class(type, klass)
+ res = self.get_length16 do
+ begin
+ typeclass.decode_rdata self
+ rescue => e
+ raise DecodeError, e.message, e.backtrace
+ end
+ end
+ res.instance_variable_set :@ttl, ttl
+ return name, ttl, res
+ end
+ end
+ end
+
+ ##
+ # SvcParams for service binding RRs. [RFC9460]
+
+ class SvcParams
+ include Enumerable
+
+ ##
+ # Create a list of SvcParams with the given initial content.
+ #
+ # +params+ has to be an enumerable of +SvcParam+s.
+ # If its content has +SvcParam+s with the duplicate key,
+ # the one appears last takes precedence.
+
+ def initialize(params = [])
+ @params = {}
+
+ params.each do |param|
+ add param
+ end
+ end
+
+ ##
+ # Get SvcParam for the given +key+ in this list.
+
+ def [](key)
+ @params[canonical_key(key)]
+ end
+
+ ##
+ # Get the number of SvcParams in this list.
+
+ def count
+ @params.count
+ end
+
+ ##
+ # Get whether this list is empty.
+
+ def empty?
+ @params.empty?
+ end
+
+ ##
+ # Add the SvcParam +param+ to this list, overwriting the existing one with the same key.
+
+ def add(param)
+ @params[param.class.key_number] = param
+ end
+
+ ##
+ # Remove the +SvcParam+ with the given +key+ and return it.
+
+ def delete(key)
+ @params.delete(canonical_key(key))
+ end
+
+ ##
+ # Enumerate the +SvcParam+s in this list.
+
+ def each(&block)
+ return enum_for(:each) unless block
+ @params.each_value(&block)
+ end
+
+ def encode(msg) # :nodoc:
+ @params.keys.sort.each do |key|
+ msg.put_pack('n', key)
+ msg.put_length16 do
+ @params.fetch(key).encode(msg)
+ end
+ end
+ end
+
+ def self.decode(msg) # :nodoc:
+ params = msg.get_list do
+ key, = msg.get_unpack('n')
+ msg.get_length16 do
+ SvcParam::ClassHash[key].decode(msg)
+ end
+ end
+
+ return self.new(params)
+ end
+
+ private
+
+ def canonical_key(key) # :nodoc:
+ case key
+ when Integer
+ key
+ when /\Akey(\d+)\z/
+ Integer($1)
+ when Symbol
+ SvcParam::ClassHash[key].key_number
+ else
+ raise TypeError, 'key must be either String or Symbol'
+ end
+ end
+ end
+
+
+ ##
+ # Base class for SvcParam. [RFC9460]
+
+ class SvcParam
+
+ ##
+ # Get the presentation name of the SvcParamKey.
+
+ def self.key_name
+ const_get(:KeyName)
+ end
+
+ ##
+ # Get the registered number of the SvcParamKey.
+
+ def self.key_number
+ const_get(:KeyNumber)
+ end
+
+ ClassHash = Hash.new do |h, key| # :nodoc:
+ case key
+ when Integer
+ Generic.create(key)
+ when /\Akey(?<key>\d+)\z/
+ Generic.create(key.to_int)
+ when Symbol
+ raise KeyError, "unknown key #{key}"
+ else
+ raise TypeError, 'key must be either String or Symbol'
+ end
+ end
+
+ ##
+ # Generic SvcParam abstract class.
+
+ class Generic < SvcParam
+
+ ##
+ # SvcParamValue in wire-format byte string.
+
+ attr_reader :value
+
+ ##
+ # Create generic SvcParam
+
+ def initialize(value)
+ @value = value
+ end
+
+ def encode(msg) # :nodoc:
+ msg.put_bytes(@value)
+ end
+
+ def self.decode(msg) # :nodoc:
+ return self.new(msg.get_bytes)
+ end
+
+ def self.create(key_number)
+ c = Class.new(Generic)
+ key_name = :"key#{key_number}"
+ c.const_set(:KeyName, key_name)
+ c.const_set(:KeyNumber, key_number)
+ self.const_set(:"Key#{key_number}", c)
+ ClassHash[key_name] = ClassHash[key_number] = c
+ return c
+ end
+ end
+
+ ##
+ # "mandatory" SvcParam -- Mandatory keys in service binding RR
+
+ class Mandatory < SvcParam
+ KeyName = :mandatory
+ KeyNumber = 0
+ ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+
+ ##
+ # Mandatory keys.
+
+ attr_reader :keys
+
+ ##
+ # Initialize "mandatory" ScvParam.
+
+ def initialize(keys)
+ @keys = keys.map(&:to_int)
+ end
+
+ def encode(msg) # :nodoc:
+ @keys.sort.each do |key|
+ msg.put_pack('n', key)
+ end
+ end
+
+ def self.decode(msg) # :nodoc:
+ keys = msg.get_list { msg.get_unpack('n')[0] }
+ return self.new(keys)
+ end
+ end
+
+ ##
+ # "alpn" SvcParam -- Additional supported protocols
+
+ class ALPN < SvcParam
+ KeyName = :alpn
+ KeyNumber = 1
+ ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+
+ ##
+ # Supported protocol IDs.
+
+ attr_reader :protocol_ids
+
+ ##
+ # Initialize "alpn" ScvParam.
+
+ def initialize(protocol_ids)
+ @protocol_ids = protocol_ids.map(&:to_str)
+ end
+
+ def encode(msg) # :nodoc:
+ msg.put_string_list(@protocol_ids)
+ end
+
+ def self.decode(msg) # :nodoc:
+ return self.new(msg.get_string_list)
+ end
+ end
+
+ ##
+ # "no-default-alpn" SvcParam -- No support for default protocol
+
+ class NoDefaultALPN < SvcParam
+ KeyName = :'no-default-alpn'
+ KeyNumber = 2
+ ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+
+ def encode(msg) # :nodoc:
+ # no payload
+ end
+
+ def self.decode(msg) # :nodoc:
+ return self.new
+ end
+ end
+
+ ##
+ # "port" SvcParam -- Port for alternative endpoint
+
+ class Port < SvcParam
+ KeyName = :port
+ KeyNumber = 3
+ ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+
+ ##
+ # Port number.
+
+ attr_reader :port
+
+ ##
+ # Initialize "port" ScvParam.
+
+ def initialize(port)
+ @port = port.to_int
+ end
+
+ def encode(msg) # :nodoc:
+ msg.put_pack('n', @port)
+ end
+
+ def self.decode(msg) # :nodoc:
+ port, = msg.get_unpack('n')
+ return self.new(port)
+ end
+ end
+
+ ##
+ # "ipv4hint" SvcParam -- IPv4 address hints
+
+ class IPv4Hint < SvcParam
+ KeyName = :ipv4hint
+ KeyNumber = 4
+ ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+
+ ##
+ # Set of IPv4 addresses.
+
+ attr_reader :addresses
+
+ ##
+ # Initialize "ipv4hint" ScvParam.
+
+ def initialize(addresses)
+ @addresses = addresses.map {|address| IPv4.create(address) }
+ end
+
+ def encode(msg) # :nodoc:
+ @addresses.each do |address|
+ msg.put_bytes(address.address)
+ end
+ end
+
+ def self.decode(msg) # :nodoc:
+ addresses = msg.get_list { IPv4.new(msg.get_bytes(4)) }
+ return self.new(addresses)
+ end
+ end
+
+ ##
+ # "ipv6hint" SvcParam -- IPv6 address hints
+
+ class IPv6Hint < SvcParam
+ KeyName = :ipv6hint
+ KeyNumber = 6
+ ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+
+ ##
+ # Set of IPv6 addresses.
+
+ attr_reader :addresses
+
+ ##
+ # Initialize "ipv6hint" ScvParam.
+
+ def initialize(addresses)
+ @addresses = addresses.map {|address| IPv6.create(address) }
+ end
+
+ def encode(msg) # :nodoc:
+ @addresses.each do |address|
+ msg.put_bytes(address.address)
+ end
+ end
+
+ def self.decode(msg) # :nodoc:
+ addresses = msg.get_list { IPv6.new(msg.get_bytes(16)) }
+ return self.new(addresses)
+ end
+ end
+
+ ##
+ # "dohpath" SvcParam -- DNS over HTTPS path template [RFC9461]
+
+ class DoHPath < SvcParam
+ KeyName = :dohpath
+ KeyNumber = 7
+ ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+
+ ##
+ # URI template for DoH queries.
+
+ attr_reader :template
+
+ ##
+ # Initialize "dohpath" ScvParam.
+
+ def initialize(template)
+ @template = template.encode('utf-8')
+ end
+
+ def encode(msg) # :nodoc:
+ msg.put_bytes(@template)
+ end
+
+ def self.decode(msg) # :nodoc:
+ template = msg.get_bytes.force_encoding('utf-8')
+ return self.new(template)
+ end
+ end
+ end
+
+ ##
+ # A DNS query abstract class.
+
+ class Query
+ def encode_rdata(msg) # :nodoc:
+ raise EncodeError.new("#{self.class} is query.")
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ raise DecodeError.new("#{self.class} is query.")
+ end
+ end
+
+ ##
+ # A DNS resource abstract class.
+
+ class Resource < Query
+
+ ##
+ # Remaining Time To Live for this Resource.
+
+ attr_reader :ttl
+
+ ClassHash = {} # :nodoc:
+
+ def encode_rdata(msg) # :nodoc:
+ raise NotImplementedError.new
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ raise NotImplementedError.new
+ end
+
+ def ==(other) # :nodoc:
+ return false unless self.class == other.class
+ s_ivars = self.instance_variables
+ s_ivars.sort!
+ s_ivars.delete :@ttl
+ o_ivars = other.instance_variables
+ o_ivars.sort!
+ o_ivars.delete :@ttl
+ return s_ivars == o_ivars &&
+ s_ivars.collect {|name| self.instance_variable_get name} ==
+ o_ivars.collect {|name| other.instance_variable_get name}
+ end
+
+ def eql?(other) # :nodoc:
+ return self == other
+ end
+
+ def hash # :nodoc:
+ h = 0
+ vars = self.instance_variables
+ vars.delete :@ttl
+ vars.each {|name|
+ h ^= self.instance_variable_get(name).hash
+ }
+ return h
+ end
+
+ def self.get_class(type_value, class_value) # :nodoc:
+ return ClassHash[[type_value, class_value]] ||
+ Generic.create(type_value, class_value)
+ end
+
+ ##
+ # A generic resource abstract class.
+
+ class Generic < Resource
+
+ ##
+ # Creates a new generic resource.
+
+ def initialize(data)
+ @data = data
+ end
+
+ ##
+ # Data for this generic resource.
+
+ attr_reader :data
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_bytes(data)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ return self.new(msg.get_bytes)
+ end
+
+ def self.create(type_value, class_value) # :nodoc:
+ c = Class.new(Generic)
+ c.const_set(:TypeValue, type_value)
+ c.const_set(:ClassValue, class_value)
+ Generic.const_set("Type#{type_value}_Class#{class_value}", c)
+ ClassHash[[type_value, class_value]] = c
+ return c
+ end
+ end
+
+ ##
+ # Domain Name resource abstract class.
+
+ class DomainName < Resource
+
+ ##
+ # Creates a new DomainName from +name+.
+
+ def initialize(name)
+ @name = name
+ end
+
+ ##
+ # The name of this DomainName.
+
+ attr_reader :name
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_name(@name)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ return self.new(msg.get_name)
+ end
+ end
+
+ # Standard (class generic) RRs
+
+ ClassValue = nil # :nodoc:
+
+ ##
+ # An authoritative name server.
+
+ class NS < DomainName
+ TypeValue = 2 # :nodoc:
+ end
+
+ ##
+ # The canonical name for an alias.
+
+ class CNAME < DomainName
+ TypeValue = 5 # :nodoc:
+ end
+
+ ##
+ # Start Of Authority resource.
+
+ class SOA < Resource
+
+ TypeValue = 6 # :nodoc:
+
+ ##
+ # Creates a new SOA record. See the attr documentation for the
+ # details of each argument.
+
+ def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
+ @mname = mname
+ @rname = rname
+ @serial = serial
+ @refresh = refresh
+ @retry = retry_
+ @expire = expire
+ @minimum = minimum
+ end
+
+ ##
+ # Name of the host where the master zone file for this zone resides.
+
+ attr_reader :mname
+
+ ##
+ # The person responsible for this domain name.
+
+ attr_reader :rname
+
+ ##
+ # The version number of the zone file.
+
+ attr_reader :serial
+
+ ##
+ # How often, in seconds, a secondary name server is to check for
+ # updates from the primary name server.
+
+ attr_reader :refresh
+
+ ##
+ # How often, in seconds, a secondary name server is to retry after a
+ # failure to check for a refresh.
+
+ attr_reader :retry
+
+ ##
+ # Time in seconds that a secondary name server is to use the data
+ # before refreshing from the primary name server.
+
+ attr_reader :expire
+
+ ##
+ # The minimum number of seconds to be used for TTL values in RRs.
+
+ attr_reader :minimum
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_name(@mname)
+ msg.put_name(@rname)
+ msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ mname = msg.get_name
+ rname = msg.get_name
+ serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
+ return self.new(
+ mname, rname, serial, refresh, retry_, expire, minimum)
+ end
+ end
+
+ ##
+ # A Pointer to another DNS name.
+
+ class PTR < DomainName
+ TypeValue = 12 # :nodoc:
+ end
+
+ ##
+ # Host Information resource.
+
+ class HINFO < Resource
+
+ TypeValue = 13 # :nodoc:
+
+ ##
+ # Creates a new HINFO running +os+ on +cpu+.
+
+ def initialize(cpu, os)
+ @cpu = cpu
+ @os = os
+ end
+
+ ##
+ # CPU architecture for this resource.
+
+ attr_reader :cpu
+
+ ##
+ # Operating system for this resource.
+
+ attr_reader :os
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_string(@cpu)
+ msg.put_string(@os)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ cpu = msg.get_string
+ os = msg.get_string
+ return self.new(cpu, os)
+ end
+ end
+
+ ##
+ # Mailing list or mailbox information.
+
+ class MINFO < Resource
+
+ TypeValue = 14 # :nodoc:
+
+ def initialize(rmailbx, emailbx)
+ @rmailbx = rmailbx
+ @emailbx = emailbx
+ end
+
+ ##
+ # Domain name responsible for this mail list or mailbox.
+
+ attr_reader :rmailbx
+
+ ##
+ # Mailbox to use for error messages related to the mail list or mailbox.
+
+ attr_reader :emailbx
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_name(@rmailbx)
+ msg.put_name(@emailbx)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ rmailbx = msg.get_string
+ emailbx = msg.get_string
+ return self.new(rmailbx, emailbx)
+ end
+ end
+
+ ##
+ # Mail Exchanger resource.
+
+ class MX < Resource
+
+ TypeValue= 15 # :nodoc:
+
+ ##
+ # Creates a new MX record with +preference+, accepting mail at
+ # +exchange+.
+
+ def initialize(preference, exchange)
+ @preference = preference
+ @exchange = exchange
+ end
+
+ ##
+ # The preference for this MX.
+
+ attr_reader :preference
+
+ ##
+ # The host of this MX.
+
+ attr_reader :exchange
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_pack('n', @preference)
+ msg.put_name(@exchange)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ preference, = msg.get_unpack('n')
+ exchange = msg.get_name
+ return self.new(preference, exchange)
+ end
+ end
+
+ ##
+ # Unstructured text resource.
+
+ class TXT < Resource
+
+ TypeValue = 16 # :nodoc:
+
+ def initialize(first_string, *rest_strings)
+ @strings = [first_string, *rest_strings]
+ end
+
+ ##
+ # Returns an Array of Strings for this TXT record.
+
+ attr_reader :strings
+
+ ##
+ # Returns the concatenated string from +strings+.
+
+ def data
+ @strings.join("")
+ end
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_string_list(@strings)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ strings = msg.get_string_list
+ return self.new(*strings)
+ end
+ end
+
+ ##
+ # Location resource
+
+ class LOC < Resource
+
+ TypeValue = 29 # :nodoc:
+
+ def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude)
+ @version = version
+ @ssize = Gem::Resolv::LOC::Size.create(ssize)
+ @hprecision = Gem::Resolv::LOC::Size.create(hprecision)
+ @vprecision = Gem::Resolv::LOC::Size.create(vprecision)
+ @latitude = Gem::Resolv::LOC::Coord.create(latitude)
+ @longitude = Gem::Resolv::LOC::Coord.create(longitude)
+ @altitude = Gem::Resolv::LOC::Alt.create(altitude)
+ end
+
+ ##
+ # Returns the version value for this LOC record which should always be 00
+
+ attr_reader :version
+
+ ##
+ # The spherical size of this LOC
+ # in meters using scientific notation as 2 integers of XeY
+
+ attr_reader :ssize
+
+ ##
+ # The horizontal precision using ssize type values
+ # in meters using scientific notation as 2 integers of XeY
+ # for precision use value/2 e.g. 2m = +/-1m
+
+ attr_reader :hprecision
+
+ ##
+ # The vertical precision using ssize type values
+ # in meters using scientific notation as 2 integers of XeY
+ # for precision use value/2 e.g. 2m = +/-1m
+
+ attr_reader :vprecision
+
+ ##
+ # The latitude for this LOC where 2**31 is the equator
+ # in thousandths of an arc second as an unsigned 32bit integer
+
+ attr_reader :latitude
+
+ ##
+ # The longitude for this LOC where 2**31 is the prime meridian
+ # in thousandths of an arc second as an unsigned 32bit integer
+
+ attr_reader :longitude
+
+ ##
+ # The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid
+ # in centimeters as an unsigned 32bit integer
+
+ attr_reader :altitude
+
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_bytes(@version)
+ msg.put_bytes(@ssize.scalar)
+ msg.put_bytes(@hprecision.scalar)
+ msg.put_bytes(@vprecision.scalar)
+ msg.put_bytes(@latitude.coordinates)
+ msg.put_bytes(@longitude.coordinates)
+ msg.put_bytes(@altitude.altitude)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ version = msg.get_bytes(1)
+ ssize = msg.get_bytes(1)
+ hprecision = msg.get_bytes(1)
+ vprecision = msg.get_bytes(1)
+ latitude = msg.get_bytes(4)
+ longitude = msg.get_bytes(4)
+ altitude = msg.get_bytes(4)
+ return self.new(
+ version,
+ Gem::Resolv::LOC::Size.new(ssize),
+ Gem::Resolv::LOC::Size.new(hprecision),
+ Gem::Resolv::LOC::Size.new(vprecision),
+ Gem::Resolv::LOC::Coord.new(latitude,"lat"),
+ Gem::Resolv::LOC::Coord.new(longitude,"lon"),
+ Gem::Resolv::LOC::Alt.new(altitude)
+ )
+ end
+ end
+
+ ##
+ # A Query type requesting any RR.
+
+ class ANY < Query
+ TypeValue = 255 # :nodoc:
+ end
+
+ ##
+ # CAA resource record defined in RFC 8659
+ #
+ # These records identify certificate authority allowed to issue
+ # certificates for the given domain.
+
+ class CAA < Resource
+ TypeValue = 257
+
+ ##
+ # Creates a new CAA for +flags+, +tag+ and +value+.
+
+ def initialize(flags, tag, value)
+ unless (0..255) === flags
+ raise ArgumentError.new('flags must be an Integer between 0 and 255')
+ end
+ unless (1..15) === tag.bytesize
+ raise ArgumentError.new('length of tag must be between 1 and 15')
+ end
+
+ @flags = flags
+ @tag = tag
+ @value = value
+ end
+
+ ##
+ # Flags for this proprty:
+ # - Bit 0 : 0 = not critical, 1 = critical
+
+ attr_reader :flags
+
+ ##
+ # Property tag ("issue", "issuewild", "iodef"...).
+
+ attr_reader :tag
+
+ ##
+ # Property value.
+
+ attr_reader :value
+
+ ##
+ # Whether the critical flag is set on this property.
+
+ def critical?
+ flags & 0x80 != 0
+ end
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_pack('C', @flags)
+ msg.put_string(@tag)
+ msg.put_bytes(@value)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ flags, = msg.get_unpack('C')
+ tag = msg.get_string
+ value = msg.get_bytes
+ self.new flags, tag, value
+ end
+ end
+
+ ClassInsensitiveTypes = [ # :nodoc:
+ NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY, CAA
+ ]
+
+ ##
+ # module IN contains ARPA Internet specific RRs.
+
+ module IN
+
+ ClassValue = 1 # :nodoc:
+
+ ClassInsensitiveTypes.each {|s|
+ c = Class.new(s)
+ c.const_set(:TypeValue, s::TypeValue)
+ c.const_set(:ClassValue, ClassValue)
+ ClassHash[[s::TypeValue, ClassValue]] = c
+ self.const_set(s.name.sub(/.*::/, ''), c)
+ }
+
+ ##
+ # IPv4 Address resource
+
+ class A < Resource
+ TypeValue = 1
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+
+ ##
+ # Creates a new A for +address+.
+
+ def initialize(address)
+ @address = IPv4.create(address)
+ end
+
+ ##
+ # The Gem::Resolv::IPv4 address for this A.
+
+ attr_reader :address
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_bytes(@address.address)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ return self.new(IPv4.new(msg.get_bytes(4)))
+ end
+ end
+
+ ##
+ # Well Known Service resource.
+
+ class WKS < Resource
+ TypeValue = 11
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+
+ def initialize(address, protocol, bitmap)
+ @address = IPv4.create(address)
+ @protocol = protocol
+ @bitmap = bitmap
+ end
+
+ ##
+ # The host these services run on.
+
+ attr_reader :address
+
+ ##
+ # IP protocol number for these services.
+
+ attr_reader :protocol
+
+ ##
+ # A bit map of enabled services on this host.
+ #
+ # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
+ # service (port 25). If this bit is set, then an SMTP server should
+ # be listening on TCP port 25; if zero, SMTP service is not
+ # supported.
+
+ attr_reader :bitmap
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_bytes(@address.address)
+ msg.put_pack("n", @protocol)
+ msg.put_bytes(@bitmap)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ address = IPv4.new(msg.get_bytes(4))
+ protocol, = msg.get_unpack("n")
+ bitmap = msg.get_bytes
+ return self.new(address, protocol, bitmap)
+ end
+ end
+
+ ##
+ # An IPv6 address record.
+
+ class AAAA < Resource
+ TypeValue = 28
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+
+ ##
+ # Creates a new AAAA for +address+.
+
+ def initialize(address)
+ @address = IPv6.create(address)
+ end
+
+ ##
+ # The Gem::Resolv::IPv6 address for this AAAA.
+
+ attr_reader :address
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_bytes(@address.address)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ return self.new(IPv6.new(msg.get_bytes(16)))
+ end
+ end
+
+ ##
+ # SRV resource record defined in RFC 2782
+ #
+ # These records identify the hostname and port that a service is
+ # available at.
+
+ class SRV < Resource
+ TypeValue = 33
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+
+ # Create a SRV resource record.
+ #
+ # See the documentation for #priority, #weight, #port and #target
+ # for +priority+, +weight+, +port and +target+ respectively.
+
+ def initialize(priority, weight, port, target)
+ @priority = priority.to_int
+ @weight = weight.to_int
+ @port = port.to_int
+ @target = Name.create(target)
+ end
+
+ # The priority of this target host.
+ #
+ # A client MUST attempt to contact the target host with the
+ # lowest-numbered priority it can reach; target hosts with the same
+ # priority SHOULD be tried in an order defined by the weight field.
+ # The range is 0-65535. Note that it is not widely implemented and
+ # should be set to zero.
+
+ attr_reader :priority
+
+ # A server selection mechanism.
+ #
+ # The weight field specifies a relative weight for entries with the
+ # same priority. Larger weights SHOULD be given a proportionately
+ # higher probability of being selected. The range of this number is
+ # 0-65535. Domain administrators SHOULD use Weight 0 when there
+ # isn't any server selection to do, to make the RR easier to read
+ # for humans (less noisy). Note that it is not widely implemented
+ # and should be set to zero.
+
+ attr_reader :weight
+
+ # The port on this target host of this service.
+ #
+ # The range is 0-65535.
+
+ attr_reader :port
+
+ # The domain name of the target host.
+ #
+ # A target of "." means that the service is decidedly not available
+ # at this domain.
+
+ attr_reader :target
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_pack("n", @priority)
+ msg.put_pack("n", @weight)
+ msg.put_pack("n", @port)
+ msg.put_name(@target, compress: false)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ priority, = msg.get_unpack("n")
+ weight, = msg.get_unpack("n")
+ port, = msg.get_unpack("n")
+ target = msg.get_name
+ return self.new(priority, weight, port, target)
+ end
+ end
+
+ ##
+ # Common implementation for SVCB-compatible resource records.
+
+ class ServiceBinding
+
+ ##
+ # Create a service binding resource record.
+
+ def initialize(priority, target, params = [])
+ @priority = priority.to_int
+ @target = Name.create(target)
+ @params = SvcParams.new(params)
+ end
+
+ ##
+ # The priority of this target host.
+ #
+ # The range is 0-65535.
+ # If set to 0, this RR is in AliasMode. Otherwise, it is in ServiceMode.
+
+ attr_reader :priority
+
+ ##
+ # The domain name of the target host.
+
+ attr_reader :target
+
+ ##
+ # The service parameters for the target host.
+
+ attr_reader :params
+
+ ##
+ # Whether this RR is in AliasMode.
+
+ def alias_mode?
+ self.priority == 0
+ end
+
+ ##
+ # Whether this RR is in ServiceMode.
+
+ def service_mode?
+ !alias_mode?
+ end
+
+ def encode_rdata(msg) # :nodoc:
+ msg.put_pack("n", @priority)
+ msg.put_name(@target, compress: false)
+ @params.encode(msg)
+ end
+
+ def self.decode_rdata(msg) # :nodoc:
+ priority, = msg.get_unpack("n")
+ target = msg.get_name
+ params = SvcParams.decode(msg)
+ return self.new(priority, target, params)
+ end
+ end
+
+ ##
+ # SVCB resource record [RFC9460]
+
+ class SVCB < ServiceBinding
+ TypeValue = 64
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+ end
+
+ ##
+ # HTTPS resource record [RFC9460]
+
+ class HTTPS < ServiceBinding
+ TypeValue = 65
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+ end
+ end
+ end
+ end
+
+ ##
+ # A Gem::Resolv::DNS IPv4 address.
+
+ class IPv4
+
+ ##
+ # Regular expression IPv4 addresses must match.
+
+ Regex256 = /0
+ |1(?:[0-9][0-9]?)?
+ |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
+ |[3-9][0-9]?/x
+ Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
+
+ def self.create(arg)
+ case arg
+ when IPv4
+ return arg
+ when Regex
+ if (0..255) === (a = $1.to_i) &&
+ (0..255) === (b = $2.to_i) &&
+ (0..255) === (c = $3.to_i) &&
+ (0..255) === (d = $4.to_i)
+ return self.new([a, b, c, d].pack("CCCC"))
+ else
+ raise ArgumentError.new("IPv4 address with invalid value: " + arg)
+ end
+ else
+ raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
+ end
+ end
+
+ def initialize(address) # :nodoc:
+ unless address.kind_of?(String)
+ raise ArgumentError, 'IPv4 address must be a string'
+ end
+ unless address.length == 4
+ raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes"
+ end
+ @address = address
+ end
+
+ ##
+ # A String representation of this IPv4 address.
+
+ ##
+ # The raw IPv4 address as a String.
+
+ attr_reader :address
+
+ def to_s # :nodoc:
+ return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
+ end
+
+ def inspect # :nodoc:
+ return "#<#{self.class} #{self}>"
+ end
+
+ ##
+ # Turns this IPv4 address into a Gem::Resolv::DNS::Name.
+
+ def to_name
+ return DNS::Name.create(
+ '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
+ end
+
+ def ==(other) # :nodoc:
+ return @address == other.address
+ end
+
+ def eql?(other) # :nodoc:
+ return self == other
+ end
+
+ def hash # :nodoc:
+ return @address.hash
+ end
+ end
+
+ ##
+ # A Gem::Resolv::DNS IPv6 address.
+
+ class IPv6
+
+ ##
+ # IPv6 address format a:b:c:d:e:f:g:h
+ Regex_8Hex = /\A
+ (?:[0-9A-Fa-f]{1,4}:){7}
+ [0-9A-Fa-f]{1,4}
+ \z/x
+
+ ##
+ # Compressed IPv6 address format a::b
+
+ Regex_CompressedHex = /\A
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
+ \z/x
+
+ ##
+ # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
+
+ Regex_6Hex4Dec = /\A
+ ((?:[0-9A-Fa-f]{1,4}:){6,6})
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
+ \z/x
+
+ ##
+ # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
+
+ Regex_CompressedHex4Dec = /\A
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
+ ((?:[0-9A-Fa-f]{1,4}:)*)
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
+ \z/x
+
+ ##
+ # IPv6 link local address format fe80:b:c:d:e:f:g:h%em1
+ Regex_8HexLinkLocal = /\A
+ [Ff][Ee]80
+ (?::[0-9A-Fa-f]{1,4}){7}
+ %[-0-9A-Za-z._~]+
+ \z/x
+
+ ##
+ # Compressed IPv6 link local address format fe80::b%em1
+
+ Regex_CompressedHexLinkLocal = /\A
+ [Ff][Ee]80:
+ (?:
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
+ |
+ :((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
+ )?
+ :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+
+ \z/x
+
+ ##
+ # A composite IPv6 address Regexp.
+
+ Regex = /
+ (?:#{Regex_8Hex}) |
+ (?:#{Regex_CompressedHex}) |
+ (?:#{Regex_6Hex4Dec}) |
+ (?:#{Regex_CompressedHex4Dec}) |
+ (?:#{Regex_8HexLinkLocal}) |
+ (?:#{Regex_CompressedHexLinkLocal})
+ /x
+
+ ##
+ # Creates a new IPv6 address from +arg+ which may be:
+ #
+ # IPv6:: returns +arg+.
+ # String:: +arg+ must match one of the IPv6::Regex* constants
+
+ def self.create(arg)
+ case arg
+ when IPv6
+ return arg
+ when String
+ address = ''.b
+ if Regex_8Hex =~ arg
+ arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
+ elsif Regex_CompressedHex =~ arg
+ prefix = $1
+ suffix = $2
+ a1 = ''.b
+ a2 = ''.b
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
+ omitlen = 16 - a1.length - a2.length
+ address << a1 << "\0" * omitlen << a2
+ elsif Regex_6Hex4Dec =~ arg
+ prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
+ address << [a, b, c, d].pack('CCCC')
+ else
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
+ end
+ elsif Regex_CompressedHex4Dec =~ arg
+ prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
+ a1 = ''.b
+ a2 = ''.b
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
+ omitlen = 12 - a1.length - a2.length
+ address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
+ else
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
+ end
+ else
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
+ end
+ return IPv6.new(address)
+ else
+ raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
+ end
+ end
+
+ def initialize(address) # :nodoc:
+ unless address.kind_of?(String) && address.length == 16
+ raise ArgumentError.new('IPv6 address must be 16 bytes')
+ end
+ @address = address
+ end
+
+ ##
+ # The raw IPv6 address as a String.
+
+ attr_reader :address
+
+ def to_s # :nodoc:
+ sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn")).sub(/(^|:)0(:0)+(:|$)/, '::')
+ end
+
+ def inspect # :nodoc:
+ return "#<#{self.class} #{self}>"
+ end
+
+ ##
+ # Turns this IPv6 address into a Gem::Resolv::DNS::Name.
+ #--
+ # ip6.arpa should be searched too. [RFC3152]
+
+ def to_name
+ return DNS::Name.new(
+ @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
+ end
+
+ def ==(other) # :nodoc:
+ return @address == other.address
+ end
+
+ def eql?(other) # :nodoc:
+ return self == other
+ end
+
+ def hash # :nodoc:
+ return @address.hash
+ end
+ end
+
+ ##
+ # Gem::Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver. It blindly
+ # makes queries to the mDNS addresses without understanding anything about
+ # multicast ports.
+ #
+ # Information taken form the following places:
+ #
+ # * RFC 6762
+
+ class MDNS < DNS
+
+ ##
+ # Default mDNS Port
+
+ Port = 5353
+
+ ##
+ # Default IPv4 mDNS address
+
+ AddressV4 = '224.0.0.251'
+
+ ##
+ # Default IPv6 mDNS address
+
+ AddressV6 = 'ff02::fb'
+
+ ##
+ # Default mDNS addresses
+
+ Addresses = [
+ [AddressV4, Port],
+ [AddressV6, Port],
+ ]
+
+ ##
+ # Creates a new one-shot Multicast DNS (mDNS) resolver.
+ #
+ # +config_info+ can be:
+ #
+ # nil::
+ # Uses the default mDNS addresses
+ #
+ # Hash::
+ # Must contain :nameserver or :nameserver_port like
+ # Gem::Resolv::DNS#initialize.
+
+ def initialize(config_info=nil)
+ if config_info then
+ super({ nameserver_port: Addresses }.merge(config_info))
+ else
+ super(nameserver_port: Addresses)
+ end
+ end
+
+ ##
+ # Iterates over all IP addresses for +name+ retrieved from the mDNS
+ # resolver, provided name ends with "local". If the name does not end in
+ # "local" no records will be returned.
+ #
+ # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will
+ # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+
+ def each_address(name)
+ name = Gem::Resolv::DNS::Name.create(name)
+
+ return unless name[-1].to_s == 'local'
+
+ super(name)
+ end
+
+ def make_udp_requester # :nodoc:
+ nameserver_port = @config.nameserver_port
+ Requester::MDNSOneShot.new(*nameserver_port)
+ end
+
+ end
+
+ module LOC
+
+ ##
+ # A Gem::Resolv::LOC::Size
+
+ class Size
+
+ Regex = /^(\d+\.*\d*)[m]$/
+
+ ##
+ # Creates a new LOC::Size from +arg+ which may be:
+ #
+ # LOC::Size:: returns +arg+.
+ # String:: +arg+ must match the LOC::Size::Regex constant
+
+ def self.create(arg)
+ case arg
+ when Size
+ return arg
+ when String
+ scalar = ''
+ if Regex =~ arg
+ scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C")
+ else
+ raise ArgumentError.new("not a properly formed Size string: " + arg)
+ end
+ return Size.new(scalar)
+ else
+ raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}")
+ end
+ end
+
+ def initialize(scalar)
+ @scalar = scalar
+ end
+
+ ##
+ # The raw size
+
+ attr_reader :scalar
+
+ def to_s # :nodoc:
+ s = @scalar.unpack("H2").join.to_s
+ return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m"
+ end
+
+ def inspect # :nodoc:
+ return "#<#{self.class} #{self}>"
+ end
+
+ def ==(other) # :nodoc:
+ return @scalar == other.scalar
+ end
+
+ def eql?(other) # :nodoc:
+ return self == other
+ end
+
+ def hash # :nodoc:
+ return @scalar.hash
+ end
+
+ end
+
+ ##
+ # A Gem::Resolv::LOC::Coord
+
+ class Coord
+
+ Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
+
+ ##
+ # Creates a new LOC::Coord from +arg+ which may be:
+ #
+ # LOC::Coord:: returns +arg+.
+ # String:: +arg+ must match the LOC::Coord::Regex constant
+
+ def self.create(arg)
+ case arg
+ when Coord
+ return arg
+ when String
+ coordinates = ''
+ if Regex =~ arg && $1.to_f < 180
+ m = $~
+ hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1
+ coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) +
+ (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N")
+ orientation = m[4][/[NS]/] ? 'lat' : 'lon'
+ else
+ raise ArgumentError.new("not a properly formed Coord string: " + arg)
+ end
+ return Coord.new(coordinates,orientation)
+ else
+ raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}")
+ end
+ end
+
+ def initialize(coordinates,orientation)
+ unless coordinates.kind_of?(String)
+ raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
+ end
+ unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/]
+ raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"')
+ end
+ @coordinates = coordinates
+ @orientation = orientation
+ end
+
+ ##
+ # The raw coordinates
+
+ attr_reader :coordinates
+
+ ## The orientation of the hemisphere as 'lat' or 'lon'
+
+ attr_reader :orientation
+
+ def to_s # :nodoc:
+ c = @coordinates.unpack("N").join.to_i
+ val = (c - (2**31)).abs
+ fracsecs = (val % 1e3).to_i.to_s
+ val = val / 1e3
+ secs = (val % 60).to_i.to_s
+ val = val / 60
+ mins = (val % 60).to_i.to_s
+ degs = (val / 60).to_i.to_s
+ posi = (c >= 2**31)
+ case posi
+ when true
+ hemi = @orientation[/^lat$/] ? "N" : "E"
+ else
+ hemi = @orientation[/^lon$/] ? "W" : "S"
+ end
+ return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi
+ end
+
+ def inspect # :nodoc:
+ return "#<#{self.class} #{self}>"
+ end
+
+ def ==(other) # :nodoc:
+ return @coordinates == other.coordinates
+ end
+
+ def eql?(other) # :nodoc:
+ return self == other
+ end
+
+ def hash # :nodoc:
+ return @coordinates.hash
+ end
+
+ end
+
+ ##
+ # A Gem::Resolv::LOC::Alt
+
+ class Alt
+
+ Regex = /^([+-]*\d+\.*\d*)[m]$/
+
+ ##
+ # Creates a new LOC::Alt from +arg+ which may be:
+ #
+ # LOC::Alt:: returns +arg+.
+ # String:: +arg+ must match the LOC::Alt::Regex constant
+
+ def self.create(arg)
+ case arg
+ when Alt
+ return arg
+ when String
+ altitude = ''
+ if Regex =~ arg
+ altitude = [($1.to_f*(1e2))+(1e7)].pack("N")
+ else
+ raise ArgumentError.new("not a properly formed Alt string: " + arg)
+ end
+ return Alt.new(altitude)
+ else
+ raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}")
+ end
+ end
+
+ def initialize(altitude)
+ @altitude = altitude
+ end
+
+ ##
+ # The raw altitude
+
+ attr_reader :altitude
+
+ def to_s # :nodoc:
+ a = @altitude.unpack("N").join.to_i
+ return ((a.to_f/1e2)-1e5).to_s + "m"
+ end
+
+ def inspect # :nodoc:
+ return "#<#{self.class} #{self}>"
+ end
+
+ def ==(other) # :nodoc:
+ return @altitude == other.altitude
+ end
+
+ def eql?(other) # :nodoc:
+ return self == other
+ end
+
+ def hash # :nodoc:
+ return @altitude.hash
+ end
+
+ end
+
+ end
+
+ ##
+ # Default resolver to use for Gem::Resolv class methods.
+
+ DefaultResolver = self.new
+
+ ##
+ # Replaces the resolvers in the default resolver with +new_resolvers+. This
+ # allows resolvers to be changed for resolv-replace.
+
+ def DefaultResolver.replace_resolvers new_resolvers
+ @resolvers = new_resolvers
+ end
+
+ ##
+ # Address Regexp to use for matching IP addresses.
+
+ AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
+
+end
+
diff --git a/lib/rubygems/vendor/timeout/.document b/lib/rubygems/vendor/timeout/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/timeout/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/vendor/timeout/lib/timeout.rb b/lib/rubygems/vendor/timeout/lib/timeout.rb
new file mode 100644
index 0000000000..df97d64ca0
--- /dev/null
+++ b/lib/rubygems/vendor/timeout/lib/timeout.rb
@@ -0,0 +1,199 @@
+# frozen_string_literal: true
+# Timeout long-running blocks
+#
+# == Synopsis
+#
+# require 'rubygems/vendor/timeout/lib/timeout'
+# status = Gem::Timeout::timeout(5) {
+# # Something that should be interrupted if it takes more than 5 seconds...
+# }
+#
+# == Description
+#
+# Gem::Timeout provides a way to auto-terminate a potentially long-running
+# operation if it hasn't finished in a fixed amount of time.
+#
+# Previous versions didn't use a module for namespacing, however
+# #timeout is provided for backwards compatibility. You
+# should prefer Gem::Timeout.timeout instead.
+#
+# == Copyright
+#
+# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc.
+# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
+
+module Gem::Timeout
+ VERSION = "0.4.1"
+
+ # Internal error raised to when a timeout is triggered.
+ class ExitException < Exception
+ def exception(*)
+ self
+ end
+ end
+
+ # Raised by Gem::Timeout.timeout when the block times out.
+ class Error < RuntimeError
+ def self.handle_timeout(message)
+ exc = ExitException.new(message)
+
+ begin
+ yield exc
+ rescue ExitException => e
+ raise new(message) if exc.equal?(e)
+ raise
+ end
+ end
+ end
+
+ # :stopdoc:
+ CONDVAR = ConditionVariable.new
+ QUEUE = Queue.new
+ QUEUE_MUTEX = Mutex.new
+ TIMEOUT_THREAD_MUTEX = Mutex.new
+ @timeout_thread = nil
+ private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX
+
+ class Request
+ attr_reader :deadline
+
+ def initialize(thread, timeout, exception_class, message)
+ @thread = thread
+ @deadline = GET_TIME.call(Process::CLOCK_MONOTONIC) + timeout
+ @exception_class = exception_class
+ @message = message
+
+ @mutex = Mutex.new
+ @done = false # protected by @mutex
+ end
+
+ def done?
+ @mutex.synchronize do
+ @done
+ end
+ end
+
+ def expired?(now)
+ now >= @deadline
+ end
+
+ def interrupt
+ @mutex.synchronize do
+ unless @done
+ @thread.raise @exception_class, @message
+ @done = true
+ end
+ end
+ end
+
+ def finished
+ @mutex.synchronize do
+ @done = true
+ end
+ end
+ end
+ private_constant :Request
+
+ def self.create_timeout_thread
+ watcher = Thread.new do
+ requests = []
+ while true
+ until QUEUE.empty? and !requests.empty? # wait to have at least one request
+ req = QUEUE.pop
+ requests << req unless req.done?
+ end
+ closest_deadline = requests.min_by(&:deadline).deadline
+
+ now = 0.0
+ QUEUE_MUTEX.synchronize do
+ while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty?
+ CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now)
+ end
+ end
+
+ requests.each do |req|
+ req.interrupt if req.expired?(now)
+ end
+ requests.reject!(&:done?)
+ end
+ end
+ ThreadGroup::Default.add(watcher) unless watcher.group.enclosed?
+ watcher.name = "Gem::Timeout stdlib thread"
+ watcher.thread_variable_set(:"\0__detached_thread__", true)
+ watcher
+ end
+ private_class_method :create_timeout_thread
+
+ def self.ensure_timeout_thread_created
+ unless @timeout_thread and @timeout_thread.alive?
+ TIMEOUT_THREAD_MUTEX.synchronize do
+ unless @timeout_thread and @timeout_thread.alive?
+ @timeout_thread = create_timeout_thread
+ end
+ end
+ end
+ end
+
+ # We keep a private reference so that time mocking libraries won't break
+ # Gem::Timeout.
+ GET_TIME = Process.method(:clock_gettime)
+ private_constant :GET_TIME
+
+ # :startdoc:
+
+ # Perform an operation in a block, raising an error if it takes longer than
+ # +sec+ seconds to complete.
+ #
+ # +sec+:: Number of seconds to wait for the block to terminate. Any number
+ # may be used, including Floats to specify fractional seconds. A
+ # value of 0 or +nil+ will execute the block without any timeout.
+ # +klass+:: Exception Class to raise if the block fails to terminate
+ # in +sec+ seconds. Omitting will use the default, Gem::Timeout::Error
+ # +message+:: Error message to raise with Exception Class.
+ # Omitting will use the default, "execution expired"
+ #
+ # Returns the result of the block *if* the block completed before
+ # +sec+ seconds, otherwise throws an exception, based on the value of +klass+.
+ #
+ # The exception thrown to terminate the given block cannot be rescued inside
+ # the block unless +klass+ is given explicitly. However, the block can use
+ # ensure to prevent the handling of the exception. For that reason, this
+ # method cannot be relied on to enforce timeouts for untrusted blocks.
+ #
+ # If a scheduler is defined, it will be used to handle the timeout by invoking
+ # Scheduler#timeout_after.
+ #
+ # Note that this is both a method of module Gem::Timeout, so you can <tt>include
+ # Gem::Timeout</tt> into your classes so they have a #timeout method, as well as
+ # a module method, so you can call it directly as Gem::Timeout.timeout().
+ def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
+ return yield(sec) if sec == nil or sec.zero?
+
+ message ||= "execution expired"
+
+ if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after)
+ return scheduler.timeout_after(sec, klass || Error, message, &block)
+ end
+
+ Gem::Timeout.ensure_timeout_thread_created
+ perform = Proc.new do |exc|
+ request = Request.new(Thread.current, sec, exc, message)
+ QUEUE_MUTEX.synchronize do
+ QUEUE << request
+ CONDVAR.signal
+ end
+ begin
+ return yield(sec)
+ ensure
+ request.finished
+ end
+ end
+
+ if klass
+ perform.call(klass)
+ else
+ Error.handle_timeout(message, &perform)
+ end
+ end
+ module_function :timeout
+end
diff --git a/lib/rubygems/vendor/tsort/.document b/lib/rubygems/vendor/tsort/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/tsort/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/vendor/tsort/lib/tsort.rb b/lib/rubygems/vendor/tsort/lib/tsort.rb
new file mode 100644
index 0000000000..9dd7c09521
--- /dev/null
+++ b/lib/rubygems/vendor/tsort/lib/tsort.rb
@@ -0,0 +1,455 @@
+# frozen_string_literal: true
+
+#--
+# tsort.rb - provides a module for topological sorting and strongly connected components.
+#++
+#
+
+#
+# Gem::TSort implements topological sorting using Tarjan's algorithm for
+# strongly connected components.
+#
+# Gem::TSort is designed to be able to be used with any object which can be
+# interpreted as a directed graph.
+#
+# Gem::TSort requires two methods to interpret an object as a graph,
+# tsort_each_node and tsort_each_child.
+#
+# * tsort_each_node is used to iterate for all nodes over a graph.
+# * tsort_each_child is used to iterate for child nodes of a given node.
+#
+# The equality of nodes are defined by eql? and hash since
+# Gem::TSort uses Hash internally.
+#
+# == A Simple Example
+#
+# The following example demonstrates how to mix the Gem::TSort module into an
+# existing class (in this case, Hash). Here, we're treating each key in
+# the hash as a node in the graph, and so we simply alias the required
+# #tsort_each_node method to Hash's #each_key method. For each key in the
+# hash, the associated value is an array of the node's child nodes. This
+# choice in turn leads to our implementation of the required #tsort_each_child
+# method, which fetches the array of child nodes and then iterates over that
+# array using the user-supplied block.
+#
+# require 'rubygems/vendor/tsort/lib/tsort'
+#
+# class Hash
+# include Gem::TSort
+# alias tsort_each_node each_key
+# def tsort_each_child(node, &block)
+# fetch(node).each(&block)
+# end
+# end
+#
+# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort
+# #=> [3, 2, 1, 4]
+#
+# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components
+# #=> [[4], [2, 3], [1]]
+#
+# == A More Realistic Example
+#
+# A very simple `make' like tool can be implemented as follows:
+#
+# require 'rubygems/vendor/tsort/lib/tsort'
+#
+# class Make
+# def initialize
+# @dep = {}
+# @dep.default = []
+# end
+#
+# def rule(outputs, inputs=[], &block)
+# triple = [outputs, inputs, block]
+# outputs.each {|f| @dep[f] = [triple]}
+# @dep[triple] = inputs
+# end
+#
+# def build(target)
+# each_strongly_connected_component_from(target) {|ns|
+# if ns.length != 1
+# fs = ns.delete_if {|n| Array === n}
+# raise Gem::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}")
+# end
+# n = ns.first
+# if Array === n
+# outputs, inputs, block = n
+# inputs_time = inputs.map {|f| File.mtime f}.max
+# begin
+# outputs_time = outputs.map {|f| File.mtime f}.min
+# rescue Errno::ENOENT
+# outputs_time = nil
+# end
+# if outputs_time == nil ||
+# inputs_time != nil && outputs_time <= inputs_time
+# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i
+# block.call
+# end
+# end
+# }
+# end
+#
+# def tsort_each_child(node, &block)
+# @dep[node].each(&block)
+# end
+# include Gem::TSort
+# end
+#
+# def command(arg)
+# print arg, "\n"
+# system arg
+# end
+#
+# m = Make.new
+# m.rule(%w[t1]) { command 'date > t1' }
+# m.rule(%w[t2]) { command 'date > t2' }
+# m.rule(%w[t3]) { command 'date > t3' }
+# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' }
+# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' }
+# m.build('t5')
+#
+# == Bugs
+#
+# * 'tsort.rb' is wrong name because this library uses
+# Tarjan's algorithm for strongly connected components.
+# Although 'strongly_connected_components.rb' is correct but too long.
+#
+# == References
+#
+# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms",
+# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972.
+#
+
+module Gem::TSort
+
+ VERSION = "0.2.0"
+
+ class Cyclic < StandardError
+ end
+
+ # Returns a topologically sorted array of nodes.
+ # The array is sorted from children to parents, i.e.
+ # the first element has no child and the last node has no parent.
+ #
+ # If there is a cycle, Gem::TSort::Cyclic is raised.
+ #
+ # class G
+ # include Gem::TSort
+ # def initialize(g)
+ # @g = g
+ # end
+ # def tsort_each_child(n, &b) @g[n].each(&b) end
+ # def tsort_each_node(&b) @g.each_key(&b) end
+ # end
+ #
+ # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
+ # p graph.tsort #=> [4, 2, 3, 1]
+ #
+ # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
+ # p graph.tsort # raises Gem::TSort::Cyclic
+ #
+ def tsort
+ each_node = method(:tsort_each_node)
+ each_child = method(:tsort_each_child)
+ Gem::TSort.tsort(each_node, each_child)
+ end
+
+ # Returns a topologically sorted array of nodes.
+ # The array is sorted from children to parents, i.e.
+ # the first element has no child and the last node has no parent.
+ #
+ # The graph is represented by _each_node_ and _each_child_.
+ # _each_node_ should have +call+ method which yields for each node in the graph.
+ # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
+ #
+ # If there is a cycle, Gem::TSort::Cyclic is raised.
+ #
+ # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
+ # each_node = lambda {|&b| g.each_key(&b) }
+ # each_child = lambda {|n, &b| g[n].each(&b) }
+ # p Gem::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1]
+ #
+ # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
+ # each_node = lambda {|&b| g.each_key(&b) }
+ # each_child = lambda {|n, &b| g[n].each(&b) }
+ # p Gem::TSort.tsort(each_node, each_child) # raises Gem::TSort::Cyclic
+ #
+ def self.tsort(each_node, each_child)
+ tsort_each(each_node, each_child).to_a
+ end
+
+ # The iterator version of the #tsort method.
+ # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but
+ # modification of _obj_ during the iteration may lead to unexpected results.
+ #
+ # #tsort_each returns +nil+.
+ # If there is a cycle, Gem::TSort::Cyclic is raised.
+ #
+ # class G
+ # include Gem::TSort
+ # def initialize(g)
+ # @g = g
+ # end
+ # def tsort_each_child(n, &b) @g[n].each(&b) end
+ # def tsort_each_node(&b) @g.each_key(&b) end
+ # end
+ #
+ # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
+ # graph.tsort_each {|n| p n }
+ # #=> 4
+ # # 2
+ # # 3
+ # # 1
+ #
+ def tsort_each(&block) # :yields: node
+ each_node = method(:tsort_each_node)
+ each_child = method(:tsort_each_child)
+ Gem::TSort.tsort_each(each_node, each_child, &block)
+ end
+
+ # The iterator version of the Gem::TSort.tsort method.
+ #
+ # The graph is represented by _each_node_ and _each_child_.
+ # _each_node_ should have +call+ method which yields for each node in the graph.
+ # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
+ #
+ # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
+ # each_node = lambda {|&b| g.each_key(&b) }
+ # each_child = lambda {|n, &b| g[n].each(&b) }
+ # Gem::TSort.tsort_each(each_node, each_child) {|n| p n }
+ # #=> 4
+ # # 2
+ # # 3
+ # # 1
+ #
+ def self.tsort_each(each_node, each_child) # :yields: node
+ return to_enum(__method__, each_node, each_child) unless block_given?
+
+ each_strongly_connected_component(each_node, each_child) {|component|
+ if component.size == 1
+ yield component.first
+ else
+ raise Cyclic.new("topological sort failed: #{component.inspect}")
+ end
+ }
+ end
+
+ # Returns strongly connected components as an array of arrays of nodes.
+ # The array is sorted from children to parents.
+ # Each elements of the array represents a strongly connected component.
+ #
+ # class G
+ # include Gem::TSort
+ # def initialize(g)
+ # @g = g
+ # end
+ # def tsort_each_child(n, &b) @g[n].each(&b) end
+ # def tsort_each_node(&b) @g.each_key(&b) end
+ # end
+ #
+ # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
+ # p graph.strongly_connected_components #=> [[4], [2], [3], [1]]
+ #
+ # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
+ # p graph.strongly_connected_components #=> [[4], [2, 3], [1]]
+ #
+ def strongly_connected_components
+ each_node = method(:tsort_each_node)
+ each_child = method(:tsort_each_child)
+ Gem::TSort.strongly_connected_components(each_node, each_child)
+ end
+
+ # Returns strongly connected components as an array of arrays of nodes.
+ # The array is sorted from children to parents.
+ # Each elements of the array represents a strongly connected component.
+ #
+ # The graph is represented by _each_node_ and _each_child_.
+ # _each_node_ should have +call+ method which yields for each node in the graph.
+ # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
+ #
+ # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
+ # each_node = lambda {|&b| g.each_key(&b) }
+ # each_child = lambda {|n, &b| g[n].each(&b) }
+ # p Gem::TSort.strongly_connected_components(each_node, each_child)
+ # #=> [[4], [2], [3], [1]]
+ #
+ # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
+ # each_node = lambda {|&b| g.each_key(&b) }
+ # each_child = lambda {|n, &b| g[n].each(&b) }
+ # p Gem::TSort.strongly_connected_components(each_node, each_child)
+ # #=> [[4], [2, 3], [1]]
+ #
+ def self.strongly_connected_components(each_node, each_child)
+ each_strongly_connected_component(each_node, each_child).to_a
+ end
+
+ # The iterator version of the #strongly_connected_components method.
+ # <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to
+ # <tt><em>obj</em>.strongly_connected_components.each</tt>, but
+ # modification of _obj_ during the iteration may lead to unexpected results.
+ #
+ # #each_strongly_connected_component returns +nil+.
+ #
+ # class G
+ # include Gem::TSort
+ # def initialize(g)
+ # @g = g
+ # end
+ # def tsort_each_child(n, &b) @g[n].each(&b) end
+ # def tsort_each_node(&b) @g.each_key(&b) end
+ # end
+ #
+ # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
+ # graph.each_strongly_connected_component {|scc| p scc }
+ # #=> [4]
+ # # [2]
+ # # [3]
+ # # [1]
+ #
+ # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
+ # graph.each_strongly_connected_component {|scc| p scc }
+ # #=> [4]
+ # # [2, 3]
+ # # [1]
+ #
+ def each_strongly_connected_component(&block) # :yields: nodes
+ each_node = method(:tsort_each_node)
+ each_child = method(:tsort_each_child)
+ Gem::TSort.each_strongly_connected_component(each_node, each_child, &block)
+ end
+
+ # The iterator version of the Gem::TSort.strongly_connected_components method.
+ #
+ # The graph is represented by _each_node_ and _each_child_.
+ # _each_node_ should have +call+ method which yields for each node in the graph.
+ # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
+ #
+ # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
+ # each_node = lambda {|&b| g.each_key(&b) }
+ # each_child = lambda {|n, &b| g[n].each(&b) }
+ # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
+ # #=> [4]
+ # # [2]
+ # # [3]
+ # # [1]
+ #
+ # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
+ # each_node = lambda {|&b| g.each_key(&b) }
+ # each_child = lambda {|n, &b| g[n].each(&b) }
+ # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
+ # #=> [4]
+ # # [2, 3]
+ # # [1]
+ #
+ def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes
+ return to_enum(__method__, each_node, each_child) unless block_given?
+
+ id_map = {}
+ stack = []
+ each_node.call {|node|
+ unless id_map.include? node
+ each_strongly_connected_component_from(node, each_child, id_map, stack) {|c|
+ yield c
+ }
+ end
+ }
+ nil
+ end
+
+ # Iterates over strongly connected component in the subgraph reachable from
+ # _node_.
+ #
+ # Return value is unspecified.
+ #
+ # #each_strongly_connected_component_from doesn't call #tsort_each_node.
+ #
+ # class G
+ # include Gem::TSort
+ # def initialize(g)
+ # @g = g
+ # end
+ # def tsort_each_child(n, &b) @g[n].each(&b) end
+ # def tsort_each_node(&b) @g.each_key(&b) end
+ # end
+ #
+ # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
+ # graph.each_strongly_connected_component_from(2) {|scc| p scc }
+ # #=> [4]
+ # # [2]
+ #
+ # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
+ # graph.each_strongly_connected_component_from(2) {|scc| p scc }
+ # #=> [4]
+ # # [2, 3]
+ #
+ def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes
+ Gem::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block)
+ end
+
+ # Iterates over strongly connected components in a graph.
+ # The graph is represented by _node_ and _each_child_.
+ #
+ # _node_ is the first node.
+ # _each_child_ should have +call+ method which takes a node argument
+ # and yields for each child node.
+ #
+ # Return value is unspecified.
+ #
+ # #Gem::TSort.each_strongly_connected_component_from is a class method and
+ # it doesn't need a class to represent a graph which includes Gem::TSort.
+ #
+ # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
+ # each_child = lambda {|n, &b| graph[n].each(&b) }
+ # Gem::TSort.each_strongly_connected_component_from(1, each_child) {|scc|
+ # p scc
+ # }
+ # #=> [4]
+ # # [2, 3]
+ # # [1]
+ #
+ def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes
+ return to_enum(__method__, node, each_child, id_map, stack) unless block_given?
+
+ minimum_id = node_id = id_map[node] = id_map.size
+ stack_length = stack.length
+ stack << node
+
+ each_child.call(node) {|child|
+ if id_map.include? child
+ child_id = id_map[child]
+ minimum_id = child_id if child_id && child_id < minimum_id
+ else
+ sub_minimum_id =
+ each_strongly_connected_component_from(child, each_child, id_map, stack) {|c|
+ yield c
+ }
+ minimum_id = sub_minimum_id if sub_minimum_id < minimum_id
+ end
+ }
+
+ if node_id == minimum_id
+ component = stack.slice!(stack_length .. -1)
+ component.each {|n| id_map[n] = nil}
+ yield component
+ end
+
+ minimum_id
+ end
+
+ # Should be implemented by a extended class.
+ #
+ # #tsort_each_node is used to iterate for all nodes over a graph.
+ #
+ def tsort_each_node # :yields: node
+ raise NotImplementedError.new
+ end
+
+ # Should be implemented by a extended class.
+ #
+ # #tsort_each_child is used to iterate for child nodes of _node_.
+ #
+ def tsort_each_child(node) # :yields: child
+ raise NotImplementedError.new
+ end
+end
diff --git a/lib/rubygems/vendor/uri/.document b/lib/rubygems/vendor/uri/.document
new file mode 100644
index 0000000000..0c43bbd6b3
--- /dev/null
+++ b/lib/rubygems/vendor/uri/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/vendor/uri/lib/uri.rb b/lib/rubygems/vendor/uri/lib/uri.rb
new file mode 100644
index 0000000000..f1ccc167cc
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: false
+# Gem::URI is a module providing classes to handle Uniform Resource Identifiers
+# (RFC2396[http://tools.ietf.org/html/rfc2396]).
+#
+# == Features
+#
+# * Uniform way of handling URIs.
+# * Flexibility to introduce custom Gem::URI schemes.
+# * Flexibility to have an alternate Gem::URI::Parser (or just different patterns
+# and regexp's).
+#
+# == Basic example
+#
+# require 'rubygems/vendor/uri/lib/uri'
+#
+# uri = Gem::URI("http://foo.com/posts?id=30&limit=5#time=1305298413")
+# #=> #<Gem::URI::HTTP http://foo.com/posts?id=30&limit=5#time=1305298413>
+#
+# uri.scheme #=> "http"
+# uri.host #=> "foo.com"
+# uri.path #=> "/posts"
+# uri.query #=> "id=30&limit=5"
+# uri.fragment #=> "time=1305298413"
+#
+# uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413"
+#
+# == Adding custom URIs
+#
+# module Gem::URI
+# class RSYNC < Generic
+# DEFAULT_PORT = 873
+# end
+# register_scheme 'RSYNC', RSYNC
+# end
+# #=> Gem::URI::RSYNC
+#
+# Gem::URI.scheme_list
+# #=> {"FILE"=>Gem::URI::File, "FTP"=>Gem::URI::FTP, "HTTP"=>Gem::URI::HTTP,
+# # "HTTPS"=>Gem::URI::HTTPS, "LDAP"=>Gem::URI::LDAP, "LDAPS"=>Gem::URI::LDAPS,
+# # "MAILTO"=>Gem::URI::MailTo, "RSYNC"=>Gem::URI::RSYNC}
+#
+# uri = Gem::URI("rsync://rsync.foo.com")
+# #=> #<Gem::URI::RSYNC rsync://rsync.foo.com>
+#
+# == RFC References
+#
+# A good place to view an RFC spec is http://www.ietf.org/rfc.html.
+#
+# Here is a list of all related RFC's:
+# - RFC822[http://tools.ietf.org/html/rfc822]
+# - RFC1738[http://tools.ietf.org/html/rfc1738]
+# - RFC2255[http://tools.ietf.org/html/rfc2255]
+# - RFC2368[http://tools.ietf.org/html/rfc2368]
+# - RFC2373[http://tools.ietf.org/html/rfc2373]
+# - RFC2396[http://tools.ietf.org/html/rfc2396]
+# - RFC2732[http://tools.ietf.org/html/rfc2732]
+# - RFC3986[http://tools.ietf.org/html/rfc3986]
+#
+# == Class tree
+#
+# - Gem::URI::Generic (in uri/generic.rb)
+# - Gem::URI::File - (in uri/file.rb)
+# - Gem::URI::FTP - (in uri/ftp.rb)
+# - Gem::URI::HTTP - (in uri/http.rb)
+# - Gem::URI::HTTPS - (in uri/https.rb)
+# - Gem::URI::LDAP - (in uri/ldap.rb)
+# - Gem::URI::LDAPS - (in uri/ldaps.rb)
+# - Gem::URI::MailTo - (in uri/mailto.rb)
+# - Gem::URI::Parser - (in uri/common.rb)
+# - Gem::URI::REGEXP - (in uri/common.rb)
+# - Gem::URI::REGEXP::PATTERN - (in uri/common.rb)
+# - Gem::URI::Util - (in uri/common.rb)
+# - Gem::URI::Error - (in uri/common.rb)
+# - Gem::URI::InvalidURIError - (in uri/common.rb)
+# - Gem::URI::InvalidComponentError - (in uri/common.rb)
+# - Gem::URI::BadURIError - (in uri/common.rb)
+#
+# == Copyright Info
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# Documentation::
+# Akira Yamada <akira@ruby-lang.org>
+# Dmitry V. Sabanin <sdmitry@lrn.ru>
+# Vincent Batts <vbatts@hashbangbash.com>
+# License::
+# Copyright (c) 2001 akira yamada <akira@ruby-lang.org>
+# You can redistribute it and/or modify it under the same term as Ruby.
+#
+
+module Gem::URI
+end
+
+require_relative 'uri/version'
+require_relative 'uri/common'
+require_relative 'uri/generic'
+require_relative 'uri/file'
+require_relative 'uri/ftp'
+require_relative 'uri/http'
+require_relative 'uri/https'
+require_relative 'uri/ldap'
+require_relative 'uri/ldaps'
+require_relative 'uri/mailto'
+require_relative 'uri/ws'
+require_relative 'uri/wss'
diff --git a/lib/rubygems/vendor/uri/lib/uri/common.rb b/lib/rubygems/vendor/uri/lib/uri/common.rb
new file mode 100644
index 0000000000..921fb9dd28
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/common.rb
@@ -0,0 +1,853 @@
+# frozen_string_literal: true
+#--
+# = uri/common.rb
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License::
+# You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative "rfc2396_parser"
+require_relative "rfc3986_parser"
+
+module Gem::URI
+ include RFC2396_REGEXP
+
+ REGEXP = RFC2396_REGEXP
+ Parser = RFC2396_Parser
+ RFC3986_PARSER = RFC3986_Parser.new
+ Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor)
+
+ # Gem::URI::Parser.new
+ DEFAULT_PARSER = Parser.new
+ DEFAULT_PARSER.pattern.each_pair do |sym, str|
+ unless REGEXP::PATTERN.const_defined?(sym)
+ REGEXP::PATTERN.const_set(sym, str)
+ end
+ end
+ DEFAULT_PARSER.regexp.each_pair do |sym, str|
+ const_set(sym, str)
+ end
+ Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor)
+
+ module Util # :nodoc:
+ def make_components_hash(klass, array_hash)
+ tmp = {}
+ if array_hash.kind_of?(Array) &&
+ array_hash.size == klass.component.size - 1
+ klass.component[1..-1].each_index do |i|
+ begin
+ tmp[klass.component[i + 1]] = array_hash[i].clone
+ rescue TypeError
+ tmp[klass.component[i + 1]] = array_hash[i]
+ end
+ end
+
+ elsif array_hash.kind_of?(Hash)
+ array_hash.each do |key, value|
+ begin
+ tmp[key] = value.clone
+ rescue TypeError
+ tmp[key] = value
+ end
+ end
+ else
+ raise ArgumentError,
+ "expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})"
+ end
+ tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase
+
+ return tmp
+ end
+ module_function :make_components_hash
+ end
+
+ module Schemes
+ end
+ private_constant :Schemes
+
+ # Registers the given +klass+ as the class to be instantiated
+ # when parsing a \Gem::URI with the given +scheme+:
+ #
+ # Gem::URI.register_scheme('MS_SEARCH', Gem::URI::Generic) # => Gem::URI::Generic
+ # Gem::URI.scheme_list['MS_SEARCH'] # => Gem::URI::Generic
+ #
+ # Note that after calling String#upcase on +scheme+, it must be a valid
+ # constant name.
+ def self.register_scheme(scheme, klass)
+ Schemes.const_set(scheme.to_s.upcase, klass)
+ end
+
+ # Returns a hash of the defined schemes:
+ #
+ # Gem::URI.scheme_list
+ # # =>
+ # {"MAILTO"=>Gem::URI::MailTo,
+ # "LDAPS"=>Gem::URI::LDAPS,
+ # "WS"=>Gem::URI::WS,
+ # "HTTP"=>Gem::URI::HTTP,
+ # "HTTPS"=>Gem::URI::HTTPS,
+ # "LDAP"=>Gem::URI::LDAP,
+ # "FILE"=>Gem::URI::File,
+ # "FTP"=>Gem::URI::FTP}
+ #
+ # Related: Gem::URI.register_scheme.
+ def self.scheme_list
+ Schemes.constants.map { |name|
+ [name.to_s.upcase, Schemes.const_get(name)]
+ }.to_h
+ end
+
+ INITIAL_SCHEMES = scheme_list
+ private_constant :INITIAL_SCHEMES
+ Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
+
+ # Returns a new object constructed from the given +scheme+, +arguments+,
+ # and +default+:
+ #
+ # - The new object is an instance of <tt>Gem::URI.scheme_list[scheme.upcase]</tt>.
+ # - The object is initialized by calling the class initializer
+ # using +scheme+ and +arguments+.
+ # See Gem::URI::Generic.new.
+ #
+ # Examples:
+ #
+ # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top']
+ # Gem::URI.for('https', *values)
+ # # => #<Gem::URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
+ # Gem::URI.for('foo', *values, default: Gem::URI::HTTP)
+ # # => #<Gem::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
+ #
+ def self.for(scheme, *arguments, default: Generic)
+ const_name = scheme.to_s.upcase
+
+ uri_class = INITIAL_SCHEMES[const_name]
+ uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
+ Schemes.const_get(const_name, false)
+ end
+ uri_class ||= default
+
+ return uri_class.new(scheme, *arguments)
+ end
+
+ #
+ # Base class for all Gem::URI exceptions.
+ #
+ class Error < StandardError; end
+ #
+ # Not a Gem::URI.
+ #
+ class InvalidURIError < Error; end
+ #
+ # Not a Gem::URI component.
+ #
+ class InvalidComponentError < Error; end
+ #
+ # Gem::URI is valid, bad usage is not.
+ #
+ class BadURIError < Error; end
+
+ # Returns a 9-element array representing the parts of the \Gem::URI
+ # formed from the string +uri+;
+ # each array element is a string or +nil+:
+ #
+ # names = %w[scheme userinfo host port registry path opaque query fragment]
+ # values = Gem::URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
+ # names.zip(values)
+ # # =>
+ # [["scheme", "https"],
+ # ["userinfo", "john.doe"],
+ # ["host", "www.example.com"],
+ # ["port", "123"],
+ # ["registry", nil],
+ # ["path", "/forum/questions/"],
+ # ["opaque", nil],
+ # ["query", "tag=networking&order=newest"],
+ # ["fragment", "top"]]
+ #
+ def self.split(uri)
+ RFC3986_PARSER.split(uri)
+ end
+
+ # Returns a new \Gem::URI object constructed from the given string +uri+:
+ #
+ # Gem::URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
+ # # => #<Gem::URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
+ # Gem::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
+ # # => #<Gem::URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
+ #
+ # It's recommended to first ::escape string +uri+
+ # if it may contain invalid Gem::URI characters.
+ #
+ def self.parse(uri)
+ RFC3986_PARSER.parse(uri)
+ end
+
+ # Merges the given Gem::URI strings +str+
+ # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html].
+ #
+ # Each string in +str+ is converted to an
+ # {RFC3986 Gem::URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged.
+ #
+ # Examples:
+ #
+ # Gem::URI.join("http://example.com/","main.rbx")
+ # # => #<Gem::URI::HTTP http://example.com/main.rbx>
+ #
+ # Gem::URI.join('http://example.com', 'foo')
+ # # => #<Gem::URI::HTTP http://example.com/foo>
+ #
+ # Gem::URI.join('http://example.com', '/foo', '/bar')
+ # # => #<Gem::URI::HTTP http://example.com/bar>
+ #
+ # Gem::URI.join('http://example.com', '/foo', 'bar')
+ # # => #<Gem::URI::HTTP http://example.com/bar>
+ #
+ # Gem::URI.join('http://example.com', '/foo/', 'bar')
+ # # => #<Gem::URI::HTTP http://example.com/foo/bar>
+ #
+ def self.join(*str)
+ RFC3986_PARSER.join(*str)
+ end
+
+ #
+ # == Synopsis
+ #
+ # Gem::URI::extract(str[, schemes][,&blk])
+ #
+ # == Args
+ #
+ # +str+::
+ # String to extract URIs from.
+ # +schemes+::
+ # Limit Gem::URI matching to specific schemes.
+ #
+ # == Description
+ #
+ # Extracts URIs from a string. If block given, iterates through all matched URIs.
+ # Returns nil if block given or array with matches.
+ #
+ # == Usage
+ #
+ # require "rubygems/vendor/uri/lib/uri"
+ #
+ # Gem::URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.")
+ # # => ["http://foo.example.com/bla", "mailto:test@example.com"]
+ #
+ def self.extract(str, schemes = nil, &block) # :nodoc:
+ warn "Gem::URI.extract is obsolete", uplevel: 1 if $VERBOSE
+ DEFAULT_PARSER.extract(str, schemes, &block)
+ end
+
+ #
+ # == Synopsis
+ #
+ # Gem::URI::regexp([match_schemes])
+ #
+ # == Args
+ #
+ # +match_schemes+::
+ # Array of schemes. If given, resulting regexp matches to URIs
+ # whose scheme is one of the match_schemes.
+ #
+ # == Description
+ #
+ # Returns a Regexp object which matches to Gem::URI-like strings.
+ # The Regexp object returned by this method includes arbitrary
+ # number of capture group (parentheses). Never rely on its number.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # # extract first Gem::URI from html_string
+ # html_string.slice(Gem::URI.regexp)
+ #
+ # # remove ftp URIs
+ # html_string.sub(Gem::URI.regexp(['ftp']), '')
+ #
+ # # You should not rely on the number of parentheses
+ # html_string.scan(Gem::URI.regexp) do |*matches|
+ # p $&
+ # end
+ #
+ def self.regexp(schemes = nil)# :nodoc:
+ warn "Gem::URI.regexp is obsolete", uplevel: 1 if $VERBOSE
+ DEFAULT_PARSER.make_regexp(schemes)
+ end
+
+ TBLENCWWWCOMP_ = {} # :nodoc:
+ 256.times do |i|
+ TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i)
+ end
+ TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze
+ TBLENCWWWCOMP_[' '] = '+'
+ TBLENCWWWCOMP_.freeze
+ TBLDECWWWCOMP_ = {} # :nodoc:
+ 256.times do |i|
+ h, l = i>>4, i&15
+ TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr
+ TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr
+ TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr
+ TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr
+ end
+ TBLDECWWWCOMP_['+'] = ' '
+ TBLDECWWWCOMP_.freeze
+
+ # Returns a URL-encoded string derived from the given string +str+.
+ #
+ # The returned string:
+ #
+ # - Preserves:
+ #
+ # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
+ # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
+ # and <tt>'0'..'9'</tt>.
+ #
+ # Example:
+ #
+ # Gem::URI.encode_www_form_component('*.-_azAZ09')
+ # # => "*.-_azAZ09"
+ #
+ # - Converts:
+ #
+ # - Character <tt>' '</tt> to character <tt>'+'</tt>.
+ # - Any other character to "percent notation";
+ # the percent notation for character <i>c</i> is <tt>'%%%X' % c.ord</tt>.
+ #
+ # Example:
+ #
+ # Gem::URI.encode_www_form_component('Here are some punctuation characters: ,;?:')
+ # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A"
+ #
+ # Encoding:
+ #
+ # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored.
+ # - Otherwise +str+ is converted first to Encoding::UTF_8
+ # (with suitable character replacements),
+ # and then to encoding +enc+.
+ #
+ # In either case, the returned string has forced encoding Encoding::US_ASCII.
+ #
+ # Related: Gem::URI.encode_uri_component (encodes <tt>' '</tt> as <tt>'%20'</tt>).
+ def self.encode_www_form_component(str, enc=nil)
+ _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc)
+ end
+
+ # Returns a string decoded from the given \URL-encoded string +str+.
+ #
+ # The given string is first encoded as Encoding::ASCII-8BIT (using String#b),
+ # then decoded (as below), and finally force-encoded to the given encoding +enc+.
+ #
+ # The returned string:
+ #
+ # - Preserves:
+ #
+ # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
+ # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
+ # and <tt>'0'..'9'</tt>.
+ #
+ # Example:
+ #
+ # Gem::URI.decode_www_form_component('*.-_azAZ09')
+ # # => "*.-_azAZ09"
+ #
+ # - Converts:
+ #
+ # - Character <tt>'+'</tt> to character <tt>' '</tt>.
+ # - Each "percent notation" to an ASCII character.
+ #
+ # Example:
+ #
+ # Gem::URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A')
+ # # => "Here are some punctuation characters: ,;?:"
+ #
+ # Related: Gem::URI.decode_uri_component (preserves <tt>'+'</tt>).
+ def self.decode_www_form_component(str, enc=Encoding::UTF_8)
+ _decode_uri_component(/\+|%\h\h/, str, enc)
+ end
+
+ # Like Gem::URI.encode_www_form_component, except that <tt>' '</tt> (space)
+ # is encoded as <tt>'%20'</tt> (instead of <tt>'+'</tt>).
+ def self.encode_uri_component(str, enc=nil)
+ _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc)
+ end
+
+ # Like Gem::URI.decode_www_form_component, except that <tt>'+'</tt> is preserved.
+ def self.decode_uri_component(str, enc=Encoding::UTF_8)
+ _decode_uri_component(/%\h\h/, str, enc)
+ end
+
+ def self._encode_uri_component(regexp, table, str, enc)
+ str = str.to_s.dup
+ if str.encoding != Encoding::ASCII_8BIT
+ if enc && enc != Encoding::ASCII_8BIT
+ str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
+ str.encode!(enc, fallback: ->(x){"&##{x.ord};"})
+ end
+ str.force_encoding(Encoding::ASCII_8BIT)
+ end
+ str.gsub!(regexp, table)
+ str.force_encoding(Encoding::US_ASCII)
+ end
+ private_class_method :_encode_uri_component
+
+ def self._decode_uri_component(regexp, str, enc)
+ raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
+ str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
+ end
+ private_class_method :_decode_uri_component
+
+ # Returns a URL-encoded string derived from the given
+ # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes]
+ # +enum+.
+ #
+ # The result is suitable for use as form data
+ # for an \HTTP request whose <tt>Content-Type</tt> is
+ # <tt>'application/x-www-form-urlencoded'</tt>.
+ #
+ # The returned string consists of the elements of +enum+,
+ # each converted to one or more URL-encoded strings,
+ # and all joined with character <tt>'&'</tt>.
+ #
+ # Simple examples:
+ #
+ # Gem::URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]])
+ # # => "foo=0&bar=1&baz=2"
+ # Gem::URI.encode_www_form({foo: 0, bar: 1, baz: 2})
+ # # => "foo=0&bar=1&baz=2"
+ #
+ # The returned string is formed using method Gem::URI.encode_www_form_component,
+ # which converts certain characters:
+ #
+ # Gem::URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@')
+ # # => "f%23o=%2F&b-r=%24&b+z=%40"
+ #
+ # When +enum+ is Array-like, each element +ele+ is converted to a field:
+ #
+ # - If +ele+ is an array of two or more elements,
+ # the field is formed from its first two elements
+ # (and any additional elements are ignored):
+ #
+ # name = Gem::URI.encode_www_form_component(ele[0], enc)
+ # value = Gem::URI.encode_www_form_component(ele[1], enc)
+ # "#{name}=#{value}"
+ #
+ # Examples:
+ #
+ # Gem::URI.encode_www_form([%w[foo bar], %w[baz bat bah]])
+ # # => "foo=bar&baz=bat"
+ # Gem::URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']])
+ # # => "foo=0&bar=baz"
+ #
+ # - If +ele+ is an array of one element,
+ # the field is formed from <tt>ele[0]</tt>:
+ #
+ # Gem::URI.encode_www_form_component(ele[0])
+ #
+ # Example:
+ #
+ # Gem::URI.encode_www_form([['foo'], [:bar], [0]])
+ # # => "foo&bar&0"
+ #
+ # - Otherwise the field is formed from +ele+:
+ #
+ # Gem::URI.encode_www_form_component(ele)
+ #
+ # Example:
+ #
+ # Gem::URI.encode_www_form(['foo', :bar, 0])
+ # # => "foo&bar&0"
+ #
+ # The elements of an Array-like +enum+ may be mixture:
+ #
+ # Gem::URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat])
+ # # => "foo=0&bar=1&baz&bat"
+ #
+ # When +enum+ is Hash-like,
+ # each +key+/+value+ pair is converted to one or more fields:
+ #
+ # - If +value+ is
+ # {Array-convertible}[https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html#label-Array-Convertible+Objects],
+ # each element +ele+ in +value+ is paired with +key+ to form a field:
+ #
+ # name = Gem::URI.encode_www_form_component(key, enc)
+ # value = Gem::URI.encode_www_form_component(ele, enc)
+ # "#{name}=#{value}"
+ #
+ # Example:
+ #
+ # Gem::URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]})
+ # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2"
+ #
+ # - Otherwise, +key+ and +value+ are paired to form a field:
+ #
+ # name = Gem::URI.encode_www_form_component(key, enc)
+ # value = Gem::URI.encode_www_form_component(value, enc)
+ # "#{name}=#{value}"
+ #
+ # Example:
+ #
+ # Gem::URI.encode_www_form({foo: 0, bar: 1, baz: 2})
+ # # => "foo=0&bar=1&baz=2"
+ #
+ # The elements of a Hash-like +enum+ may be mixture:
+ #
+ # Gem::URI.encode_www_form({foo: [0, 1], bar: 2})
+ # # => "foo=0&foo=1&bar=2"
+ #
+ def self.encode_www_form(enum, enc=nil)
+ enum.map do |k,v|
+ if v.nil?
+ encode_www_form_component(k, enc)
+ elsif v.respond_to?(:to_ary)
+ v.to_ary.map do |w|
+ str = encode_www_form_component(k, enc)
+ unless w.nil?
+ str << '='
+ str << encode_www_form_component(w, enc)
+ end
+ end.join('&')
+ else
+ str = encode_www_form_component(k, enc)
+ str << '='
+ str << encode_www_form_component(v, enc)
+ end
+ end.join('&')
+ end
+
+ # Returns name/value pairs derived from the given string +str+,
+ # which must be an ASCII string.
+ #
+ # The method may be used to decode the body of Net::HTTPResponse object +res+
+ # for which <tt>res['Content-Type']</tt> is <tt>'application/x-www-form-urlencoded'</tt>.
+ #
+ # The returned data is an array of 2-element subarrays;
+ # each subarray is a name/value pair (both are strings).
+ # Each returned string has encoding +enc+,
+ # and has had invalid characters removed via
+ # {String#scrub}[https://docs.ruby-lang.org/en/master/String.html#method-i-scrub].
+ #
+ # A simple example:
+ #
+ # Gem::URI.decode_www_form('foo=0&bar=1&baz')
+ # # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
+ #
+ # The returned strings have certain conversions,
+ # similar to those performed in Gem::URI.decode_www_form_component:
+ #
+ # Gem::URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40')
+ # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]]
+ #
+ # The given string may contain consecutive separators:
+ #
+ # Gem::URI.decode_www_form('foo=0&&bar=1&&baz=2')
+ # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]]
+ #
+ # A different separator may be specified:
+ #
+ # Gem::URI.decode_www_form('foo=0--bar=1--baz', separator: '--')
+ # # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
+ #
+ def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false)
+ raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only?
+ ary = []
+ return ary if str.empty?
+ enc = Encoding.find(enc)
+ str.b.each_line(separator) do |string|
+ string.chomp!(separator)
+ key, sep, val = string.partition('=')
+ if isindex
+ if sep.empty?
+ val = key
+ key = +''
+ end
+ isindex = false
+ end
+
+ if use__charset_ and key == '_charset_' and e = get_encoding(val)
+ enc = e
+ use__charset_ = false
+ end
+
+ key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
+ if val
+ val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
+ else
+ val = +''
+ end
+
+ ary << [key, val]
+ end
+ ary.each do |k, v|
+ k.force_encoding(enc)
+ k.scrub!
+ v.force_encoding(enc)
+ v.scrub!
+ end
+ ary
+ end
+
+ private
+=begin command for WEB_ENCODINGS_
+ curl https://encoding.spec.whatwg.org/encodings.json|
+ ruby -rjson -e 'H={}
+ h={
+ "shift_jis"=>"Windows-31J",
+ "euc-jp"=>"cp51932",
+ "iso-2022-jp"=>"cp50221",
+ "x-mac-cyrillic"=>"macCyrillic",
+ }
+ JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x|
+ Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next
+ x["labels"].each{|y|H[y]=n}
+ }
+ puts "{"
+ H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]}
+ puts "}"
+'
+=end
+ WEB_ENCODINGS_ = {
+ "unicode-1-1-utf-8"=>"utf-8",
+ "utf-8"=>"utf-8",
+ "utf8"=>"utf-8",
+ "866"=>"ibm866",
+ "cp866"=>"ibm866",
+ "csibm866"=>"ibm866",
+ "ibm866"=>"ibm866",
+ "csisolatin2"=>"iso-8859-2",
+ "iso-8859-2"=>"iso-8859-2",
+ "iso-ir-101"=>"iso-8859-2",
+ "iso8859-2"=>"iso-8859-2",
+ "iso88592"=>"iso-8859-2",
+ "iso_8859-2"=>"iso-8859-2",
+ "iso_8859-2:1987"=>"iso-8859-2",
+ "l2"=>"iso-8859-2",
+ "latin2"=>"iso-8859-2",
+ "csisolatin3"=>"iso-8859-3",
+ "iso-8859-3"=>"iso-8859-3",
+ "iso-ir-109"=>"iso-8859-3",
+ "iso8859-3"=>"iso-8859-3",
+ "iso88593"=>"iso-8859-3",
+ "iso_8859-3"=>"iso-8859-3",
+ "iso_8859-3:1988"=>"iso-8859-3",
+ "l3"=>"iso-8859-3",
+ "latin3"=>"iso-8859-3",
+ "csisolatin4"=>"iso-8859-4",
+ "iso-8859-4"=>"iso-8859-4",
+ "iso-ir-110"=>"iso-8859-4",
+ "iso8859-4"=>"iso-8859-4",
+ "iso88594"=>"iso-8859-4",
+ "iso_8859-4"=>"iso-8859-4",
+ "iso_8859-4:1988"=>"iso-8859-4",
+ "l4"=>"iso-8859-4",
+ "latin4"=>"iso-8859-4",
+ "csisolatincyrillic"=>"iso-8859-5",
+ "cyrillic"=>"iso-8859-5",
+ "iso-8859-5"=>"iso-8859-5",
+ "iso-ir-144"=>"iso-8859-5",
+ "iso8859-5"=>"iso-8859-5",
+ "iso88595"=>"iso-8859-5",
+ "iso_8859-5"=>"iso-8859-5",
+ "iso_8859-5:1988"=>"iso-8859-5",
+ "arabic"=>"iso-8859-6",
+ "asmo-708"=>"iso-8859-6",
+ "csiso88596e"=>"iso-8859-6",
+ "csiso88596i"=>"iso-8859-6",
+ "csisolatinarabic"=>"iso-8859-6",
+ "ecma-114"=>"iso-8859-6",
+ "iso-8859-6"=>"iso-8859-6",
+ "iso-8859-6-e"=>"iso-8859-6",
+ "iso-8859-6-i"=>"iso-8859-6",
+ "iso-ir-127"=>"iso-8859-6",
+ "iso8859-6"=>"iso-8859-6",
+ "iso88596"=>"iso-8859-6",
+ "iso_8859-6"=>"iso-8859-6",
+ "iso_8859-6:1987"=>"iso-8859-6",
+ "csisolatingreek"=>"iso-8859-7",
+ "ecma-118"=>"iso-8859-7",
+ "elot_928"=>"iso-8859-7",
+ "greek"=>"iso-8859-7",
+ "greek8"=>"iso-8859-7",
+ "iso-8859-7"=>"iso-8859-7",
+ "iso-ir-126"=>"iso-8859-7",
+ "iso8859-7"=>"iso-8859-7",
+ "iso88597"=>"iso-8859-7",
+ "iso_8859-7"=>"iso-8859-7",
+ "iso_8859-7:1987"=>"iso-8859-7",
+ "sun_eu_greek"=>"iso-8859-7",
+ "csiso88598e"=>"iso-8859-8",
+ "csisolatinhebrew"=>"iso-8859-8",
+ "hebrew"=>"iso-8859-8",
+ "iso-8859-8"=>"iso-8859-8",
+ "iso-8859-8-e"=>"iso-8859-8",
+ "iso-ir-138"=>"iso-8859-8",
+ "iso8859-8"=>"iso-8859-8",
+ "iso88598"=>"iso-8859-8",
+ "iso_8859-8"=>"iso-8859-8",
+ "iso_8859-8:1988"=>"iso-8859-8",
+ "visual"=>"iso-8859-8",
+ "csisolatin6"=>"iso-8859-10",
+ "iso-8859-10"=>"iso-8859-10",
+ "iso-ir-157"=>"iso-8859-10",
+ "iso8859-10"=>"iso-8859-10",
+ "iso885910"=>"iso-8859-10",
+ "l6"=>"iso-8859-10",
+ "latin6"=>"iso-8859-10",
+ "iso-8859-13"=>"iso-8859-13",
+ "iso8859-13"=>"iso-8859-13",
+ "iso885913"=>"iso-8859-13",
+ "iso-8859-14"=>"iso-8859-14",
+ "iso8859-14"=>"iso-8859-14",
+ "iso885914"=>"iso-8859-14",
+ "csisolatin9"=>"iso-8859-15",
+ "iso-8859-15"=>"iso-8859-15",
+ "iso8859-15"=>"iso-8859-15",
+ "iso885915"=>"iso-8859-15",
+ "iso_8859-15"=>"iso-8859-15",
+ "l9"=>"iso-8859-15",
+ "iso-8859-16"=>"iso-8859-16",
+ "cskoi8r"=>"koi8-r",
+ "koi"=>"koi8-r",
+ "koi8"=>"koi8-r",
+ "koi8-r"=>"koi8-r",
+ "koi8_r"=>"koi8-r",
+ "koi8-ru"=>"koi8-u",
+ "koi8-u"=>"koi8-u",
+ "dos-874"=>"windows-874",
+ "iso-8859-11"=>"windows-874",
+ "iso8859-11"=>"windows-874",
+ "iso885911"=>"windows-874",
+ "tis-620"=>"windows-874",
+ "windows-874"=>"windows-874",
+ "cp1250"=>"windows-1250",
+ "windows-1250"=>"windows-1250",
+ "x-cp1250"=>"windows-1250",
+ "cp1251"=>"windows-1251",
+ "windows-1251"=>"windows-1251",
+ "x-cp1251"=>"windows-1251",
+ "ansi_x3.4-1968"=>"windows-1252",
+ "ascii"=>"windows-1252",
+ "cp1252"=>"windows-1252",
+ "cp819"=>"windows-1252",
+ "csisolatin1"=>"windows-1252",
+ "ibm819"=>"windows-1252",
+ "iso-8859-1"=>"windows-1252",
+ "iso-ir-100"=>"windows-1252",
+ "iso8859-1"=>"windows-1252",
+ "iso88591"=>"windows-1252",
+ "iso_8859-1"=>"windows-1252",
+ "iso_8859-1:1987"=>"windows-1252",
+ "l1"=>"windows-1252",
+ "latin1"=>"windows-1252",
+ "us-ascii"=>"windows-1252",
+ "windows-1252"=>"windows-1252",
+ "x-cp1252"=>"windows-1252",
+ "cp1253"=>"windows-1253",
+ "windows-1253"=>"windows-1253",
+ "x-cp1253"=>"windows-1253",
+ "cp1254"=>"windows-1254",
+ "csisolatin5"=>"windows-1254",
+ "iso-8859-9"=>"windows-1254",
+ "iso-ir-148"=>"windows-1254",
+ "iso8859-9"=>"windows-1254",
+ "iso88599"=>"windows-1254",
+ "iso_8859-9"=>"windows-1254",
+ "iso_8859-9:1989"=>"windows-1254",
+ "l5"=>"windows-1254",
+ "latin5"=>"windows-1254",
+ "windows-1254"=>"windows-1254",
+ "x-cp1254"=>"windows-1254",
+ "cp1255"=>"windows-1255",
+ "windows-1255"=>"windows-1255",
+ "x-cp1255"=>"windows-1255",
+ "cp1256"=>"windows-1256",
+ "windows-1256"=>"windows-1256",
+ "x-cp1256"=>"windows-1256",
+ "cp1257"=>"windows-1257",
+ "windows-1257"=>"windows-1257",
+ "x-cp1257"=>"windows-1257",
+ "cp1258"=>"windows-1258",
+ "windows-1258"=>"windows-1258",
+ "x-cp1258"=>"windows-1258",
+ "x-mac-cyrillic"=>"macCyrillic",
+ "x-mac-ukrainian"=>"macCyrillic",
+ "chinese"=>"gbk",
+ "csgb2312"=>"gbk",
+ "csiso58gb231280"=>"gbk",
+ "gb2312"=>"gbk",
+ "gb_2312"=>"gbk",
+ "gb_2312-80"=>"gbk",
+ "gbk"=>"gbk",
+ "iso-ir-58"=>"gbk",
+ "x-gbk"=>"gbk",
+ "gb18030"=>"gb18030",
+ "big5"=>"big5",
+ "big5-hkscs"=>"big5",
+ "cn-big5"=>"big5",
+ "csbig5"=>"big5",
+ "x-x-big5"=>"big5",
+ "cseucpkdfmtjapanese"=>"cp51932",
+ "euc-jp"=>"cp51932",
+ "x-euc-jp"=>"cp51932",
+ "csiso2022jp"=>"cp50221",
+ "iso-2022-jp"=>"cp50221",
+ "csshiftjis"=>"Windows-31J",
+ "ms932"=>"Windows-31J",
+ "ms_kanji"=>"Windows-31J",
+ "shift-jis"=>"Windows-31J",
+ "shift_jis"=>"Windows-31J",
+ "sjis"=>"Windows-31J",
+ "windows-31j"=>"Windows-31J",
+ "x-sjis"=>"Windows-31J",
+ "cseuckr"=>"euc-kr",
+ "csksc56011987"=>"euc-kr",
+ "euc-kr"=>"euc-kr",
+ "iso-ir-149"=>"euc-kr",
+ "korean"=>"euc-kr",
+ "ks_c_5601-1987"=>"euc-kr",
+ "ks_c_5601-1989"=>"euc-kr",
+ "ksc5601"=>"euc-kr",
+ "ksc_5601"=>"euc-kr",
+ "windows-949"=>"euc-kr",
+ "utf-16be"=>"utf-16be",
+ "utf-16"=>"utf-16le",
+ "utf-16le"=>"utf-16le",
+ } # :nodoc:
+ Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor)
+
+ # :nodoc:
+ # return encoding or nil
+ # http://encoding.spec.whatwg.org/#concept-encoding-get
+ def self.get_encoding(label)
+ Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil
+ end
+end # module Gem::URI
+
+module Gem
+
+ #
+ # Returns a \Gem::URI object derived from the given +uri+,
+ # which may be a \Gem::URI string or an existing \Gem::URI object:
+ #
+ # # Returns a new Gem::URI.
+ # uri = Gem::URI('http://github.com/ruby/ruby')
+ # # => #<Gem::URI::HTTP http://github.com/ruby/ruby>
+ # # Returns the given Gem::URI.
+ # Gem::URI(uri)
+ # # => #<Gem::URI::HTTP http://github.com/ruby/ruby>
+ #
+ def URI(uri)
+ if uri.is_a?(Gem::URI::Generic)
+ uri
+ elsif uri = String.try_convert(uri)
+ Gem::URI.parse(uri)
+ else
+ raise ArgumentError,
+ "bad argument (expected Gem::URI object or Gem::URI string)"
+ end
+ end
+ module_function :URI
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/file.rb b/lib/rubygems/vendor/uri/lib/uri/file.rb
new file mode 100644
index 0000000000..d419b26055
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/file.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require_relative 'generic'
+
+module Gem::URI
+
+ #
+ # The "file" Gem::URI is defined by RFC8089.
+ #
+ class File < Generic
+ # A Default port of nil for Gem::URI::File.
+ DEFAULT_PORT = nil
+
+ #
+ # An Array of the available components for Gem::URI::File.
+ #
+ COMPONENT = [
+ :scheme,
+ :host,
+ :path
+ ].freeze
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::File object from components, with syntax checking.
+ #
+ # The components accepted are +host+ and +path+.
+ #
+ # The components should be provided either as an Array, or as a Hash
+ # with keys formed by preceding the component names with a colon.
+ #
+ # If an Array is used, the components must be passed in the
+ # order <code>[host, path]</code>.
+ #
+ # A path from e.g. the File class should be escaped before
+ # being passed.
+ #
+ # Examples:
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri1 = Gem::URI::File.build(['host.example.com', '/path/file.zip'])
+ # uri1.to_s # => "file://host.example.com/path/file.zip"
+ #
+ # uri2 = Gem::URI::File.build({:host => 'host.example.com',
+ # :path => '/ruby/src'})
+ # uri2.to_s # => "file://host.example.com/ruby/src"
+ #
+ # uri3 = Gem::URI::File.build({:path => Gem::URI::escape('/path/my file.txt')})
+ # uri3.to_s # => "file:///path/my%20file.txt"
+ #
+ def self.build(args)
+ tmp = Util::make_components_hash(self, args)
+ super(tmp)
+ end
+
+ # Protected setter for the host component +v+.
+ #
+ # See also Gem::URI::Generic.host=.
+ #
+ def set_host(v)
+ v = "" if v.nil? || v == "localhost"
+ @host = v
+ end
+
+ # do nothing
+ def set_port(v)
+ end
+
+ # raise InvalidURIError
+ def check_userinfo(user)
+ raise Gem::URI::InvalidURIError, "can not set userinfo for file Gem::URI"
+ end
+
+ # raise InvalidURIError
+ def check_user(user)
+ raise Gem::URI::InvalidURIError, "can not set user for file Gem::URI"
+ end
+
+ # raise InvalidURIError
+ def check_password(user)
+ raise Gem::URI::InvalidURIError, "can not set password for file Gem::URI"
+ end
+
+ # do nothing
+ def set_userinfo(v)
+ end
+
+ # do nothing
+ def set_user(v)
+ end
+
+ # do nothing
+ def set_password(v)
+ end
+ end
+
+ register_scheme 'FILE', File
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/ftp.rb b/lib/rubygems/vendor/uri/lib/uri/ftp.rb
new file mode 100644
index 0000000000..100498ffb2
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/ftp.rb
@@ -0,0 +1,267 @@
+# frozen_string_literal: false
+# = uri/ftp.rb
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'generic'
+
+module Gem::URI
+
+ #
+ # FTP Gem::URI syntax is defined by RFC1738 section 3.2.
+ #
+ # This class will be redesigned because of difference of implementations;
+ # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it
+ # is a good summary about the de facto spec.
+ # http://tools.ietf.org/html/draft-hoffman-ftp-uri-04
+ #
+ class FTP < Generic
+ # A Default port of 21 for Gem::URI::FTP.
+ DEFAULT_PORT = 21
+
+ #
+ # An Array of the available components for Gem::URI::FTP.
+ #
+ COMPONENT = [
+ :scheme,
+ :userinfo, :host, :port,
+ :path, :typecode
+ ].freeze
+
+ #
+ # Typecode is "a", "i", or "d".
+ #
+ # * "a" indicates a text file (the FTP command was ASCII)
+ # * "i" indicates a binary file (FTP command IMAGE)
+ # * "d" indicates the contents of a directory should be displayed
+ #
+ TYPECODE = ['a', 'i', 'd'].freeze
+
+ # Typecode prefix ";type=".
+ TYPECODE_PREFIX = ';type='.freeze
+
+ def self.new2(user, password, host, port, path,
+ typecode = nil, arg_check = true) # :nodoc:
+ # Do not use this method! Not tested. [Bug #7301]
+ # This methods remains just for compatibility,
+ # Keep it undocumented until the active maintainer is assigned.
+ typecode = nil if typecode.size == 0
+ if typecode && !TYPECODE.include?(typecode)
+ raise ArgumentError,
+ "bad typecode is specified: #{typecode}"
+ end
+
+ # do escape
+
+ self.new('ftp',
+ [user, password],
+ host, port, nil,
+ typecode ? path + TYPECODE_PREFIX + typecode : path,
+ nil, nil, nil, arg_check)
+ end
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::FTP object from components, with syntax checking.
+ #
+ # The components accepted are +userinfo+, +host+, +port+, +path+, and
+ # +typecode+.
+ #
+ # The components should be provided either as an Array, or as a Hash
+ # with keys formed by preceding the component names with a colon.
+ #
+ # If an Array is used, the components must be passed in the
+ # order <code>[userinfo, host, port, path, typecode]</code>.
+ #
+ # If the path supplied is absolute, it will be escaped in order to
+ # make it absolute in the Gem::URI.
+ #
+ # Examples:
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri1 = Gem::URI::FTP.build(['user:password', 'ftp.example.com', nil,
+ # '/path/file.zip', 'i'])
+ # uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i"
+ #
+ # uri2 = Gem::URI::FTP.build({:host => 'ftp.example.com',
+ # :path => 'ruby/src'})
+ # uri2.to_s # => "ftp://ftp.example.com/ruby/src"
+ #
+ def self.build(args)
+
+ # Fix the incoming path to be generic URL syntax
+ # FTP path -> URL path
+ # foo/bar /foo/bar
+ # /foo/bar /%2Ffoo/bar
+ #
+ if args.kind_of?(Array)
+ args[3] = '/' + args[3].sub(/^\//, '%2F')
+ else
+ args[:path] = '/' + args[:path].sub(/^\//, '%2F')
+ end
+
+ tmp = Util::make_components_hash(self, args)
+
+ if tmp[:typecode]
+ if tmp[:typecode].size == 1
+ tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode]
+ end
+ tmp[:path] << tmp[:typecode]
+ end
+
+ return super(tmp)
+ end
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::FTP object from generic URL components with no
+ # syntax checking.
+ #
+ # Unlike build(), this method does not escape the path component as
+ # required by RFC1738; instead it is treated as per RFC2396.
+ #
+ # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+,
+ # +opaque+, +query+, and +fragment+, in that order.
+ #
+ def initialize(scheme,
+ userinfo, host, port, registry,
+ path, opaque,
+ query,
+ fragment,
+ parser = nil,
+ arg_check = false)
+ raise InvalidURIError unless path
+ path = path.sub(/^\//,'')
+ path.sub!(/^%2F/,'/')
+ super(scheme, userinfo, host, port, registry, path, opaque,
+ query, fragment, parser, arg_check)
+ @typecode = nil
+ if tmp = @path.index(TYPECODE_PREFIX)
+ typecode = @path[tmp + TYPECODE_PREFIX.size..-1]
+ @path = @path[0..tmp - 1]
+
+ if arg_check
+ self.typecode = typecode
+ else
+ self.set_typecode(typecode)
+ end
+ end
+ end
+
+ # typecode accessor.
+ #
+ # See Gem::URI::FTP::COMPONENT.
+ attr_reader :typecode
+
+ # Validates typecode +v+,
+ # returns +true+ or +false+.
+ #
+ def check_typecode(v)
+ if TYPECODE.include?(v)
+ return true
+ else
+ raise InvalidComponentError,
+ "bad typecode(expected #{TYPECODE.join(', ')}): #{v}"
+ end
+ end
+ private :check_typecode
+
+ # Private setter for the typecode +v+.
+ #
+ # See also Gem::URI::FTP.typecode=.
+ #
+ def set_typecode(v)
+ @typecode = v
+ end
+ protected :set_typecode
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the typecode +v+
+ # (with validation).
+ #
+ # See also Gem::URI::FTP.check_typecode.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("ftp://john@ftp.example.com/my_file.img")
+ # #=> #<Gem::URI::FTP ftp://john@ftp.example.com/my_file.img>
+ # uri.typecode = "i"
+ # uri
+ # #=> #<Gem::URI::FTP ftp://john@ftp.example.com/my_file.img;type=i>
+ #
+ def typecode=(typecode)
+ check_typecode(typecode)
+ set_typecode(typecode)
+ typecode
+ end
+
+ def merge(oth) # :nodoc:
+ tmp = super(oth)
+ if self != tmp
+ tmp.set_typecode(oth.typecode)
+ end
+
+ return tmp
+ end
+
+ # Returns the path from an FTP Gem::URI.
+ #
+ # RFC 1738 specifically states that the path for an FTP Gem::URI does not
+ # include the / which separates the Gem::URI path from the Gem::URI host. Example:
+ #
+ # <code>ftp://ftp.example.com/pub/ruby</code>
+ #
+ # The above Gem::URI indicates that the client should connect to
+ # ftp.example.com then cd to pub/ruby from the initial login directory.
+ #
+ # If you want to cd to an absolute directory, you must include an
+ # escaped / (%2F) in the path. Example:
+ #
+ # <code>ftp://ftp.example.com/%2Fpub/ruby</code>
+ #
+ # This method will then return "/pub/ruby".
+ #
+ def path
+ return @path.sub(/^\//,'').sub(/^%2F/,'/')
+ end
+
+ # Private setter for the path of the Gem::URI::FTP.
+ def set_path(v)
+ super("/" + v.sub(/^\//, "%2F"))
+ end
+ protected :set_path
+
+ # Returns a String representation of the Gem::URI::FTP.
+ def to_s
+ save_path = nil
+ if @typecode
+ save_path = @path
+ @path = @path + TYPECODE_PREFIX + @typecode
+ end
+ str = super
+ if @typecode
+ @path = save_path
+ end
+
+ return str
+ end
+ end
+
+ register_scheme 'FTP', FTP
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/generic.rb b/lib/rubygems/vendor/uri/lib/uri/generic.rb
new file mode 100644
index 0000000000..72c52aa8ee
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/generic.rb
@@ -0,0 +1,1588 @@
+# frozen_string_literal: true
+
+# = uri/generic.rb
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'common'
+autoload :IPSocket, 'socket'
+autoload :IPAddr, 'ipaddr'
+
+module Gem::URI
+
+ #
+ # Base class for all Gem::URI classes.
+ # Implements generic Gem::URI syntax as per RFC 2396.
+ #
+ class Generic
+ include Gem::URI
+
+ #
+ # A Default port of nil for Gem::URI::Generic.
+ #
+ DEFAULT_PORT = nil
+
+ #
+ # Returns default port.
+ #
+ def self.default_port
+ self::DEFAULT_PORT
+ end
+
+ #
+ # Returns default port.
+ #
+ def default_port
+ self.class.default_port
+ end
+
+ #
+ # An Array of the available components for Gem::URI::Generic.
+ #
+ COMPONENT = [
+ :scheme,
+ :userinfo, :host, :port, :registry,
+ :path, :opaque,
+ :query,
+ :fragment
+ ].freeze
+
+ #
+ # Components of the Gem::URI in the order.
+ #
+ def self.component
+ self::COMPONENT
+ end
+
+ USE_REGISTRY = false # :nodoc:
+
+ def self.use_registry # :nodoc:
+ self::USE_REGISTRY
+ end
+
+ #
+ # == Synopsis
+ #
+ # See ::new.
+ #
+ # == Description
+ #
+ # At first, tries to create a new Gem::URI::Generic instance using
+ # Gem::URI::Generic::build. But, if exception Gem::URI::InvalidComponentError is raised,
+ # then it does Gem::URI::Escape.escape all Gem::URI components and tries again.
+ #
+ def self.build2(args)
+ begin
+ return self.build(args)
+ rescue InvalidComponentError
+ if args.kind_of?(Array)
+ return self.build(args.collect{|x|
+ if x.is_a?(String)
+ DEFAULT_PARSER.escape(x)
+ else
+ x
+ end
+ })
+ elsif args.kind_of?(Hash)
+ tmp = {}
+ args.each do |key, value|
+ tmp[key] = if value
+ DEFAULT_PARSER.escape(value)
+ else
+ value
+ end
+ end
+ return self.build(tmp)
+ end
+ end
+ end
+
+ #
+ # == Synopsis
+ #
+ # See ::new.
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::Generic instance from components of Gem::URI::Generic
+ # with check. Components are: scheme, userinfo, host, port, registry, path,
+ # opaque, query, and fragment. You can provide arguments either by an Array or a Hash.
+ # See ::new for hash keys to use or for order of array items.
+ #
+ def self.build(args)
+ if args.kind_of?(Array) &&
+ args.size == ::Gem::URI::Generic::COMPONENT.size
+ tmp = args.dup
+ elsif args.kind_of?(Hash)
+ tmp = ::Gem::URI::Generic::COMPONENT.collect do |c|
+ if args.include?(c)
+ args[c]
+ else
+ nil
+ end
+ end
+ else
+ component = self.class.component rescue ::Gem::URI::Generic::COMPONENT
+ raise ArgumentError,
+ "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})"
+ end
+
+ tmp << nil
+ tmp << true
+ return self.new(*tmp)
+ end
+
+ #
+ # == Args
+ #
+ # +scheme+::
+ # Protocol scheme, i.e. 'http','ftp','mailto' and so on.
+ # +userinfo+::
+ # User name and password, i.e. 'sdmitry:bla'.
+ # +host+::
+ # Server host name.
+ # +port+::
+ # Server port.
+ # +registry+::
+ # Registry of naming authorities.
+ # +path+::
+ # Path on server.
+ # +opaque+::
+ # Opaque part.
+ # +query+::
+ # Query data.
+ # +fragment+::
+ # Part of the Gem::URI after '#' character.
+ # +parser+::
+ # Parser for internal use [Gem::URI::DEFAULT_PARSER by default].
+ # +arg_check+::
+ # Check arguments [false by default].
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::Generic instance from ``generic'' components without check.
+ #
+ def initialize(scheme,
+ userinfo, host, port, registry,
+ path, opaque,
+ query,
+ fragment,
+ parser = DEFAULT_PARSER,
+ arg_check = false)
+ @scheme = nil
+ @user = nil
+ @password = nil
+ @host = nil
+ @port = nil
+ @path = nil
+ @query = nil
+ @opaque = nil
+ @fragment = nil
+ @parser = parser == DEFAULT_PARSER ? nil : parser
+
+ if arg_check
+ self.scheme = scheme
+ self.userinfo = userinfo
+ self.hostname = host
+ self.port = port
+ self.path = path
+ self.query = query
+ self.opaque = opaque
+ self.fragment = fragment
+ else
+ self.set_scheme(scheme)
+ self.set_userinfo(userinfo)
+ self.set_host(host)
+ self.set_port(port)
+ self.set_path(path)
+ self.query = query
+ self.set_opaque(opaque)
+ self.fragment=(fragment)
+ end
+ if registry
+ raise InvalidURIError,
+ "the scheme #{@scheme} does not accept registry part: #{registry} (or bad hostname?)"
+ end
+
+ @scheme&.freeze
+ self.set_path('') if !@path && !@opaque # (see RFC2396 Section 5.2)
+ self.set_port(self.default_port) if self.default_port && !@port
+ end
+
+ #
+ # Returns the scheme component of the Gem::URI.
+ #
+ # Gem::URI("http://foo/bar/baz").scheme #=> "http"
+ #
+ attr_reader :scheme
+
+ # Returns the host component of the Gem::URI.
+ #
+ # Gem::URI("http://foo/bar/baz").host #=> "foo"
+ #
+ # It returns nil if no host component exists.
+ #
+ # Gem::URI("mailto:foo@example.org").host #=> nil
+ #
+ # The component does not contain the port number.
+ #
+ # Gem::URI("http://foo:8080/bar/baz").host #=> "foo"
+ #
+ # Since IPv6 addresses are wrapped with brackets in URIs,
+ # this method returns IPv6 addresses wrapped with brackets.
+ # This form is not appropriate to pass to socket methods such as TCPSocket.open.
+ # If unwrapped host names are required, use the #hostname method.
+ #
+ # Gem::URI("http://[::1]/bar/baz").host #=> "[::1]"
+ # Gem::URI("http://[::1]/bar/baz").hostname #=> "::1"
+ #
+ attr_reader :host
+
+ # Returns the port component of the Gem::URI.
+ #
+ # Gem::URI("http://foo/bar/baz").port #=> 80
+ # Gem::URI("http://foo:8080/bar/baz").port #=> 8080
+ #
+ attr_reader :port
+
+ def registry # :nodoc:
+ nil
+ end
+
+ # Returns the path component of the Gem::URI.
+ #
+ # Gem::URI("http://foo/bar/baz").path #=> "/bar/baz"
+ #
+ attr_reader :path
+
+ # Returns the query component of the Gem::URI.
+ #
+ # Gem::URI("http://foo/bar/baz?search=FooBar").query #=> "search=FooBar"
+ #
+ attr_reader :query
+
+ # Returns the opaque part of the Gem::URI.
+ #
+ # Gem::URI("mailto:foo@example.org").opaque #=> "foo@example.org"
+ # Gem::URI("http://foo/bar/baz").opaque #=> nil
+ #
+ # The portion of the path that does not make use of the slash '/'.
+ # The path typically refers to an absolute path or an opaque part.
+ # (See RFC2396 Section 3 and 5.2.)
+ #
+ attr_reader :opaque
+
+ # Returns the fragment component of the Gem::URI.
+ #
+ # Gem::URI("http://foo/bar/baz?search=FooBar#ponies").fragment #=> "ponies"
+ #
+ attr_reader :fragment
+
+ # Returns the parser to be used.
+ #
+ # Unless a Gem::URI::Parser is defined, DEFAULT_PARSER is used.
+ #
+ def parser
+ if !defined?(@parser) || !@parser
+ DEFAULT_PARSER
+ else
+ @parser || DEFAULT_PARSER
+ end
+ end
+
+ # Replaces self by other Gem::URI object.
+ #
+ def replace!(oth)
+ if self.class != oth.class
+ raise ArgumentError, "expected #{self.class} object"
+ end
+
+ component.each do |c|
+ self.__send__("#{c}=", oth.__send__(c))
+ end
+ end
+ private :replace!
+
+ #
+ # Components of the Gem::URI in the order.
+ #
+ def component
+ self.class.component
+ end
+
+ #
+ # Checks the scheme +v+ component against the Gem::URI::Parser Regexp for :SCHEME.
+ #
+ def check_scheme(v)
+ if v && parser.regexp[:SCHEME] !~ v
+ raise InvalidComponentError,
+ "bad component(expected scheme component): #{v}"
+ end
+
+ return true
+ end
+ private :check_scheme
+
+ # Protected setter for the scheme component +v+.
+ #
+ # See also Gem::URI::Generic.scheme=.
+ #
+ def set_scheme(v)
+ @scheme = v&.downcase
+ end
+ protected :set_scheme
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the scheme component +v+
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.check_scheme.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com")
+ # uri.scheme = "https"
+ # uri.to_s #=> "https://my.example.com"
+ #
+ def scheme=(v)
+ check_scheme(v)
+ set_scheme(v)
+ v
+ end
+
+ #
+ # Checks the +user+ and +password+.
+ #
+ # If +password+ is not provided, then +user+ is
+ # split, using Gem::URI::Generic.split_userinfo, to
+ # pull +user+ and +password.
+ #
+ # See also Gem::URI::Generic.check_user, Gem::URI::Generic.check_password.
+ #
+ def check_userinfo(user, password = nil)
+ if !password
+ user, password = split_userinfo(user)
+ end
+ check_user(user)
+ check_password(password, user)
+
+ return true
+ end
+ private :check_userinfo
+
+ #
+ # Checks the user +v+ component for RFC2396 compliance
+ # and against the Gem::URI::Parser Regexp for :USERINFO.
+ #
+ # Can not have a registry or opaque component defined,
+ # with a user component defined.
+ #
+ def check_user(v)
+ if @opaque
+ raise InvalidURIError,
+ "can not set user with opaque"
+ end
+
+ return v unless v
+
+ if parser.regexp[:USERINFO] !~ v
+ raise InvalidComponentError,
+ "bad component(expected userinfo component or user component): #{v}"
+ end
+
+ return true
+ end
+ private :check_user
+
+ #
+ # Checks the password +v+ component for RFC2396 compliance
+ # and against the Gem::URI::Parser Regexp for :USERINFO.
+ #
+ # Can not have a registry or opaque component defined,
+ # with a user component defined.
+ #
+ def check_password(v, user = @user)
+ if @opaque
+ raise InvalidURIError,
+ "can not set password with opaque"
+ end
+ return v unless v
+
+ if !user
+ raise InvalidURIError,
+ "password component depends user component"
+ end
+
+ if parser.regexp[:USERINFO] !~ v
+ raise InvalidComponentError,
+ "bad password component"
+ end
+
+ return true
+ end
+ private :check_password
+
+ #
+ # Sets userinfo, argument is string like 'name:pass'.
+ #
+ def userinfo=(userinfo)
+ if userinfo.nil?
+ return nil
+ end
+ check_userinfo(*userinfo)
+ set_userinfo(*userinfo)
+ # returns userinfo
+ end
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the +user+ component
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.check_user.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://john:S3nsit1ve@my.example.com")
+ # uri.user = "sam"
+ # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com"
+ #
+ def user=(user)
+ check_user(user)
+ set_user(user)
+ # returns user
+ end
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the +password+ component
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.check_password.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://john:S3nsit1ve@my.example.com")
+ # uri.password = "V3ry_S3nsit1ve"
+ # uri.to_s #=> "http://john:V3ry_S3nsit1ve@my.example.com"
+ #
+ def password=(password)
+ check_password(password)
+ set_password(password)
+ # returns password
+ end
+
+ # Protected setter for the +user+ component, and +password+ if available
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.userinfo=.
+ #
+ def set_userinfo(user, password = nil)
+ unless password
+ user, password = split_userinfo(user)
+ end
+ @user = user
+ @password = password if password
+
+ [@user, @password]
+ end
+ protected :set_userinfo
+
+ # Protected setter for the user component +v+.
+ #
+ # See also Gem::URI::Generic.user=.
+ #
+ def set_user(v)
+ set_userinfo(v, @password)
+ v
+ end
+ protected :set_user
+
+ # Protected setter for the password component +v+.
+ #
+ # See also Gem::URI::Generic.password=.
+ #
+ def set_password(v)
+ @password = v
+ # returns v
+ end
+ protected :set_password
+
+ # Returns the userinfo +ui+ as <code>[user, password]</code>
+ # if properly formatted as 'user:password'.
+ def split_userinfo(ui)
+ return nil, nil unless ui
+ user, password = ui.split(':', 2)
+
+ return user, password
+ end
+ private :split_userinfo
+
+ # Escapes 'user:password' +v+ based on RFC 1738 section 3.1.
+ def escape_userpass(v)
+ parser.escape(v, /[@:\/]/o) # RFC 1738 section 3.1 #/
+ end
+ private :escape_userpass
+
+ # Returns the userinfo, either as 'user' or 'user:password'.
+ def userinfo
+ if @user.nil?
+ nil
+ elsif @password.nil?
+ @user
+ else
+ @user + ':' + @password
+ end
+ end
+
+ # Returns the user component (without Gem::URI decoding).
+ def user
+ @user
+ end
+
+ # Returns the password component (without Gem::URI decoding).
+ def password
+ @password
+ end
+
+ # Returns the user component after Gem::URI decoding.
+ def decoded_user
+ Gem::URI.decode_uri_component(@user) if @user
+ end
+
+ # Returns the password component after Gem::URI decoding.
+ def decoded_password
+ Gem::URI.decode_uri_component(@password) if @password
+ end
+
+ #
+ # Checks the host +v+ component for RFC2396 compliance
+ # and against the Gem::URI::Parser Regexp for :HOST.
+ #
+ # Can not have a registry or opaque component defined,
+ # with a host component defined.
+ #
+ def check_host(v)
+ return v unless v
+
+ if @opaque
+ raise InvalidURIError,
+ "can not set host with registry or opaque"
+ elsif parser.regexp[:HOST] !~ v
+ raise InvalidComponentError,
+ "bad component(expected host component): #{v}"
+ end
+
+ return true
+ end
+ private :check_host
+
+ # Protected setter for the host component +v+.
+ #
+ # See also Gem::URI::Generic.host=.
+ #
+ def set_host(v)
+ @host = v
+ end
+ protected :set_host
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the host component +v+
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.check_host.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com")
+ # uri.host = "foo.com"
+ # uri.to_s #=> "http://foo.com"
+ #
+ def host=(v)
+ check_host(v)
+ set_host(v)
+ v
+ end
+
+ # Extract the host part of the Gem::URI and unwrap brackets for IPv6 addresses.
+ #
+ # This method is the same as Gem::URI::Generic#host except
+ # brackets for IPv6 (and future IP) addresses are removed.
+ #
+ # uri = Gem::URI("http://[::1]/bar")
+ # uri.hostname #=> "::1"
+ # uri.host #=> "[::1]"
+ #
+ def hostname
+ v = self.host
+ v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v
+ end
+
+ # Sets the host part of the Gem::URI as the argument with brackets for IPv6 addresses.
+ #
+ # This method is the same as Gem::URI::Generic#host= except
+ # the argument can be a bare IPv6 address.
+ #
+ # uri = Gem::URI("http://foo/bar")
+ # uri.hostname = "::1"
+ # uri.to_s #=> "http://[::1]/bar"
+ #
+ # If the argument seems to be an IPv6 address,
+ # it is wrapped with brackets.
+ #
+ def hostname=(v)
+ v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':')
+ self.host = v
+ end
+
+ #
+ # Checks the port +v+ component for RFC2396 compliance
+ # and against the Gem::URI::Parser Regexp for :PORT.
+ #
+ # Can not have a registry or opaque component defined,
+ # with a port component defined.
+ #
+ def check_port(v)
+ return v unless v
+
+ if @opaque
+ raise InvalidURIError,
+ "can not set port with registry or opaque"
+ elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v
+ raise InvalidComponentError,
+ "bad component(expected port component): #{v.inspect}"
+ end
+
+ return true
+ end
+ private :check_port
+
+ # Protected setter for the port component +v+.
+ #
+ # See also Gem::URI::Generic.port=.
+ #
+ def set_port(v)
+ v = v.empty? ? nil : v.to_i unless !v || v.kind_of?(Integer)
+ @port = v
+ end
+ protected :set_port
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the port component +v+
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.check_port.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com")
+ # uri.port = 8080
+ # uri.to_s #=> "http://my.example.com:8080"
+ #
+ def port=(v)
+ check_port(v)
+ set_port(v)
+ port
+ end
+
+ def check_registry(v) # :nodoc:
+ raise InvalidURIError, "can not set registry"
+ end
+ private :check_registry
+
+ def set_registry(v) #:nodoc:
+ raise InvalidURIError, "can not set registry"
+ end
+ protected :set_registry
+
+ def registry=(v)
+ raise InvalidURIError, "can not set registry"
+ end
+
+ #
+ # Checks the path +v+ component for RFC2396 compliance
+ # and against the Gem::URI::Parser Regexp
+ # for :ABS_PATH and :REL_PATH.
+ #
+ # Can not have a opaque component defined,
+ # with a path component defined.
+ #
+ def check_path(v)
+ # raise if both hier and opaque are not nil, because:
+ # absoluteURI = scheme ":" ( hier_part | opaque_part )
+ # hier_part = ( net_path | abs_path ) [ "?" query ]
+ if v && @opaque
+ raise InvalidURIError,
+ "path conflicts with opaque"
+ end
+
+ # If scheme is ftp, path may be relative.
+ # See RFC 1738 section 3.2.2, and RFC 2396.
+ if @scheme && @scheme != "ftp"
+ if v && v != '' && parser.regexp[:ABS_PATH] !~ v
+ raise InvalidComponentError,
+ "bad component(expected absolute path component): #{v}"
+ end
+ else
+ if v && v != '' && parser.regexp[:ABS_PATH] !~ v &&
+ parser.regexp[:REL_PATH] !~ v
+ raise InvalidComponentError,
+ "bad component(expected relative path component): #{v}"
+ end
+ end
+
+ return true
+ end
+ private :check_path
+
+ # Protected setter for the path component +v+.
+ #
+ # See also Gem::URI::Generic.path=.
+ #
+ def set_path(v)
+ @path = v
+ end
+ protected :set_path
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the path component +v+
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.check_path.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com/pub/files")
+ # uri.path = "/faq/"
+ # uri.to_s #=> "http://my.example.com/faq/"
+ #
+ def path=(v)
+ check_path(v)
+ set_path(v)
+ v
+ end
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the query component +v+.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com/?id=25")
+ # uri.query = "id=1"
+ # uri.to_s #=> "http://my.example.com/?id=1"
+ #
+ def query=(v)
+ return @query = nil unless v
+ raise InvalidURIError, "query conflicts with opaque" if @opaque
+
+ x = v.to_str
+ v = x.dup if x.equal? v
+ v.encode!(Encoding::UTF_8) rescue nil
+ v.delete!("\t\r\n")
+ v.force_encoding(Encoding::ASCII_8BIT)
+ raise InvalidURIError, "invalid percent escape: #{$1}" if /(%\H\H)/n.match(v)
+ v.gsub!(/(?!%\h\h|[!$-&(-;=?-_a-~])./n.freeze){'%%%02X' % $&.ord}
+ v.force_encoding(Encoding::US_ASCII)
+ @query = v
+ end
+
+ #
+ # Checks the opaque +v+ component for RFC2396 compliance and
+ # against the Gem::URI::Parser Regexp for :OPAQUE.
+ #
+ # Can not have a host, port, user, or path component defined,
+ # with an opaque component defined.
+ #
+ def check_opaque(v)
+ return v unless v
+
+ # raise if both hier and opaque are not nil, because:
+ # absoluteURI = scheme ":" ( hier_part | opaque_part )
+ # hier_part = ( net_path | abs_path ) [ "?" query ]
+ if @host || @port || @user || @path # userinfo = @user + ':' + @password
+ raise InvalidURIError,
+ "can not set opaque with host, port, userinfo or path"
+ elsif v && parser.regexp[:OPAQUE] !~ v
+ raise InvalidComponentError,
+ "bad component(expected opaque component): #{v}"
+ end
+
+ return true
+ end
+ private :check_opaque
+
+ # Protected setter for the opaque component +v+.
+ #
+ # See also Gem::URI::Generic.opaque=.
+ #
+ def set_opaque(v)
+ @opaque = v
+ end
+ protected :set_opaque
+
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the opaque component +v+
+ # (with validation).
+ #
+ # See also Gem::URI::Generic.check_opaque.
+ #
+ def opaque=(v)
+ check_opaque(v)
+ set_opaque(v)
+ v
+ end
+
+ #
+ # Checks the fragment +v+ component against the Gem::URI::Parser Regexp for :FRAGMENT.
+ #
+ #
+ # == Args
+ #
+ # +v+::
+ # String
+ #
+ # == Description
+ #
+ # Public setter for the fragment component +v+
+ # (with validation).
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com/?id=25#time=1305212049")
+ # uri.fragment = "time=1305212086"
+ # uri.to_s #=> "http://my.example.com/?id=25#time=1305212086"
+ #
+ def fragment=(v)
+ return @fragment = nil unless v
+
+ x = v.to_str
+ v = x.dup if x.equal? v
+ v.encode!(Encoding::UTF_8) rescue nil
+ v.delete!("\t\r\n")
+ v.force_encoding(Encoding::ASCII_8BIT)
+ v.gsub!(/(?!%\h\h|[!-~])./n){'%%%02X' % $&.ord}
+ v.force_encoding(Encoding::US_ASCII)
+ @fragment = v
+ end
+
+ #
+ # Returns true if Gem::URI is hierarchical.
+ #
+ # == Description
+ #
+ # Gem::URI has components listed in order of decreasing significance from left to right,
+ # see RFC3986 https://tools.ietf.org/html/rfc3986 1.2.3.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com/")
+ # uri.hierarchical?
+ # #=> true
+ # uri = Gem::URI.parse("mailto:joe@example.com")
+ # uri.hierarchical?
+ # #=> false
+ #
+ def hierarchical?
+ if @path
+ true
+ else
+ false
+ end
+ end
+
+ #
+ # Returns true if Gem::URI has a scheme (e.g. http:// or https://) specified.
+ #
+ def absolute?
+ if @scheme
+ true
+ else
+ false
+ end
+ end
+ alias absolute absolute?
+
+ #
+ # Returns true if Gem::URI does not have a scheme (e.g. http:// or https://) specified.
+ #
+ def relative?
+ !absolute?
+ end
+
+ #
+ # Returns an Array of the path split on '/'.
+ #
+ def split_path(path)
+ path.split("/", -1)
+ end
+ private :split_path
+
+ #
+ # Merges a base path +base+, with relative path +rel+,
+ # returns a modified base path.
+ #
+ def merge_path(base, rel)
+
+ # RFC2396, Section 5.2, 5)
+ # RFC2396, Section 5.2, 6)
+ base_path = split_path(base)
+ rel_path = split_path(rel)
+
+ # RFC2396, Section 5.2, 6), a)
+ base_path << '' if base_path.last == '..'
+ while i = base_path.index('..')
+ base_path.slice!(i - 1, 2)
+ end
+
+ if (first = rel_path.first) and first.empty?
+ base_path.clear
+ rel_path.shift
+ end
+
+ # RFC2396, Section 5.2, 6), c)
+ # RFC2396, Section 5.2, 6), d)
+ rel_path.push('') if rel_path.last == '.' || rel_path.last == '..'
+ rel_path.delete('.')
+
+ # RFC2396, Section 5.2, 6), e)
+ tmp = []
+ rel_path.each do |x|
+ if x == '..' &&
+ !(tmp.empty? || tmp.last == '..')
+ tmp.pop
+ else
+ tmp << x
+ end
+ end
+
+ add_trailer_slash = !tmp.empty?
+ if base_path.empty?
+ base_path = [''] # keep '/' for root directory
+ elsif add_trailer_slash
+ base_path.pop
+ end
+ while x = tmp.shift
+ if x == '..'
+ # RFC2396, Section 4
+ # a .. or . in an absolute path has no special meaning
+ base_path.pop if base_path.size > 1
+ else
+ # if x == '..'
+ # valid absolute (but abnormal) path "/../..."
+ # else
+ # valid absolute path
+ # end
+ base_path << x
+ tmp.each {|t| base_path << t}
+ add_trailer_slash = false
+ break
+ end
+ end
+ base_path.push('') if add_trailer_slash
+
+ return base_path.join('/')
+ end
+ private :merge_path
+
+ #
+ # == Args
+ #
+ # +oth+::
+ # Gem::URI or String
+ #
+ # == Description
+ #
+ # Destructive form of #merge.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com")
+ # uri.merge!("/main.rbx?page=1")
+ # uri.to_s # => "http://my.example.com/main.rbx?page=1"
+ #
+ def merge!(oth)
+ t = merge(oth)
+ if self == t
+ nil
+ else
+ replace!(t)
+ self
+ end
+ end
+
+ #
+ # == Args
+ #
+ # +oth+::
+ # Gem::URI or String
+ #
+ # == Description
+ #
+ # Merges two URIs.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com")
+ # uri.merge("/main.rbx?page=1")
+ # # => "http://my.example.com/main.rbx?page=1"
+ #
+ def merge(oth)
+ rel = parser.__send__(:convert_to_uri, oth)
+
+ if rel.absolute?
+ #raise BadURIError, "both Gem::URI are absolute" if absolute?
+ # hmm... should return oth for usability?
+ return rel
+ end
+
+ unless self.absolute?
+ raise BadURIError, "both Gem::URI are relative"
+ end
+
+ base = self.dup
+
+ authority = rel.userinfo || rel.host || rel.port
+
+ # RFC2396, Section 5.2, 2)
+ if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
+ base.fragment=(rel.fragment) if rel.fragment
+ return base
+ end
+
+ base.query = nil
+ base.fragment=(nil)
+
+ # RFC2396, Section 5.2, 4)
+ if !authority
+ base.set_path(merge_path(base.path, rel.path)) if base.path && rel.path
+ else
+ # RFC2396, Section 5.2, 4)
+ base.set_path(rel.path) if rel.path
+ end
+
+ # RFC2396, Section 5.2, 7)
+ base.set_userinfo(rel.userinfo) if rel.userinfo
+ base.set_host(rel.host) if rel.host
+ base.set_port(rel.port) if rel.port
+ base.query = rel.query if rel.query
+ base.fragment=(rel.fragment) if rel.fragment
+
+ return base
+ end # merge
+ alias + merge
+
+ # :stopdoc:
+ def route_from_path(src, dst)
+ case dst
+ when src
+ # RFC2396, Section 4.2
+ return ''
+ when %r{(?:\A|/)\.\.?(?:/|\z)}
+ # dst has abnormal absolute path,
+ # like "/./", "/../", "/x/../", ...
+ return dst.dup
+ end
+
+ src_path = src.scan(%r{[^/]*/})
+ dst_path = dst.scan(%r{[^/]*/?})
+
+ # discard same parts
+ while !dst_path.empty? && dst_path.first == src_path.first
+ src_path.shift
+ dst_path.shift
+ end
+
+ tmp = dst_path.join
+
+ # calculate
+ if src_path.empty?
+ if tmp.empty?
+ return './'
+ elsif dst_path.first.include?(':') # (see RFC2396 Section 5)
+ return './' + tmp
+ else
+ return tmp
+ end
+ end
+
+ return '../' * src_path.size + tmp
+ end
+ private :route_from_path
+ # :startdoc:
+
+ # :stopdoc:
+ def route_from0(oth)
+ oth = parser.__send__(:convert_to_uri, oth)
+ if self.relative?
+ raise BadURIError,
+ "relative Gem::URI: #{self}"
+ end
+ if oth.relative?
+ raise BadURIError,
+ "relative Gem::URI: #{oth}"
+ end
+
+ if self.scheme != oth.scheme
+ return self, self.dup
+ end
+ rel = Gem::URI::Generic.new(nil, # it is relative Gem::URI
+ self.userinfo, self.host, self.port,
+ nil, self.path, self.opaque,
+ self.query, self.fragment, parser)
+
+ if rel.userinfo != oth.userinfo ||
+ rel.host.to_s.downcase != oth.host.to_s.downcase ||
+ rel.port != oth.port
+
+ if self.userinfo.nil? && self.host.nil?
+ return self, self.dup
+ end
+
+ rel.set_port(nil) if rel.port == oth.default_port
+ return rel, rel
+ end
+ rel.set_userinfo(nil)
+ rel.set_host(nil)
+ rel.set_port(nil)
+
+ if rel.path && rel.path == oth.path
+ rel.set_path('')
+ rel.query = nil if rel.query == oth.query
+ return rel, rel
+ elsif rel.opaque && rel.opaque == oth.opaque
+ rel.set_opaque('')
+ rel.query = nil if rel.query == oth.query
+ return rel, rel
+ end
+
+ # you can modify `rel', but can not `oth'.
+ return oth, rel
+ end
+ private :route_from0
+ # :startdoc:
+
+ #
+ # == Args
+ #
+ # +oth+::
+ # Gem::URI or String
+ #
+ # == Description
+ #
+ # Calculates relative path from oth to self.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse('http://my.example.com/main.rbx?page=1')
+ # uri.route_from('http://my.example.com')
+ # #=> #<Gem::URI::Generic /main.rbx?page=1>
+ #
+ def route_from(oth)
+ # you can modify `rel', but can not `oth'.
+ begin
+ oth, rel = route_from0(oth)
+ rescue
+ raise $!.class, $!.message
+ end
+ if oth == rel
+ return rel
+ end
+
+ rel.set_path(route_from_path(oth.path, self.path))
+ if rel.path == './' && self.query
+ # "./?foo" -> "?foo"
+ rel.set_path('')
+ end
+
+ return rel
+ end
+
+ alias - route_from
+
+ #
+ # == Args
+ #
+ # +oth+::
+ # Gem::URI or String
+ #
+ # == Description
+ #
+ # Calculates relative path to oth from self.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse('http://my.example.com')
+ # uri.route_to('http://my.example.com/main.rbx?page=1')
+ # #=> #<Gem::URI::Generic /main.rbx?page=1>
+ #
+ def route_to(oth)
+ parser.__send__(:convert_to_uri, oth).route_from(self)
+ end
+
+ #
+ # Returns normalized Gem::URI.
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # Gem::URI("HTTP://my.EXAMPLE.com").normalize
+ # #=> #<Gem::URI::HTTP http://my.example.com/>
+ #
+ # Normalization here means:
+ #
+ # * scheme and host are converted to lowercase,
+ # * an empty path component is set to "/".
+ #
+ def normalize
+ uri = dup
+ uri.normalize!
+ uri
+ end
+
+ #
+ # Destructive version of #normalize.
+ #
+ def normalize!
+ if path&.empty?
+ set_path('/')
+ end
+ if scheme && scheme != scheme.downcase
+ set_scheme(self.scheme.downcase)
+ end
+ if host && host != host.downcase
+ set_host(self.host.downcase)
+ end
+ end
+
+ #
+ # Constructs String from Gem::URI.
+ #
+ def to_s
+ str = ''.dup
+ if @scheme
+ str << @scheme
+ str << ':'
+ end
+
+ if @opaque
+ str << @opaque
+ else
+ if @host || %w[file postgres].include?(@scheme)
+ str << '//'
+ end
+ if self.userinfo
+ str << self.userinfo
+ str << '@'
+ end
+ if @host
+ str << @host
+ end
+ if @port && @port != self.default_port
+ str << ':'
+ str << @port.to_s
+ end
+ str << @path
+ if @query
+ str << '?'
+ str << @query
+ end
+ end
+ if @fragment
+ str << '#'
+ str << @fragment
+ end
+ str
+ end
+ alias to_str to_s
+
+ #
+ # Compares two URIs.
+ #
+ def ==(oth)
+ if self.class == oth.class
+ self.normalize.component_ary == oth.normalize.component_ary
+ else
+ false
+ end
+ end
+
+ def hash
+ self.component_ary.hash
+ end
+
+ def eql?(oth)
+ self.class == oth.class &&
+ parser == oth.parser &&
+ self.component_ary.eql?(oth.component_ary)
+ end
+
+=begin
+
+--- Gem::URI::Generic#===(oth)
+
+=end
+# def ===(oth)
+# raise NotImplementedError
+# end
+
+=begin
+=end
+
+
+ # Returns an Array of the components defined from the COMPONENT Array.
+ def component_ary
+ component.collect do |x|
+ self.__send__(x)
+ end
+ end
+ protected :component_ary
+
+ # == Args
+ #
+ # +components+::
+ # Multiple Symbol arguments defined in Gem::URI::HTTP.
+ #
+ # == Description
+ #
+ # Selects specified components from Gem::URI.
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse('http://myuser:mypass@my.example.com/test.rbx')
+ # uri.select(:userinfo, :host, :path)
+ # # => ["myuser:mypass", "my.example.com", "/test.rbx"]
+ #
+ def select(*components)
+ components.collect do |c|
+ if component.include?(c)
+ self.__send__(c)
+ else
+ raise ArgumentError,
+ "expected of components of #{self.class} (#{self.class.component.join(', ')})"
+ end
+ end
+ end
+
+ def inspect
+ "#<#{self.class} #{self}>"
+ end
+
+ #
+ # == Args
+ #
+ # +v+::
+ # Gem::URI or String
+ #
+ # == Description
+ #
+ # Attempts to parse other Gem::URI +oth+,
+ # returns [parsed_oth, self].
+ #
+ # == Usage
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("http://my.example.com")
+ # uri.coerce("http://foo.com")
+ # #=> [#<Gem::URI::HTTP http://foo.com>, #<Gem::URI::HTTP http://my.example.com>]
+ #
+ def coerce(oth)
+ case oth
+ when String
+ oth = parser.parse(oth)
+ else
+ super
+ end
+
+ return oth, self
+ end
+
+ # Returns a proxy Gem::URI.
+ # The proxy Gem::URI is obtained from environment variables such as http_proxy,
+ # ftp_proxy, no_proxy, etc.
+ # If there is no proper proxy, nil is returned.
+ #
+ # If the optional parameter +env+ is specified, it is used instead of ENV.
+ #
+ # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.)
+ # are examined, too.
+ #
+ # But http_proxy and HTTP_PROXY is treated specially under CGI environment.
+ # It's because HTTP_PROXY may be set by Proxy: header.
+ # So HTTP_PROXY is not used.
+ # http_proxy is not used too if the variable is case insensitive.
+ # CGI_HTTP_PROXY can be used instead.
+ def find_proxy(env=ENV)
+ raise BadURIError, "relative Gem::URI: #{self}" if self.relative?
+ name = self.scheme.downcase + '_proxy'
+ proxy_uri = nil
+ if name == 'http_proxy' && env.include?('REQUEST_METHOD') # CGI?
+ # HTTP_PROXY conflicts with *_proxy for proxy settings and
+ # HTTP_* for header information in CGI.
+ # So it should be careful to use it.
+ pairs = env.reject {|k, v| /\Ahttp_proxy\z/i !~ k }
+ case pairs.length
+ when 0 # no proxy setting anyway.
+ proxy_uri = nil
+ when 1
+ k, _ = pairs.shift
+ if k == 'http_proxy' && env[k.upcase] == nil
+ # http_proxy is safe to use because ENV is case sensitive.
+ proxy_uri = env[name]
+ else
+ proxy_uri = nil
+ end
+ else # http_proxy is safe to use because ENV is case sensitive.
+ proxy_uri = env.to_hash[name]
+ end
+ if !proxy_uri
+ # Use CGI_HTTP_PROXY. cf. libwww-perl.
+ proxy_uri = env["CGI_#{name.upcase}"]
+ end
+ elsif name == 'http_proxy'
+ if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost']
+ p_port = ENV_JAVA['http.proxyPort']
+ if p_user = ENV_JAVA['http.proxyUser']
+ p_pass = ENV_JAVA['http.proxyPass']
+ proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}"
+ else
+ proxy_uri = "http://#{p_addr}:#{p_port}"
+ end
+ else
+ unless proxy_uri = env[name]
+ if proxy_uri = env[name.upcase]
+ warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1
+ end
+ end
+ end
+ else
+ proxy_uri = env[name] || env[name.upcase]
+ end
+
+ if proxy_uri.nil? || proxy_uri.empty?
+ return nil
+ end
+
+ if self.hostname
+ begin
+ addr = IPSocket.getaddress(self.hostname)
+ return nil if /\A127\.|\A::1\z/ =~ addr
+ rescue SocketError
+ end
+ end
+
+ name = 'no_proxy'
+ if no_proxy = env[name] || env[name.upcase]
+ return nil unless Gem::URI::Generic.use_proxy?(self.hostname, addr, self.port, no_proxy)
+ end
+ Gem::URI.parse(proxy_uri)
+ end
+
+ def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc:
+ hostname = hostname.downcase
+ dothostname = ".#{hostname}"
+ no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) {|p_host, p_port|
+ if !p_port || port == p_port.to_i
+ if p_host.start_with?('.')
+ return false if hostname.end_with?(p_host.downcase)
+ else
+ return false if dothostname.end_with?(".#{p_host.downcase}")
+ end
+ if addr
+ begin
+ return false if IPAddr.new(p_host).include?(addr)
+ rescue IPAddr::InvalidAddressError
+ next
+ end
+ end
+ end
+ }
+ true
+ end
+ end
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/http.rb b/lib/rubygems/vendor/uri/lib/uri/http.rb
new file mode 100644
index 0000000000..bef43490a3
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/http.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: false
+# = uri/http.rb
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'generic'
+
+module Gem::URI
+
+ #
+ # The syntax of HTTP URIs is defined in RFC1738 section 3.3.
+ #
+ # Note that the Ruby Gem::URI library allows HTTP URLs containing usernames and
+ # passwords. This is not legal as per the RFC, but used to be
+ # supported in Internet Explorer 5 and 6, before the MS04-004 security
+ # update. See <URL:http://support.microsoft.com/kb/834489>.
+ #
+ class HTTP < Generic
+ # A Default port of 80 for Gem::URI::HTTP.
+ DEFAULT_PORT = 80
+
+ # An Array of the available components for Gem::URI::HTTP.
+ COMPONENT = %i[
+ scheme
+ userinfo host port
+ path
+ query
+ fragment
+ ].freeze
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::HTTP object from components, with syntax checking.
+ #
+ # The components accepted are userinfo, host, port, path, query, and
+ # fragment.
+ #
+ # The components should be provided either as an Array, or as a Hash
+ # with keys formed by preceding the component names with a colon.
+ #
+ # If an Array is used, the components must be passed in the
+ # order <code>[userinfo, host, port, path, query, fragment]</code>.
+ #
+ # Example:
+ #
+ # uri = Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar')
+ #
+ # uri = Gem::URI::HTTP.build([nil, "www.example.com", nil, "/path",
+ # "query", 'fragment'])
+ #
+ # Currently, if passed userinfo components this method generates
+ # invalid HTTP URIs as per RFC 1738.
+ #
+ def self.build(args)
+ tmp = Util.make_components_hash(self, args)
+ super(tmp)
+ end
+
+ #
+ # == Description
+ #
+ # Returns the full path for an HTTP request, as required by Net::HTTP::Get.
+ #
+ # If the Gem::URI contains a query, the full path is Gem::URI#path + '?' + Gem::URI#query.
+ # Otherwise, the path is simply Gem::URI#path.
+ #
+ # Example:
+ #
+ # uri = Gem::URI::HTTP.build(path: '/foo/bar', query: 'test=true')
+ # uri.request_uri # => "/foo/bar?test=true"
+ #
+ def request_uri
+ return unless @path
+
+ url = @query ? "#@path?#@query" : @path.dup
+ url.start_with?(?/.freeze) ? url : ?/ + url
+ end
+
+ #
+ # == Description
+ #
+ # Returns the authority for an HTTP uri, as defined in
+ # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.
+ #
+ #
+ # Example:
+ #
+ # Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com"
+ # Gem::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000"
+ # Gem::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com"
+ #
+ def authority
+ if port == default_port
+ host
+ else
+ "#{host}:#{port}"
+ end
+ end
+
+ #
+ # == Description
+ #
+ # Returns the origin for an HTTP uri, as defined in
+ # https://datatracker.ietf.org/doc/html/rfc6454.
+ #
+ #
+ # Example:
+ #
+ # Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com"
+ # Gem::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000"
+ # Gem::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com"
+ # Gem::URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com"
+ #
+ def origin
+ "#{scheme}://#{authority}"
+ end
+ end
+
+ register_scheme 'HTTP', HTTP
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/https.rb b/lib/rubygems/vendor/uri/lib/uri/https.rb
new file mode 100644
index 0000000000..6e8e732e1d
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/https.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: false
+# = uri/https.rb
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'http'
+
+module Gem::URI
+
+ # The default port for HTTPS URIs is 443, and the scheme is 'https:' rather
+ # than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs;
+ # see Gem::URI::HTTP.
+ class HTTPS < HTTP
+ # A Default port of 443 for Gem::URI::HTTPS
+ DEFAULT_PORT = 443
+ end
+
+ register_scheme 'HTTPS', HTTPS
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/ldap.rb b/lib/rubygems/vendor/uri/lib/uri/ldap.rb
new file mode 100644
index 0000000000..1a08b5ab7e
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/ldap.rb
@@ -0,0 +1,261 @@
+# frozen_string_literal: false
+# = uri/ldap.rb
+#
+# Author::
+# Takaaki Tateishi <ttate@jaist.ac.jp>
+# Akira Yamada <akira@ruby-lang.org>
+# License::
+# Gem::URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada.
+# You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'generic'
+
+module Gem::URI
+
+ #
+ # LDAP Gem::URI SCHEMA (described in RFC2255).
+ #--
+ # ldap://<host>/<dn>[?<attrs>[?<scope>[?<filter>[?<extensions>]]]]
+ #++
+ class LDAP < Generic
+
+ # A Default port of 389 for Gem::URI::LDAP.
+ DEFAULT_PORT = 389
+
+ # An Array of the available components for Gem::URI::LDAP.
+ COMPONENT = [
+ :scheme,
+ :host, :port,
+ :dn,
+ :attributes,
+ :scope,
+ :filter,
+ :extensions,
+ ].freeze
+
+ # Scopes available for the starting point.
+ #
+ # * SCOPE_BASE - the Base DN
+ # * SCOPE_ONE - one level under the Base DN, not including the base DN and
+ # not including any entries under this
+ # * SCOPE_SUB - subtrees, all entries at all levels
+ #
+ SCOPE = [
+ SCOPE_ONE = 'one',
+ SCOPE_SUB = 'sub',
+ SCOPE_BASE = 'base',
+ ].freeze
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::LDAP object from components, with syntax checking.
+ #
+ # The components accepted are host, port, dn, attributes,
+ # scope, filter, and extensions.
+ #
+ # The components should be provided either as an Array, or as a Hash
+ # with keys formed by preceding the component names with a colon.
+ #
+ # If an Array is used, the components must be passed in the
+ # order <code>[host, port, dn, attributes, scope, filter, extensions]</code>.
+ #
+ # Example:
+ #
+ # uri = Gem::URI::LDAP.build({:host => 'ldap.example.com',
+ # :dn => '/dc=example'})
+ #
+ # uri = Gem::URI::LDAP.build(["ldap.example.com", nil,
+ # "/dc=example;dc=com", "query", nil, nil, nil])
+ #
+ def self.build(args)
+ tmp = Util::make_components_hash(self, args)
+
+ if tmp[:dn]
+ tmp[:path] = tmp[:dn]
+ end
+
+ query = []
+ [:extensions, :filter, :scope, :attributes].collect do |x|
+ next if !tmp[x] && query.size == 0
+ query.unshift(tmp[x])
+ end
+
+ tmp[:query] = query.join('?')
+
+ return super(tmp)
+ end
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::LDAP object from generic Gem::URI components as per
+ # RFC 2396. No LDAP-specific syntax checking is performed.
+ #
+ # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+,
+ # +opaque+, +query+, and +fragment+, in that order.
+ #
+ # Example:
+ #
+ # uri = Gem::URI::LDAP.new("ldap", nil, "ldap.example.com", nil, nil,
+ # "/dc=example;dc=com", nil, "query", nil)
+ #
+ # See also Gem::URI::Generic.new.
+ #
+ def initialize(*arg)
+ super(*arg)
+
+ if @fragment
+ raise InvalidURIError, 'bad LDAP URL'
+ end
+
+ parse_dn
+ parse_query
+ end
+
+ # Private method to cleanup +dn+ from using the +path+ component attribute.
+ def parse_dn
+ raise InvalidURIError, 'bad LDAP URL' unless @path
+ @dn = @path[1..-1]
+ end
+ private :parse_dn
+
+ # Private method to cleanup +attributes+, +scope+, +filter+, and +extensions+
+ # from using the +query+ component attribute.
+ def parse_query
+ @attributes = nil
+ @scope = nil
+ @filter = nil
+ @extensions = nil
+
+ if @query
+ attrs, scope, filter, extensions = @query.split('?')
+
+ @attributes = attrs if attrs && attrs.size > 0
+ @scope = scope if scope && scope.size > 0
+ @filter = filter if filter && filter.size > 0
+ @extensions = extensions if extensions && extensions.size > 0
+ end
+ end
+ private :parse_query
+
+ # Private method to assemble +query+ from +attributes+, +scope+, +filter+, and +extensions+.
+ def build_path_query
+ @path = '/' + @dn
+
+ query = []
+ [@extensions, @filter, @scope, @attributes].each do |x|
+ next if !x && query.size == 0
+ query.unshift(x)
+ end
+ @query = query.join('?')
+ end
+ private :build_path_query
+
+ # Returns dn.
+ def dn
+ @dn
+ end
+
+ # Private setter for dn +val+.
+ def set_dn(val)
+ @dn = val
+ build_path_query
+ @dn
+ end
+ protected :set_dn
+
+ # Setter for dn +val+.
+ def dn=(val)
+ set_dn(val)
+ val
+ end
+
+ # Returns attributes.
+ def attributes
+ @attributes
+ end
+
+ # Private setter for attributes +val+.
+ def set_attributes(val)
+ @attributes = val
+ build_path_query
+ @attributes
+ end
+ protected :set_attributes
+
+ # Setter for attributes +val+.
+ def attributes=(val)
+ set_attributes(val)
+ val
+ end
+
+ # Returns scope.
+ def scope
+ @scope
+ end
+
+ # Private setter for scope +val+.
+ def set_scope(val)
+ @scope = val
+ build_path_query
+ @scope
+ end
+ protected :set_scope
+
+ # Setter for scope +val+.
+ def scope=(val)
+ set_scope(val)
+ val
+ end
+
+ # Returns filter.
+ def filter
+ @filter
+ end
+
+ # Private setter for filter +val+.
+ def set_filter(val)
+ @filter = val
+ build_path_query
+ @filter
+ end
+ protected :set_filter
+
+ # Setter for filter +val+.
+ def filter=(val)
+ set_filter(val)
+ val
+ end
+
+ # Returns extensions.
+ def extensions
+ @extensions
+ end
+
+ # Private setter for extensions +val+.
+ def set_extensions(val)
+ @extensions = val
+ build_path_query
+ @extensions
+ end
+ protected :set_extensions
+
+ # Setter for extensions +val+.
+ def extensions=(val)
+ set_extensions(val)
+ val
+ end
+
+ # Checks if Gem::URI has a path.
+ # For Gem::URI::LDAP this will return +false+.
+ def hierarchical?
+ false
+ end
+ end
+
+ register_scheme 'LDAP', LDAP
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/ldaps.rb b/lib/rubygems/vendor/uri/lib/uri/ldaps.rb
new file mode 100644
index 0000000000..b7a5b50e27
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/ldaps.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: false
+# = uri/ldap.rb
+#
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'ldap'
+
+module Gem::URI
+
+ # The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather
+ # than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs;
+ # see Gem::URI::LDAP.
+ class LDAPS < LDAP
+ # A Default port of 636 for Gem::URI::LDAPS
+ DEFAULT_PORT = 636
+ end
+
+ register_scheme 'LDAPS', LDAPS
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/mailto.rb b/lib/rubygems/vendor/uri/lib/uri/mailto.rb
new file mode 100644
index 0000000000..7ae544d194
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/mailto.rb
@@ -0,0 +1,293 @@
+# frozen_string_literal: false
+# = uri/mailto.rb
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'generic'
+
+module Gem::URI
+
+ #
+ # RFC6068, the mailto URL scheme.
+ #
+ class MailTo < Generic
+ include RFC2396_REGEXP
+
+ # A Default port of nil for Gem::URI::MailTo.
+ DEFAULT_PORT = nil
+
+ # An Array of the available components for Gem::URI::MailTo.
+ COMPONENT = [ :scheme, :to, :headers ].freeze
+
+ # :stopdoc:
+ # "hname" and "hvalue" are encodings of an RFC 822 header name and
+ # value, respectively. As with "to", all URL reserved characters must
+ # be encoded.
+ #
+ # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it
+ # consists of zero or more comma-separated mail addresses, possibly
+ # including "phrase" and "comment" components. Note that all URL
+ # reserved characters in "to" must be encoded: in particular,
+ # parentheses, commas, and the percent sign ("%"), which commonly occur
+ # in the "mailbox" syntax.
+ #
+ # Within mailto URLs, the characters "?", "=", "&" are reserved.
+
+ # ; RFC 6068
+ # hfields = "?" hfield *( "&" hfield )
+ # hfield = hfname "=" hfvalue
+ # hfname = *qchar
+ # hfvalue = *qchar
+ # qchar = unreserved / pct-encoded / some-delims
+ # some-delims = "!" / "$" / "'" / "(" / ")" / "*"
+ # / "+" / "," / ";" / ":" / "@"
+ #
+ # ; RFC3986
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ # pct-encoded = "%" HEXDIG HEXDIG
+ HEADER_REGEXP = /\A(?<hfield>(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g<hfield>)*\z/
+ # practical regexp for email address
+ # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
+ EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/
+ # :startdoc:
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::MailTo object from components, with syntax checking.
+ #
+ # Components can be provided as an Array or Hash. If an Array is used,
+ # the components must be supplied as <code>[to, headers]</code>.
+ #
+ # If a Hash is used, the keys are the component names preceded by colons.
+ #
+ # The headers can be supplied as a pre-encoded string, such as
+ # <code>"subject=subscribe&cc=address"</code>, or as an Array of Arrays
+ # like <code>[['subject', 'subscribe'], ['cc', 'address']]</code>.
+ #
+ # Examples:
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # m1 = Gem::URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
+ # m1.to_s # => "mailto:joe@example.com?subject=Ruby"
+ #
+ # m2 = Gem::URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]])
+ # m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com"
+ #
+ # m3 = Gem::URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]})
+ # m3.to_s # => "mailto:listman@example.com?subject=subscribe"
+ #
+ def self.build(args)
+ tmp = Util.make_components_hash(self, args)
+
+ case tmp[:to]
+ when Array
+ tmp[:opaque] = tmp[:to].join(',')
+ when String
+ tmp[:opaque] = tmp[:to].dup
+ else
+ tmp[:opaque] = ''
+ end
+
+ if tmp[:headers]
+ query =
+ case tmp[:headers]
+ when Array
+ tmp[:headers].collect { |x|
+ if x.kind_of?(Array)
+ x[0] + '=' + x[1..-1].join
+ else
+ x.to_s
+ end
+ }.join('&')
+ when Hash
+ tmp[:headers].collect { |h,v|
+ h + '=' + v
+ }.join('&')
+ else
+ tmp[:headers].to_s
+ end
+ unless query.empty?
+ tmp[:opaque] << '?' << query
+ end
+ end
+
+ super(tmp)
+ end
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::MailTo object from generic URL components with
+ # no syntax checking.
+ #
+ # This method is usually called from Gem::URI::parse, which checks
+ # the validity of each component.
+ #
+ def initialize(*arg)
+ super(*arg)
+
+ @to = nil
+ @headers = []
+
+ # The RFC3986 parser does not normally populate opaque
+ @opaque = "?#{@query}" if @query && !@opaque
+
+ unless @opaque
+ raise InvalidComponentError,
+ "missing opaque part for mailto URL"
+ end
+ to, header = @opaque.split('?', 2)
+ # allow semicolon as a addr-spec separator
+ # http://support.microsoft.com/kb/820868
+ unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to
+ raise InvalidComponentError,
+ "unrecognised opaque part for mailtoURL: #{@opaque}"
+ end
+
+ if arg[10] # arg_check
+ self.to = to
+ self.headers = header
+ else
+ set_to(to)
+ set_headers(header)
+ end
+ end
+
+ # The primary e-mail address of the URL, as a String.
+ attr_reader :to
+
+ # E-mail headers set by the URL, as an Array of Arrays.
+ attr_reader :headers
+
+ # Checks the to +v+ component.
+ def check_to(v)
+ return true unless v
+ return true if v.size == 0
+
+ v.split(/[,;]/).each do |addr|
+ # check url safety as path-rootless
+ if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr
+ raise InvalidComponentError,
+ "an address in 'to' is invalid as Gem::URI #{addr.dump}"
+ end
+
+ # check addr-spec
+ # don't s/\+/ /g
+ addr.gsub!(/%\h\h/, Gem::URI::TBLDECWWWCOMP_)
+ if EMAIL_REGEXP !~ addr
+ raise InvalidComponentError,
+ "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}"
+ end
+ end
+
+ true
+ end
+ private :check_to
+
+ # Private setter for to +v+.
+ def set_to(v)
+ @to = v
+ end
+ protected :set_to
+
+ # Setter for to +v+.
+ def to=(v)
+ check_to(v)
+ set_to(v)
+ v
+ end
+
+ # Checks the headers +v+ component against either
+ # * HEADER_REGEXP
+ def check_headers(v)
+ return true unless v
+ return true if v.size == 0
+ if HEADER_REGEXP !~ v
+ raise InvalidComponentError,
+ "bad component(expected opaque component): #{v}"
+ end
+
+ true
+ end
+ private :check_headers
+
+ # Private setter for headers +v+.
+ def set_headers(v)
+ @headers = []
+ if v
+ v.split('&').each do |x|
+ @headers << x.split(/=/, 2)
+ end
+ end
+ end
+ protected :set_headers
+
+ # Setter for headers +v+.
+ def headers=(v)
+ check_headers(v)
+ set_headers(v)
+ v
+ end
+
+ # Constructs String from Gem::URI.
+ def to_s
+ @scheme + ':' +
+ if @to
+ @to
+ else
+ ''
+ end +
+ if @headers.size > 0
+ '?' + @headers.collect{|x| x.join('=')}.join('&')
+ else
+ ''
+ end +
+ if @fragment
+ '#' + @fragment
+ else
+ ''
+ end
+ end
+
+ # Returns the RFC822 e-mail text equivalent of the URL, as a String.
+ #
+ # Example:
+ #
+ # require 'rubygems/vendor/uri/lib/uri'
+ #
+ # uri = Gem::URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr")
+ # uri.to_mailtext
+ # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n"
+ #
+ def to_mailtext
+ to = Gem::URI.decode_www_form_component(@to)
+ head = ''
+ body = ''
+ @headers.each do |x|
+ case x[0]
+ when 'body'
+ body = Gem::URI.decode_www_form_component(x[1])
+ when 'to'
+ to << ', ' + Gem::URI.decode_www_form_component(x[1])
+ else
+ head << Gem::URI.decode_www_form_component(x[0]).capitalize + ': ' +
+ Gem::URI.decode_www_form_component(x[1]) + "\n"
+ end
+ end
+
+ "To: #{to}
+#{head}
+#{body}
+"
+ end
+ alias to_rfc822text to_mailtext
+ end
+
+ register_scheme 'MAILTO', MailTo
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb
new file mode 100644
index 0000000000..735a269f2c
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb
@@ -0,0 +1,539 @@
+# frozen_string_literal: false
+#--
+# = uri/common.rb
+#
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License::
+# You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+module Gem::URI
+ #
+ # Includes Gem::URI::REGEXP::PATTERN
+ #
+ module RFC2396_REGEXP
+ #
+ # Patterns used to parse Gem::URI's
+ #
+ module PATTERN
+ # :stopdoc:
+
+ # RFC 2396 (Gem::URI Generic Syntax)
+ # RFC 2732 (IPv6 Literal Addresses in URL's)
+ # RFC 2373 (IPv6 Addressing Architecture)
+
+ # alpha = lowalpha | upalpha
+ ALPHA = "a-zA-Z"
+ # alphanum = alpha | digit
+ ALNUM = "#{ALPHA}\\d"
+
+ # hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
+ # "a" | "b" | "c" | "d" | "e" | "f"
+ HEX = "a-fA-F\\d"
+ # escaped = "%" hex hex
+ ESCAPED = "%[#{HEX}]{2}"
+ # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" |
+ # "(" | ")"
+ # unreserved = alphanum | mark
+ UNRESERVED = "\\-_.!~*'()#{ALNUM}"
+ # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
+ # "$" | ","
+ # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
+ # "$" | "," | "[" | "]" (RFC 2732)
+ RESERVED = ";/?:@&=+$,\\[\\]"
+
+ # domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+ DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
+ # toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+ TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
+ # hostname = *( domainlabel "." ) toplabel [ "." ]
+ HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
+
+ # :startdoc:
+ end # PATTERN
+
+ # :startdoc:
+ end # REGEXP
+
+ # Class that parses String's into Gem::URI's.
+ #
+ # It contains a Hash set of patterns and Regexp's that match and validate.
+ #
+ class RFC2396_Parser
+ include RFC2396_REGEXP
+
+ #
+ # == Synopsis
+ #
+ # Gem::URI::Parser.new([opts])
+ #
+ # == Args
+ #
+ # The constructor accepts a hash as options for parser.
+ # Keys of options are pattern names of Gem::URI components
+ # and values of options are pattern strings.
+ # The constructor generates set of regexps for parsing URIs.
+ #
+ # You can use the following keys:
+ #
+ # * :ESCAPED (Gem::URI::PATTERN::ESCAPED in default)
+ # * :UNRESERVED (Gem::URI::PATTERN::UNRESERVED in default)
+ # * :DOMLABEL (Gem::URI::PATTERN::DOMLABEL in default)
+ # * :TOPLABEL (Gem::URI::PATTERN::TOPLABEL in default)
+ # * :HOSTNAME (Gem::URI::PATTERN::HOSTNAME in default)
+ #
+ # == Examples
+ #
+ # p = Gem::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
+ # u = p.parse("http://example.jp/%uABCD") #=> #<Gem::URI::HTTP http://example.jp/%uABCD>
+ # Gem::URI.parse(u.to_s) #=> raises Gem::URI::InvalidURIError
+ #
+ # s = "http://example.com/ABCD"
+ # u1 = p.parse(s) #=> #<Gem::URI::HTTP http://example.com/ABCD>
+ # u2 = Gem::URI.parse(s) #=> #<Gem::URI::HTTP http://example.com/ABCD>
+ # u1 == u2 #=> true
+ # u1.eql?(u2) #=> false
+ #
+ def initialize(opts = {})
+ @pattern = initialize_pattern(opts)
+ @pattern.each_value(&:freeze)
+ @pattern.freeze
+
+ @regexp = initialize_regexp(@pattern)
+ @regexp.each_value(&:freeze)
+ @regexp.freeze
+ end
+
+ # The Hash of patterns.
+ #
+ # See also Gem::URI::Parser.initialize_pattern.
+ attr_reader :pattern
+
+ # The Hash of Regexp.
+ #
+ # See also Gem::URI::Parser.initialize_regexp.
+ attr_reader :regexp
+
+ # Returns a split Gem::URI against +regexp[:ABS_URI]+.
+ def split(uri)
+ case uri
+ when ''
+ # null uri
+
+ when @regexp[:ABS_URI]
+ scheme, opaque, userinfo, host, port,
+ registry, path, query, fragment = $~[1..-1]
+
+ # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+
+ # absoluteURI = scheme ":" ( hier_part | opaque_part )
+ # hier_part = ( net_path | abs_path ) [ "?" query ]
+ # opaque_part = uric_no_slash *uric
+
+ # abs_path = "/" path_segments
+ # net_path = "//" authority [ abs_path ]
+
+ # authority = server | reg_name
+ # server = [ [ userinfo "@" ] hostport ]
+
+ if !scheme
+ raise InvalidURIError,
+ "bad Gem::URI(absolute but no scheme): #{uri}"
+ end
+ if !opaque && (!path && (!host && !registry))
+ raise InvalidURIError,
+ "bad Gem::URI(absolute but no path): #{uri}"
+ end
+
+ when @regexp[:REL_URI]
+ scheme = nil
+ opaque = nil
+
+ userinfo, host, port, registry,
+ rel_segment, abs_path, query, fragment = $~[1..-1]
+ if rel_segment && abs_path
+ path = rel_segment + abs_path
+ elsif rel_segment
+ path = rel_segment
+ elsif abs_path
+ path = abs_path
+ end
+
+ # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+
+ # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+
+ # net_path = "//" authority [ abs_path ]
+ # abs_path = "/" path_segments
+ # rel_path = rel_segment [ abs_path ]
+
+ # authority = server | reg_name
+ # server = [ [ userinfo "@" ] hostport ]
+
+ else
+ raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri}"
+ end
+
+ path = '' if !path && !opaque # (see RFC2396 Section 5.2)
+ ret = [
+ scheme,
+ userinfo, host, port, # X
+ registry, # X
+ path, # Y
+ opaque, # Y
+ query,
+ fragment
+ ]
+ return ret
+ end
+
+ #
+ # == Args
+ #
+ # +uri+::
+ # String
+ #
+ # == Description
+ #
+ # Parses +uri+ and constructs either matching Gem::URI scheme object
+ # (File, FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or Gem::URI::Generic.
+ #
+ # == Usage
+ #
+ # p = Gem::URI::Parser.new
+ # p.parse("ldap://ldap.example.com/dc=example?user=john")
+ # #=> #<Gem::URI::LDAP ldap://ldap.example.com/dc=example?user=john>
+ #
+ def parse(uri)
+ Gem::URI.for(*self.split(uri), self)
+ end
+
+ #
+ # == Args
+ #
+ # +uris+::
+ # an Array of Strings
+ #
+ # == Description
+ #
+ # Attempts to parse and merge a set of URIs.
+ #
+ def join(*uris)
+ uris[0] = convert_to_uri(uris[0])
+ uris.inject :merge
+ end
+
+ #
+ # :call-seq:
+ # extract( str )
+ # extract( str, schemes )
+ # extract( str, schemes ) {|item| block }
+ #
+ # == Args
+ #
+ # +str+::
+ # String to search
+ # +schemes+::
+ # Patterns to apply to +str+
+ #
+ # == Description
+ #
+ # Attempts to parse and merge a set of URIs.
+ # If no +block+ given, then returns the result,
+ # else it calls +block+ for each element in result.
+ #
+ # See also Gem::URI::Parser.make_regexp.
+ #
+ def extract(str, schemes = nil)
+ if block_given?
+ str.scan(make_regexp(schemes)) { yield $& }
+ nil
+ else
+ result = []
+ str.scan(make_regexp(schemes)) { result.push $& }
+ result
+ end
+ end
+
+ # Returns Regexp that is default +self.regexp[:ABS_URI_REF]+,
+ # unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+.
+ def make_regexp(schemes = nil)
+ unless schemes
+ @regexp[:ABS_URI_REF]
+ else
+ /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x
+ end
+ end
+
+ #
+ # :call-seq:
+ # escape( str )
+ # escape( str, unsafe )
+ #
+ # == Args
+ #
+ # +str+::
+ # String to make safe
+ # +unsafe+::
+ # Regexp to apply. Defaults to +self.regexp[:UNSAFE]+
+ #
+ # == Description
+ #
+ # Constructs a safe String from +str+, removing unsafe characters,
+ # replacing them with codes.
+ #
+ def escape(str, unsafe = @regexp[:UNSAFE])
+ unless unsafe.kind_of?(Regexp)
+ # perhaps unsafe is String object
+ unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false)
+ end
+ str.gsub(unsafe) do
+ us = $&
+ tmp = ''
+ us.each_byte do |uc|
+ tmp << sprintf('%%%02X', uc)
+ end
+ tmp
+ end.force_encoding(Encoding::US_ASCII)
+ end
+
+ #
+ # :call-seq:
+ # unescape( str )
+ # unescape( str, escaped )
+ #
+ # == Args
+ #
+ # +str+::
+ # String to remove escapes from
+ # +escaped+::
+ # Regexp to apply. Defaults to +self.regexp[:ESCAPED]+
+ #
+ # == Description
+ #
+ # Removes escapes from +str+.
+ #
+ def unescape(str, escaped = @regexp[:ESCAPED])
+ enc = str.encoding
+ enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
+ str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) }
+ end
+
+ @@to_s = Kernel.instance_method(:to_s)
+ if @@to_s.respond_to?(:bind_call)
+ def inspect
+ @@to_s.bind_call(self)
+ end
+ else
+ def inspect
+ @@to_s.bind(self).call
+ end
+ end
+
+ private
+
+ # Constructs the default Hash of patterns.
+ def initialize_pattern(opts = {})
+ ret = {}
+ ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED)
+ ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED
+ ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED
+ ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL
+ ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL
+ ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME)
+
+ # RFC 2396 (Gem::URI Generic Syntax)
+ # RFC 2732 (IPv6 Literal Addresses in URL's)
+ # RFC 2373 (IPv6 Addressing Architecture)
+
+ # uric = reserved | unreserved | escaped
+ ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})"
+ # uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
+ # "&" | "=" | "+" | "$" | ","
+ ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})"
+ # query = *uric
+ ret[:QUERY] = query = "#{uric}*"
+ # fragment = *uric
+ ret[:FRAGMENT] = fragment = "#{uric}*"
+
+ # hostname = *( domainlabel "." ) toplabel [ "." ]
+ # reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986
+ unless hostname
+ ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+"
+ end
+
+ # RFC 2373, APPENDIX B:
+ # IPv6address = hexpart [ ":" IPv4address ]
+ # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
+ # hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ]
+ # hexseq = hex4 *( ":" hex4)
+ # hex4 = 1*4HEXDIG
+ #
+ # XXX: This definition has a flaw. "::" + IPv4address must be
+ # allowed too. Here is a replacement.
+ #
+ # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
+ ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"
+ # hex4 = 1*4HEXDIG
+ hex4 = "[#{PATTERN::HEX}]{1,4}"
+ # lastpart = hex4 | IPv4address
+ lastpart = "(?:#{hex4}|#{ipv4addr})"
+ # hexseq1 = *( hex4 ":" ) hex4
+ hexseq1 = "(?:#{hex4}:)*#{hex4}"
+ # hexseq2 = *( hex4 ":" ) lastpart
+ hexseq2 = "(?:#{hex4}:)*#{lastpart}"
+ # IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ]
+ ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)"
+
+ # IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT
+ # unused
+
+ # ipv6reference = "[" IPv6address "]" (RFC 2732)
+ ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]"
+
+ # host = hostname | IPv4address
+ # host = hostname | IPv4address | IPv6reference (RFC 2732)
+ ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})"
+ # port = *digit
+ ret[:PORT] = port = '\d*'
+ # hostport = host [ ":" port ]
+ ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?"
+
+ # userinfo = *( unreserved | escaped |
+ # ";" | ":" | "&" | "=" | "+" | "$" | "," )
+ ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*"
+
+ # pchar = unreserved | escaped |
+ # ":" | "@" | "&" | "=" | "+" | "$" | ","
+ pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})"
+ # param = *pchar
+ param = "#{pchar}*"
+ # segment = *pchar *( ";" param )
+ segment = "#{pchar}*(?:;#{param})*"
+ # path_segments = segment *( "/" segment )
+ ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*"
+
+ # server = [ [ userinfo "@" ] hostport ]
+ server = "(?:#{userinfo}@)?#{hostport}"
+ # reg_name = 1*( unreserved | escaped | "$" | "," |
+ # ";" | ":" | "@" | "&" | "=" | "+" )
+ ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+"
+ # authority = server | reg_name
+ authority = "(?:#{server}|#{reg_name})"
+
+ # rel_segment = 1*( unreserved | escaped |
+ # ";" | "@" | "&" | "=" | "+" | "$" | "," )
+ ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+"
+
+ # scheme = alpha *( alpha | digit | "+" | "-" | "." )
+ ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*"
+
+ # abs_path = "/" path_segments
+ ret[:ABS_PATH] = abs_path = "/#{path_segments}"
+ # rel_path = rel_segment [ abs_path ]
+ ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?"
+ # net_path = "//" authority [ abs_path ]
+ ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?"
+
+ # hier_part = ( net_path | abs_path ) [ "?" query ]
+ ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?"
+ # opaque_part = uric_no_slash *uric
+ ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*"
+
+ # absoluteURI = scheme ":" ( hier_part | opaque_part )
+ ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})"
+ # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+ ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?"
+
+ # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?"
+
+ ret[:X_ABS_URI] = "
+ (#{scheme}): (?# 1: scheme)
+ (?:
+ (#{opaque_part}) (?# 2: opaque)
+ |
+ (?:(?:
+ //(?:
+ (?:(?:(#{userinfo})@)? (?# 3: userinfo)
+ (?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port)
+ |
+ (#{reg_name}) (?# 6: registry)
+ )
+ |
+ (?!//)) (?# XXX: '//' is the mark for hostport)
+ (#{abs_path})? (?# 7: path)
+ )(?:\\?(#{query}))? (?# 8: query)
+ )
+ (?:\\#(#{fragment}))? (?# 9: fragment)
+ "
+
+ ret[:X_REL_URI] = "
+ (?:
+ (?:
+ //
+ (?:
+ (?:(#{userinfo})@)? (?# 1: userinfo)
+ (#{host})?(?::(\\d*))? (?# 2: host, 3: port)
+ |
+ (#{reg_name}) (?# 4: registry)
+ )
+ )
+ |
+ (#{rel_segment}) (?# 5: rel_segment)
+ )?
+ (#{abs_path})? (?# 6: abs_path)
+ (?:\\?(#{query}))? (?# 7: query)
+ (?:\\#(#{fragment}))? (?# 8: fragment)
+ "
+
+ ret
+ end
+
+ # Constructs the default Hash of Regexp's.
+ def initialize_regexp(pattern)
+ ret = {}
+
+ # for Gem::URI::split
+ ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED)
+ ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED)
+
+ # for Gem::URI::extract
+ ret[:URI_REF] = Regexp.new(pattern[:URI_REF])
+ ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED)
+ ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED)
+
+ # for Gem::URI::escape/unescape
+ ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED])
+ ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]")
+
+ # for Generic#initialize
+ ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z")
+ ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z")
+ ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z")
+ ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z")
+ ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z")
+ ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z")
+ ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z")
+ ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z")
+ ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z")
+ ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z")
+
+ ret
+ end
+
+ def convert_to_uri(uri)
+ if uri.is_a?(Gem::URI::Generic)
+ uri
+ elsif uri = String.try_convert(uri)
+ parse(uri)
+ else
+ raise ArgumentError,
+ "bad argument (expected Gem::URI object or Gem::URI string)"
+ end
+ end
+
+ end # class Parser
+end # module Gem::URI
diff --git a/lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb b/lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb
new file mode 100644
index 0000000000..728bb55674
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb
@@ -0,0 +1,183 @@
+# frozen_string_literal: true
+module Gem::URI
+ class RFC3986_Parser # :nodoc:
+ # Gem::URI defined in RFC3986
+ HOST = %r[
+ (?<IP-literal>\[(?:
+ (?<IPv6address>
+ (?:\h{1,4}:){6}
+ (?<ls32>\h{1,4}:\h{1,4}
+ | (?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)
+ \.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>)
+ )
+ | ::(?:\h{1,4}:){5}\g<ls32>
+ | \h{1,4}?::(?:\h{1,4}:){4}\g<ls32>
+ | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>
+ | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>
+ | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>
+ | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>
+ | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}
+ | (?:(?:\h{1,4}:){,6}\h{1,4})?::
+ )
+ | (?<IPvFuture>v\h++\.[!$&-.0-9:;=A-Z_a-z~]++)
+ )\])
+ | \g<IPv4address>
+ | (?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+)
+ ]x
+
+ USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/
+
+ SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source
+ SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source
+ SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source
+ FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source
+
+ RFC3986_URI = %r[\A
+ (?<seg>#{SEG}){0}
+ (?<Gem::URI>
+ (?<scheme>#{SCHEME}):
+ (?<hier-part>//
+ (?<authority>
+ (?:(?<userinfo>#{USERINFO.source})@)?
+ (?<host>#{HOST.source.delete(" \n")})
+ (?::(?<port>\d*+))?
+ )
+ (?<path-abempty>(?:/\g<seg>*+)?)
+ | (?<path-absolute>/((?!/)\g<seg>++)?)
+ | (?<path-rootless>(?!/)\g<seg>++)
+ | (?<path-empty>)
+ )
+ (?:\?(?<query>[^\#]*+))?
+ (?:\#(?<fragment>#{FRAGMENT}))?
+ )\z]x
+
+ RFC3986_relative_ref = %r[\A
+ (?<seg>#{SEG}){0}
+ (?<relative-ref>
+ (?<relative-part>//
+ (?<authority>
+ (?:(?<userinfo>#{USERINFO.source})@)?
+ (?<host>#{HOST.source.delete(" \n")}(?<!/))?
+ (?::(?<port>\d*+))?
+ )
+ (?<path-abempty>(?:/\g<seg>*+)?)
+ | (?<path-absolute>/\g<seg>*+)
+ | (?<path-noscheme>#{SEG_NC}++(?:/\g<seg>*+)?)
+ | (?<path-empty>)
+ )
+ (?:\?(?<query>[^#]*+))?
+ (?:\#(?<fragment>#{FRAGMENT}))?
+ )\z]x
+ attr_reader :regexp
+
+ def initialize
+ @regexp = default_regexp.each_value(&:freeze).freeze
+ end
+
+ def split(uri) #:nodoc:
+ begin
+ uri = uri.to_str
+ rescue NoMethodError
+ raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri.inspect}"
+ end
+ uri.ascii_only? or
+ raise InvalidURIError, "Gem::URI must be ascii only #{uri.dump}"
+ if m = RFC3986_URI.match(uri)
+ query = m["query"]
+ scheme = m["scheme"]
+ opaque = m["path-rootless"]
+ if opaque
+ opaque << "?#{query}" if query
+ [ scheme,
+ nil, # userinfo
+ nil, # host
+ nil, # port
+ nil, # registry
+ nil, # path
+ opaque,
+ nil, # query
+ m["fragment"]
+ ]
+ else # normal
+ [ scheme,
+ m["userinfo"],
+ m["host"],
+ m["port"],
+ nil, # registry
+ (m["path-abempty"] ||
+ m["path-absolute"] ||
+ m["path-empty"]),
+ nil, # opaque
+ query,
+ m["fragment"]
+ ]
+ end
+ elsif m = RFC3986_relative_ref.match(uri)
+ [ nil, # scheme
+ m["userinfo"],
+ m["host"],
+ m["port"],
+ nil, # registry,
+ (m["path-abempty"] ||
+ m["path-absolute"] ||
+ m["path-noscheme"] ||
+ m["path-empty"]),
+ nil, # opaque
+ m["query"],
+ m["fragment"]
+ ]
+ else
+ raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri.inspect}"
+ end
+ end
+
+ def parse(uri) # :nodoc:
+ Gem::URI.for(*self.split(uri), self)
+ end
+
+
+ def join(*uris) # :nodoc:
+ uris[0] = convert_to_uri(uris[0])
+ uris.inject :merge
+ end
+
+ @@to_s = Kernel.instance_method(:to_s)
+ if @@to_s.respond_to?(:bind_call)
+ def inspect
+ @@to_s.bind_call(self)
+ end
+ else
+ def inspect
+ @@to_s.bind(self).call
+ end
+ end
+
+ private
+
+ def default_regexp # :nodoc:
+ {
+ SCHEME: %r[\A#{SCHEME}\z]o,
+ USERINFO: %r[\A#{USERINFO}\z]o,
+ HOST: %r[\A#{HOST}\z]o,
+ ABS_PATH: %r[\A/#{SEG}*+\z]o,
+ REL_PATH: %r[\A(?!/)#{SEG}++\z]o,
+ QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z],
+ FRAGMENT: %r[\A#{FRAGMENT}\z]o,
+ OPAQUE: %r[\A(?:[^/].*)?\z],
+ PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/,
+ }
+ end
+
+ def convert_to_uri(uri)
+ if uri.is_a?(Gem::URI::Generic)
+ uri
+ elsif uri = String.try_convert(uri)
+ parse(uri)
+ else
+ raise ArgumentError,
+ "bad argument (expected Gem::URI object or Gem::URI string)"
+ end
+ end
+
+ end # class Parser
+end # module Gem::URI
diff --git a/lib/rubygems/vendor/uri/lib/uri/version.rb b/lib/rubygems/vendor/uri/lib/uri/version.rb
new file mode 100644
index 0000000000..3c80c334d4
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/version.rb
@@ -0,0 +1,6 @@
+module Gem::URI
+ # :stopdoc:
+ VERSION_CODE = '001300'.freeze
+ VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
+ # :startdoc:
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/ws.rb b/lib/rubygems/vendor/uri/lib/uri/ws.rb
new file mode 100644
index 0000000000..0dd2a7a1bb
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/ws.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: false
+# = uri/ws.rb
+#
+# Author:: Matt Muller <mamuller@amazon.com>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'generic'
+
+module Gem::URI
+
+ #
+ # The syntax of WS URIs is defined in RFC6455 section 3.
+ #
+ # Note that the Ruby Gem::URI library allows WS URLs containing usernames and
+ # passwords. This is not legal as per the RFC, but used to be
+ # supported in Internet Explorer 5 and 6, before the MS04-004 security
+ # update. See <URL:http://support.microsoft.com/kb/834489>.
+ #
+ class WS < Generic
+ # A Default port of 80 for Gem::URI::WS.
+ DEFAULT_PORT = 80
+
+ # An Array of the available components for Gem::URI::WS.
+ COMPONENT = %i[
+ scheme
+ userinfo host port
+ path
+ query
+ ].freeze
+
+ #
+ # == Description
+ #
+ # Creates a new Gem::URI::WS object from components, with syntax checking.
+ #
+ # The components accepted are userinfo, host, port, path, and query.
+ #
+ # The components should be provided either as an Array, or as a Hash
+ # with keys formed by preceding the component names with a colon.
+ #
+ # If an Array is used, the components must be passed in the
+ # order <code>[userinfo, host, port, path, query]</code>.
+ #
+ # Example:
+ #
+ # uri = Gem::URI::WS.build(host: 'www.example.com', path: '/foo/bar')
+ #
+ # uri = Gem::URI::WS.build([nil, "www.example.com", nil, "/path", "query"])
+ #
+ # Currently, if passed userinfo components this method generates
+ # invalid WS URIs as per RFC 1738.
+ #
+ def self.build(args)
+ tmp = Util.make_components_hash(self, args)
+ super(tmp)
+ end
+
+ #
+ # == Description
+ #
+ # Returns the full path for a WS Gem::URI, as required by Net::HTTP::Get.
+ #
+ # If the Gem::URI contains a query, the full path is Gem::URI#path + '?' + Gem::URI#query.
+ # Otherwise, the path is simply Gem::URI#path.
+ #
+ # Example:
+ #
+ # uri = Gem::URI::WS.build(path: '/foo/bar', query: 'test=true')
+ # uri.request_uri # => "/foo/bar?test=true"
+ #
+ def request_uri
+ return unless @path
+
+ url = @query ? "#@path?#@query" : @path.dup
+ url.start_with?(?/.freeze) ? url : ?/ + url
+ end
+ end
+
+ register_scheme 'WS', WS
+end
diff --git a/lib/rubygems/vendor/uri/lib/uri/wss.rb b/lib/rubygems/vendor/uri/lib/uri/wss.rb
new file mode 100644
index 0000000000..0b91d334bb
--- /dev/null
+++ b/lib/rubygems/vendor/uri/lib/uri/wss.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: false
+# = uri/wss.rb
+#
+# Author:: Matt Muller <mamuller@amazon.com>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
+#
+# See Gem::URI for general documentation
+#
+
+require_relative 'ws'
+
+module Gem::URI
+
+ # The default port for WSS URIs is 443, and the scheme is 'wss:' rather
+ # than 'ws:'. Other than that, WSS URIs are identical to WS URIs;
+ # see Gem::URI::WS.
+ class WSS < WS
+ # A Default port of 443 for Gem::URI::WSS
+ DEFAULT_PORT = 443
+ end
+
+ register_scheme 'WSS', WSS
+end
diff --git a/lib/rubygems/vendored_molinillo.rb b/lib/rubygems/vendored_molinillo.rb
new file mode 100644
index 0000000000..45906c0e5c
--- /dev/null
+++ b/lib/rubygems/vendored_molinillo.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+require_relative "vendor/molinillo/lib/molinillo"
diff --git a/lib/rubygems/vendored_net_http.rb b/lib/rubygems/vendored_net_http.rb
new file mode 100644
index 0000000000..a84c52a947
--- /dev/null
+++ b/lib/rubygems/vendored_net_http.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+# Ruby 3.3 and RubyGems 3.5 is already load Gem::Timeout from lib/rubygems/net/http.rb
+# We should avoid to load it again
+require_relative "vendor/net-http/lib/net/http" unless defined?(Gem::Net::HTTP)
diff --git a/lib/rubygems/vendored_optparse.rb b/lib/rubygems/vendored_optparse.rb
new file mode 100644
index 0000000000..a5611d32f0
--- /dev/null
+++ b/lib/rubygems/vendored_optparse.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+require_relative "vendor/optparse/lib/optparse"
diff --git a/lib/rubygems/vendored_timeout.rb b/lib/rubygems/vendored_timeout.rb
new file mode 100644
index 0000000000..45541928e6
--- /dev/null
+++ b/lib/rubygems/vendored_timeout.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+# Ruby 3.3 and RubyGems 3.5 is already load Gem::Timeout from lib/rubygems/timeout.rb
+# We should avoid to load it again
+require_relative "vendor/timeout/lib/timeout" unless defined?(Gem::Timeout)
diff --git a/lib/rubygems/vendored_tsort.rb b/lib/rubygems/vendored_tsort.rb
new file mode 100644
index 0000000000..c3d815650d
--- /dev/null
+++ b/lib/rubygems/vendored_tsort.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+require_relative "vendor/tsort/lib/tsort"
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index 20bbff4fdd..e174d8ad95 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -1,4 +1,7 @@
# frozen_string_literal: true
+
+require_relative "deprecate"
+
##
# The Version class processes string versions into comparable
# values. A version string should normally be a series of numbers
@@ -128,7 +131,7 @@
#
# == Preventing Version Catastrophe:
#
-# From: http://blog.zenspider.com/2008/10/rubygems-howto-preventing-cata.html
+# From: https://www.zenspider.com/ruby/2008/10/rubygems-how-to-preventing-catastrophe.html
#
# Let's say you're depending on the fnord gem version 2.y.z. If you
# specify your dependency as ">= 2.0.0" then, you're good, right? What
@@ -150,31 +153,27 @@
# a zero to give a sensible result.
class Gem::Version
- autoload :Requirement, File.expand_path('requirement', __dir__)
-
include Comparable
- VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?'.freeze # :nodoc:
- ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/.freeze # :nodoc:
+ VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc:
+ ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc:
##
# A string representation of this Version.
def version
- @version.dup
+ @version
end
- alias to_s version
+ alias_method :to_s, :version
##
# True if the +version+ string matches RubyGems' requirements.
def self.correct?(version)
- unless Gem::Deprecate.skip
- warn "nil versions are discouraged and will be deprecated in Rubygems 4" if version.nil?
- end
+ nil_versions_are_discouraged! if version.nil?
- !!(version.to_s =~ ANCHORED_VERSION_PATTERN)
+ ANCHORED_VERSION_PATTERN.match?(version.to_s)
end
##
@@ -189,6 +188,8 @@ class Gem::Version
if self === input # check yourself before you wreck yourself
input
elsif input.nil?
+ nil_versions_are_discouraged!
+
nil
else
new input
@@ -200,11 +201,19 @@ class Gem::Version
@@release = {}
def self.new(version) # :nodoc:
- return super unless Gem::Version == self
+ return super unless self == Gem::Version
@@all[version] ||= super
end
+ def self.nil_versions_are_discouraged!
+ unless Gem::Deprecate.skip
+ warn "nil versions are discouraged and will be deprecated in Rubygems 4"
+ end
+ end
+
+ private_class_method :nil_versions_are_discouraged!
+
##
# Constructs a Version from the +version+ string. A version string is a
# series of digits or ASCII letters separated by dots.
@@ -215,9 +224,17 @@ class Gem::Version
end
# If version is an empty string convert it to 0
- version = 0 if version.is_a?(String) && version =~ /\A\s*\Z/
+ version = 0 if version.is_a?(String) && /\A\s*\Z/.match?(version)
- @version = version.to_s.strip.gsub("-",".pre.")
+ @version = version.to_s
+
+ # optimization to avoid allocation when given an integer, since we know
+ # it's to_s won't have any spaces or dashes
+ unless version.is_a?(Integer)
+ @version = @version.strip
+ @version.gsub!("-",".pre.")
+ end
+ @version = -@version
@segments = nil
end
@@ -243,7 +260,7 @@ class Gem::Version
# same precision. Version "1.0" is not the same as version "1".
def eql?(other)
- self.class === other and @version == other._version
+ self.class === other && @version == other.version
end
def hash # :nodoc:
@@ -263,7 +280,7 @@ class Gem::Version
# string for backwards (RubyGems 1.3.5 and earlier) compatibility.
def marshal_dump
- [version]
+ [@version]
end
##
@@ -275,7 +292,7 @@ class Gem::Version
end
def yaml_initialize(tag, map) # :nodoc:
- @version = map['version']
+ @version = -map["version"]
@segments = nil
@hash = nil
end
@@ -285,7 +302,7 @@ class Gem::Version
end
def encode_with(coder) # :nodoc:
- coder.add 'version', @version
+ coder.add "version", @version
end
##
@@ -293,7 +310,7 @@ class Gem::Version
def prerelease?
unless instance_variable_defined? :@prerelease
- @prerelease = !!(@version =~ /[a-zA-Z]/)
+ @prerelease = /[a-zA-Z]/.match?(version)
end
@prerelease
end
@@ -308,12 +325,12 @@ class Gem::Version
def release
@@release[self] ||= if prerelease?
- segments = self.segments
- segments.pop while segments.any? {|s| String === s }
- self.class.new segments.join('.')
- else
- self
- end
+ segments = self.segments
+ segments.pop while segments.any? {|s| String === s }
+ self.class.new segments.join(".")
+ else
+ self
+ end
end
def segments # :nodoc:
@@ -339,11 +356,13 @@ class Gem::Version
# Compares this version with +other+ returning -1, 0, or 1 if the
# other version is larger, the same, or smaller than this
# one. Attempts to compare to something that's not a
- # <tt>Gem::Version</tt> return +nil+.
+ # <tt>Gem::Version</tt> or a valid version String return +nil+.
def <=>(other)
+ return self <=> self.class.new(other) if (String === other) && self.class.correct?(other)
+
return unless Gem::Version === other
- return 0 if @version == other._version || canonical_segments == other.canonical_segments
+ return 0 if @version == other.version || canonical_segments == other.canonical_segments
lhsegments = canonical_segments
rhsegments = other.canonical_segments
@@ -355,7 +374,8 @@ class Gem::Version
i = 0
while i <= limit
- lhs, rhs = lhsegments[i] || 0, rhsegments[i] || 0
+ lhs = lhsegments[i] || 0
+ rhs = rhsegments[i] || 0
i += 1
next if lhs == rhs
@@ -365,42 +385,40 @@ class Gem::Version
return lhs <=> rhs
end
- return 0
+ 0
end
+ # remove trailing zeros segments before first letter or at the end of the version
def canonical_segments
- @canonical_segments ||=
- _split_segments.map! do |segments|
- segments.reverse_each.drop_while {|s| s == 0 }.reverse
- end.reduce(&:concat)
+ @canonical_segments ||= begin
+ # remove trailing 0 segments, using dot or letter as anchor
+ # may leave a trailing dot which will be ignored by partition_segments
+ canonical_version = @version.sub(/(?<=[a-zA-Z.])[.0]+\z/, "")
+ # remove 0 segments before the first letter in a prerelease version
+ canonical_version.sub!(/(?<=\.|\A)[0.]+(?=[a-zA-Z])/, "") if prerelease?
+ partition_segments(canonical_version)
+ end
end
def freeze
prerelease?
+ _segments
canonical_segments
super
end
protected
- def _version
- @version
- end
-
def _segments
# segments is lazy so it can pick up version values that come from
# old marshaled versions, which don't go through marshal_load.
# since this version object is cached in @@all, its @segments should be frozen
-
- @segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
- /^\d+$/ =~ s ? s.to_i : s
- end.freeze
+ @segments ||= partition_segments(@version)
end
- def _split_segments
- string_start = _segments.index {|s| s.is_a?(String) }
- string_segments = segments
- numeric_segments = string_segments.slice!(0, string_start || string_segments.size)
- return numeric_segments, string_segments
+ def partition_segments(ver)
+ ver.scan(/\d+|[a-z]+/i).map! do |s|
+ /\A\d/.match?(s) ? s.to_i : -s
+ end.freeze
end
end
diff --git a/lib/rubygems/version_option.rb b/lib/rubygems/version_option.rb
index be71ef409b..7910fd3d1b 100644
--- a/lib/rubygems/version_option.rb
+++ b/lib/rubygems/version_option.rb
@@ -1,22 +1,22 @@
# frozen_string_literal: true
+
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
-require 'rubygems'
+require_relative "../rubygems"
##
# Mixin methods for --version and --platform Gem::Command options.
module Gem::VersionOption
-
##
# Add the --platform option to the option parser.
def add_platform_option(task = command, *wrap)
- OptionParser.accept Gem::Platform do |value|
+ Gem::OptionParser.accept Gem::Platform do |value|
if value == Gem::Platform::RUBY
value
else
@@ -24,9 +24,8 @@ module Gem::VersionOption
end
end
- add_option('--platform PLATFORM', Gem::Platform,
- "Specify the platform of gem to #{task}", *wrap) do
- |value, options|
+ add_option("--platform PLATFORM", Gem::Platform,
+ "Specify the platform of gem to #{task}", *wrap) do |value, options|
unless options[:added_platform]
Gem.platforms = [Gem::Platform::RUBY]
options[:added_platform] = true
@@ -51,13 +50,12 @@ module Gem::VersionOption
# Add the --version option to the option parser.
def add_version_option(task = command, *wrap)
- OptionParser.accept Gem::Requirement do |value|
+ Gem::OptionParser.accept Gem::Requirement do |value|
Gem::Requirement.new(*value.split(/\s*,\s*/))
end
- add_option('-v', '--version VERSION', Gem::Requirement,
- "Specify version of gem to #{task}", *wrap) do
- |value, options|
+ add_option("-v", "--version VERSION", Gem::Requirement,
+ "Specify version of gem to #{task}", *wrap) do |value, options|
# Allow handling for multiple --version operators
if options[:version] && !options[:version].none?
options[:version].concat([value])
diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb
new file mode 100644
index 0000000000..128becc1ce
--- /dev/null
+++ b/lib/rubygems/yaml_serializer.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+module Gem
+ # A stub yaml serializer that can handle only hashes and strings (as of now).
+ module YAMLSerializer
+ module_function
+
+ def dump(hash)
+ yaml = String.new("---")
+ yaml << dump_hash(hash)
+ end
+
+ def dump_hash(hash)
+ yaml = String.new("\n")
+ hash.each do |k, v|
+ yaml << k << ":"
+ if v.is_a?(Hash)
+ yaml << dump_hash(v).gsub(/^(?!$)/, " ") # indent all non-empty lines
+ elsif v.is_a?(Array) # Expected to be array of strings
+ if v.empty?
+ yaml << " []\n"
+ else
+ yaml << "\n- " << v.map {|s| s.to_s.gsub(/\s+/, " ").inspect }.join("\n- ") << "\n"
+ end
+ else
+ yaml << " " << v.to_s.gsub(/\s+/, " ").inspect << "\n"
+ end
+ end
+ yaml
+ end
+
+ ARRAY_REGEX = /
+ ^
+ (?:[ ]*-[ ]) # '- ' before array items
+ (['"]?) # optional opening quote
+ (.*) # value
+ \1 # matching closing quote
+ $
+ /xo
+
+ HASH_REGEX = /
+ ^
+ ([ ]*) # indentations
+ (.+) # key
+ (?::(?=(?:\s|$))) # : (without the lookahead the #key includes this when : is present in value)
+ [ ]?
+ (['"]?) # optional opening quote
+ (.*) # value
+ \3 # matching closing quote
+ $
+ /xo
+
+ def load(str)
+ res = {}
+ stack = [res]
+ last_hash = nil
+ last_empty_key = nil
+ str.split(/\r?\n/) do |line|
+ if match = HASH_REGEX.match(line)
+ indent, key, quote, val = match.captures
+ val = strip_comment(val)
+
+ convert_to_backward_compatible_key!(key)
+ depth = indent.size / 2
+ if quote.empty? && val.empty?
+ new_hash = {}
+ stack[depth][key] = new_hash
+ stack[depth + 1] = new_hash
+ last_empty_key = key
+ last_hash = stack[depth]
+ else
+ val = [] if val == "[]" # empty array
+ stack[depth][key] = val
+ end
+ elsif match = ARRAY_REGEX.match(line)
+ _, val = match.captures
+ val = strip_comment(val)
+
+ last_hash[last_empty_key] = [] unless last_hash[last_empty_key].is_a?(Array)
+
+ last_hash[last_empty_key].push(val)
+ end
+ end
+ res
+ end
+
+ def strip_comment(val)
+ if val.include?("#") && !val.start_with?("#")
+ val.split("#", 2).first.strip
+ else
+ val
+ end
+ end
+
+ # for settings' keys
+ def convert_to_backward_compatible_key!(key)
+ key << "/" if /https?:/i.match?(key) && !%r{/\Z}.match?(key)
+ key.gsub!(".", "__")
+ end
+
+ class << self
+ private :dump_hash, :convert_to_backward_compatible_key!
+ end
+ end
+end