From 7227b859a7bf7626ee73de8130796657b7c7f3b5 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Apr 2024 11:22:22 +0900 Subject: Merge RubyGems 3.5.9 and Bundler 2.5.9 (Fixed CI at Ruby 3.3) (#10348) * Merge RubyGems-3.5.6 and Bundler-2.5.6 * Merge RubyGems-3.5.7 and Bundler-2.5.7 * Merge RubyGems-3.5.8 and Bundler-2.5.8 * Partly reverted about https://github.com/rubygems/rubygems/pull/7483 * Merge RubyGems-3.5.9 and Bundler-2.5.9 --- lib/bundler.rb | 5 +- lib/bundler/cli.rb | 4 +- lib/bundler/cli/binstubs.rb | 2 +- lib/bundler/cli/gem.rb | 2 +- lib/bundler/cli/lock.rb | 9 +- lib/bundler/cli/plugin.rb | 5 +- lib/bundler/definition.rb | 82 +- lib/bundler/dsl.rb | 17 +- lib/bundler/environment_preserver.rb | 6 +- lib/bundler/fetcher.rb | 4 +- lib/bundler/fetcher/downloader.rb | 2 +- lib/bundler/gem_version_promoter.rb | 80 +- lib/bundler/injector.rb | 2 +- lib/bundler/installer.rb | 4 +- lib/bundler/man/bundle-add.1 | 2 +- lib/bundler/man/bundle-binstubs.1 | 2 +- lib/bundler/man/bundle-cache.1 | 2 +- lib/bundler/man/bundle-check.1 | 2 +- lib/bundler/man/bundle-clean.1 | 2 +- lib/bundler/man/bundle-config.1 | 2 +- lib/bundler/man/bundle-console.1 | 2 +- lib/bundler/man/bundle-doctor.1 | 2 +- lib/bundler/man/bundle-exec.1 | 2 +- lib/bundler/man/bundle-gem.1 | 2 +- lib/bundler/man/bundle-help.1 | 2 +- lib/bundler/man/bundle-info.1 | 2 +- lib/bundler/man/bundle-init.1 | 2 +- lib/bundler/man/bundle-inject.1 | 2 +- lib/bundler/man/bundle-install.1 | 6 +- lib/bundler/man/bundle-install.1.ronn | 4 +- lib/bundler/man/bundle-list.1 | 2 +- lib/bundler/man/bundle-lock.1 | 2 +- lib/bundler/man/bundle-open.1 | 2 +- lib/bundler/man/bundle-outdated.1 | 2 +- lib/bundler/man/bundle-platform.1 | 2 +- lib/bundler/man/bundle-plugin.1 | 11 +- lib/bundler/man/bundle-plugin.1.ronn | 10 +- lib/bundler/man/bundle-pristine.1 | 2 +- lib/bundler/man/bundle-remove.1 | 2 +- lib/bundler/man/bundle-show.1 | 2 +- lib/bundler/man/bundle-update.1 | 2 +- lib/bundler/man/bundle-version.1 | 2 +- lib/bundler/man/bundle-viz.1 | 2 +- lib/bundler/man/bundle.1 | 2 +- lib/bundler/man/gemfile.5 | 6 +- lib/bundler/man/gemfile.5.ronn | 4 +- lib/bundler/mirror.rb | 6 +- lib/bundler/plugin/api/source.rb | 4 +- lib/bundler/plugin/installer.rb | 52 +- lib/bundler/plugin/installer/path.rb | 18 + lib/bundler/plugin/source_list.rb | 8 +- lib/bundler/resolver.rb | 78 +- lib/bundler/resolver/candidate.rb | 2 +- lib/bundler/runtime.rb | 2 +- lib/bundler/self_manager.rb | 2 +- lib/bundler/settings.rb | 25 +- lib/bundler/source/git.rb | 2 +- lib/bundler/source/git/git_proxy.rb | 2 +- lib/bundler/source/rubygems.rb | 8 +- lib/bundler/source/rubygems/remote.rb | 2 +- lib/bundler/source_list.rb | 4 +- lib/bundler/spec_set.rb | 2 +- lib/bundler/templates/newgem/newgem.gemspec.tt | 7 +- lib/bundler/templates/newgem/rubocop.yml.tt | 5 - lib/bundler/uri_credentials_filter.rb | 4 +- .../net-http-persistent/lib/net/http/persistent.rb | 44 +- .../pub_grub/lib/pub_grub/static_package_source.rb | 1 + lib/bundler/vendored_net_http.rb | 10 +- lib/bundler/vendored_timeout.rb | 10 +- lib/bundler/vendored_uri.rb | 19 +- lib/bundler/version.rb | 2 +- lib/bundler/yaml_serializer.rb | 12 + lib/rubygems.rb | 4 +- lib/rubygems/command.rb | 2 +- lib/rubygems/command_manager.rb | 3 +- lib/rubygems/commands/build_command.rb | 13 +- lib/rubygems/commands/help_command.rb | 4 +- lib/rubygems/commands/rdoc_command.rb | 9 +- lib/rubygems/commands/rebuild_command.rb | 264 ++ lib/rubygems/commands/sources_command.rb | 4 +- lib/rubygems/config_file.rb | 28 +- lib/rubygems/defaults.rb | 8 +- lib/rubygems/dependency.rb | 4 +- lib/rubygems/dependency_list.rb | 2 +- lib/rubygems/ext/cargo_builder.rb | 2 +- lib/rubygems/gemcutter_utilities.rb | 63 +- .../gemcutter_utilities/webauthn_listener.rb | 2 +- lib/rubygems/gemspec_helpers.rb | 19 + lib/rubygems/local_remote_options.rb | 12 +- lib/rubygems/net-http/.document | 1 - lib/rubygems/net-http/lib/net/http.rb | 2496 --------------- lib/rubygems/net-http/lib/net/http/backward.rb | 40 - lib/rubygems/net-http/lib/net/http/exceptions.rb | 34 - .../net-http/lib/net/http/generic_request.rb | 414 --- lib/rubygems/net-http/lib/net/http/header.rb | 981 ------ lib/rubygems/net-http/lib/net/http/proxy_delta.rb | 17 - lib/rubygems/net-http/lib/net/http/request.rb | 88 - lib/rubygems/net-http/lib/net/http/requests.rb | 425 --- lib/rubygems/net-http/lib/net/http/response.rb | 738 ----- lib/rubygems/net-http/lib/net/http/responses.rb | 1174 ------- lib/rubygems/net-http/lib/net/http/status.rb | 84 - lib/rubygems/net-http/lib/net/https.rb | 23 - lib/rubygems/net-protocol/.document | 1 - lib/rubygems/net-protocol/lib/net/protocol.rb | 544 ---- lib/rubygems/net/http.rb | 3 - lib/rubygems/optparse.rb | 3 - lib/rubygems/optparse/.document | 1 - lib/rubygems/optparse/lib/optionparser.rb | 2 - lib/rubygems/optparse/lib/optparse.rb | 2330 -------------- lib/rubygems/optparse/lib/optparse/ac.rb | 54 - lib/rubygems/optparse/lib/optparse/date.rb | 18 - lib/rubygems/optparse/lib/optparse/kwargs.rb | 22 - lib/rubygems/optparse/lib/optparse/shellwords.rb | 7 - lib/rubygems/optparse/lib/optparse/time.rb | 11 - lib/rubygems/optparse/lib/optparse/uri.rb | 7 - lib/rubygems/optparse/lib/optparse/version.rb | 71 - lib/rubygems/package.rb | 4 +- lib/rubygems/remote_fetcher.rb | 6 +- lib/rubygems/request.rb | 10 +- lib/rubygems/request_set.rb | 2 +- lib/rubygems/requirement.rb | 5 + lib/rubygems/resolv/.document | 1 - lib/rubygems/resolv/lib/resolv.rb | 3387 -------------------- lib/rubygems/resolver.rb | 10 +- lib/rubygems/resolver/api_set.rb | 2 +- lib/rubygems/resolver/best_set.rb | 2 +- lib/rubygems/resolver/molinillo.rb | 3 - lib/rubygems/resolver/molinillo/.document | 1 - lib/rubygems/resolver/molinillo/lib/molinillo.rb | 11 - .../lib/molinillo/delegates/resolution_state.rb | 57 - .../molinillo/delegates/specification_provider.rb | 88 - .../molinillo/lib/molinillo/dependency_graph.rb | 255 -- .../lib/molinillo/dependency_graph/action.rb | 36 - .../dependency_graph/add_edge_no_circular.rb | 66 - .../lib/molinillo/dependency_graph/add_vertex.rb | 62 - .../lib/molinillo/dependency_graph/delete_edge.rb | 63 - .../dependency_graph/detach_vertex_named.rb | 61 - .../lib/molinillo/dependency_graph/log.rb | 126 - .../lib/molinillo/dependency_graph/set_payload.rb | 46 - .../lib/molinillo/dependency_graph/tag.rb | 36 - .../lib/molinillo/dependency_graph/vertex.rb | 164 - .../resolver/molinillo/lib/molinillo/errors.rb | 149 - .../molinillo/lib/molinillo/gem_metadata.rb | 6 - .../molinillo/modules/specification_provider.rb | 112 - .../resolver/molinillo/lib/molinillo/modules/ui.rb | 67 - .../resolver/molinillo/lib/molinillo/resolution.rb | 839 ----- .../resolver/molinillo/lib/molinillo/resolver.rb | 46 - .../resolver/molinillo/lib/molinillo/state.rb | 58 - lib/rubygems/resolver/spec_specification.rb | 7 + lib/rubygems/s3_uri_signer.rb | 6 +- lib/rubygems/safe_yaml.rb | 11 +- lib/rubygems/security.rb | 2 +- lib/rubygems/source/git.rb | 4 +- lib/rubygems/source_list.rb | 2 +- lib/rubygems/specification.rb | 12 +- lib/rubygems/specification_policy.rb | 22 +- lib/rubygems/timeout.rb | 3 - lib/rubygems/timeout/.document | 1 - lib/rubygems/timeout/lib/timeout.rb | 199 -- lib/rubygems/tsort.rb | 3 - lib/rubygems/tsort/.document | 1 - lib/rubygems/tsort/lib/tsort.rb | 455 --- lib/rubygems/uri.rb | 12 +- lib/rubygems/util.rb | 2 +- lib/rubygems/util/licenses.rb | 43 + lib/rubygems/vendor/molinillo/.document | 1 + lib/rubygems/vendor/molinillo/lib/molinillo.rb | 11 + .../lib/molinillo/delegates/resolution_state.rb | 57 + .../molinillo/delegates/specification_provider.rb | 88 + .../molinillo/lib/molinillo/dependency_graph.rb | 255 ++ .../lib/molinillo/dependency_graph/action.rb | 36 + .../dependency_graph/add_edge_no_circular.rb | 66 + .../lib/molinillo/dependency_graph/add_vertex.rb | 62 + .../lib/molinillo/dependency_graph/delete_edge.rb | 63 + .../dependency_graph/detach_vertex_named.rb | 61 + .../lib/molinillo/dependency_graph/log.rb | 126 + .../lib/molinillo/dependency_graph/set_payload.rb | 46 + .../lib/molinillo/dependency_graph/tag.rb | 36 + .../lib/molinillo/dependency_graph/vertex.rb | 164 + .../vendor/molinillo/lib/molinillo/errors.rb | 149 + .../vendor/molinillo/lib/molinillo/gem_metadata.rb | 6 + .../molinillo/modules/specification_provider.rb | 112 + .../vendor/molinillo/lib/molinillo/modules/ui.rb | 67 + .../vendor/molinillo/lib/molinillo/resolution.rb | 839 +++++ .../vendor/molinillo/lib/molinillo/resolver.rb | 46 + .../vendor/molinillo/lib/molinillo/state.rb | 58 + lib/rubygems/vendor/net-http/.document | 1 + lib/rubygems/vendor/net-http/lib/net/http.rb | 2496 +++++++++++++++ .../vendor/net-http/lib/net/http/backward.rb | 40 + .../vendor/net-http/lib/net/http/exceptions.rb | 34 + .../net-http/lib/net/http/generic_request.rb | 414 +++ .../vendor/net-http/lib/net/http/header.rb | 981 ++++++ .../vendor/net-http/lib/net/http/proxy_delta.rb | 17 + .../vendor/net-http/lib/net/http/request.rb | 88 + .../vendor/net-http/lib/net/http/requests.rb | 425 +++ .../vendor/net-http/lib/net/http/response.rb | 738 +++++ .../vendor/net-http/lib/net/http/responses.rb | 1174 +++++++ .../vendor/net-http/lib/net/http/status.rb | 84 + lib/rubygems/vendor/net-http/lib/net/https.rb | 23 + lib/rubygems/vendor/net-protocol/.document | 1 + .../vendor/net-protocol/lib/net/protocol.rb | 544 ++++ lib/rubygems/vendor/optparse/.document | 1 + lib/rubygems/vendor/optparse/lib/optionparser.rb | 2 + lib/rubygems/vendor/optparse/lib/optparse.rb | 2330 ++++++++++++++ lib/rubygems/vendor/optparse/lib/optparse/ac.rb | 54 + lib/rubygems/vendor/optparse/lib/optparse/date.rb | 18 + .../vendor/optparse/lib/optparse/kwargs.rb | 22 + .../vendor/optparse/lib/optparse/shellwords.rb | 7 + lib/rubygems/vendor/optparse/lib/optparse/time.rb | 11 + lib/rubygems/vendor/optparse/lib/optparse/uri.rb | 7 + .../vendor/optparse/lib/optparse/version.rb | 71 + lib/rubygems/vendor/resolv/.document | 1 + lib/rubygems/vendor/resolv/lib/resolv.rb | 3387 ++++++++++++++++++++ lib/rubygems/vendor/timeout/.document | 1 + lib/rubygems/vendor/timeout/lib/timeout.rb | 199 ++ lib/rubygems/vendor/tsort/.document | 1 + lib/rubygems/vendor/tsort/lib/tsort.rb | 455 +++ lib/rubygems/vendor/uri/.document | 1 + lib/rubygems/vendor/uri/lib/uri.rb | 104 + lib/rubygems/vendor/uri/lib/uri/common.rb | 853 +++++ lib/rubygems/vendor/uri/lib/uri/file.rb | 100 + lib/rubygems/vendor/uri/lib/uri/ftp.rb | 267 ++ lib/rubygems/vendor/uri/lib/uri/generic.rb | 1588 +++++++++ lib/rubygems/vendor/uri/lib/uri/http.rb | 125 + lib/rubygems/vendor/uri/lib/uri/https.rb | 23 + lib/rubygems/vendor/uri/lib/uri/ldap.rb | 261 ++ lib/rubygems/vendor/uri/lib/uri/ldaps.rb | 22 + lib/rubygems/vendor/uri/lib/uri/mailto.rb | 293 ++ lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb | 539 ++++ lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb | 183 ++ lib/rubygems/vendor/uri/lib/uri/version.rb | 6 + lib/rubygems/vendor/uri/lib/uri/ws.rb | 83 + lib/rubygems/vendor/uri/lib/uri/wss.rb | 23 + lib/rubygems/vendored_molinillo.rb | 3 + lib/rubygems/vendored_net_http.rb | 5 + lib/rubygems/vendored_optparse.rb | 3 + lib/rubygems/vendored_timeout.rb | 5 + lib/rubygems/vendored_tsort.rb | 3 + lib/rubygems/yaml_serializer.rb | 12 + 239 files changed, 21370 insertions(+), 16354 deletions(-) create mode 100644 lib/bundler/plugin/installer/path.rb create mode 100644 lib/rubygems/commands/rebuild_command.rb create mode 100644 lib/rubygems/gemspec_helpers.rb delete mode 100644 lib/rubygems/net-http/.document delete mode 100644 lib/rubygems/net-http/lib/net/http.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/backward.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/exceptions.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/generic_request.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/header.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/proxy_delta.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/request.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/requests.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/response.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/responses.rb delete mode 100644 lib/rubygems/net-http/lib/net/http/status.rb delete mode 100644 lib/rubygems/net-http/lib/net/https.rb delete mode 100644 lib/rubygems/net-protocol/.document delete mode 100644 lib/rubygems/net-protocol/lib/net/protocol.rb delete mode 100644 lib/rubygems/net/http.rb delete mode 100644 lib/rubygems/optparse.rb delete mode 100644 lib/rubygems/optparse/.document delete mode 100644 lib/rubygems/optparse/lib/optionparser.rb delete mode 100644 lib/rubygems/optparse/lib/optparse.rb delete mode 100644 lib/rubygems/optparse/lib/optparse/ac.rb delete mode 100644 lib/rubygems/optparse/lib/optparse/date.rb delete mode 100644 lib/rubygems/optparse/lib/optparse/kwargs.rb delete mode 100644 lib/rubygems/optparse/lib/optparse/shellwords.rb delete mode 100644 lib/rubygems/optparse/lib/optparse/time.rb delete mode 100644 lib/rubygems/optparse/lib/optparse/uri.rb delete mode 100644 lib/rubygems/optparse/lib/optparse/version.rb delete mode 100644 lib/rubygems/resolv/.document delete mode 100644 lib/rubygems/resolv/lib/resolv.rb delete mode 100644 lib/rubygems/resolver/molinillo.rb delete mode 100644 lib/rubygems/resolver/molinillo/.document delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb delete mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/state.rb delete mode 100644 lib/rubygems/timeout.rb delete mode 100644 lib/rubygems/timeout/.document delete mode 100644 lib/rubygems/timeout/lib/timeout.rb delete mode 100644 lib/rubygems/tsort.rb delete mode 100644 lib/rubygems/tsort/.document delete mode 100644 lib/rubygems/tsort/lib/tsort.rb create mode 100644 lib/rubygems/vendor/molinillo/.document create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/errors.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/gem_metadata.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/modules/ui.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/resolution.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/resolver.rb create mode 100644 lib/rubygems/vendor/molinillo/lib/molinillo/state.rb create mode 100644 lib/rubygems/vendor/net-http/.document create mode 100644 lib/rubygems/vendor/net-http/lib/net/http.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/backward.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/header.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/proxy_delta.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/request.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/requests.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/response.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/responses.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/http/status.rb create mode 100644 lib/rubygems/vendor/net-http/lib/net/https.rb create mode 100644 lib/rubygems/vendor/net-protocol/.document create mode 100644 lib/rubygems/vendor/net-protocol/lib/net/protocol.rb create mode 100644 lib/rubygems/vendor/optparse/.document create mode 100644 lib/rubygems/vendor/optparse/lib/optionparser.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse/ac.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse/date.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse/shellwords.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse/time.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse/uri.rb create mode 100644 lib/rubygems/vendor/optparse/lib/optparse/version.rb create mode 100644 lib/rubygems/vendor/resolv/.document create mode 100644 lib/rubygems/vendor/resolv/lib/resolv.rb create mode 100644 lib/rubygems/vendor/timeout/.document create mode 100644 lib/rubygems/vendor/timeout/lib/timeout.rb create mode 100644 lib/rubygems/vendor/tsort/.document create mode 100644 lib/rubygems/vendor/tsort/lib/tsort.rb create mode 100644 lib/rubygems/vendor/uri/.document create mode 100644 lib/rubygems/vendor/uri/lib/uri.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/common.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/file.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/ftp.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/generic.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/http.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/https.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/ldap.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/ldaps.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/mailto.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/version.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/ws.rb create mode 100644 lib/rubygems/vendor/uri/lib/uri/wss.rb create mode 100644 lib/rubygems/vendored_molinillo.rb create mode 100644 lib/rubygems/vendored_net_http.rb create mode 100644 lib/rubygems/vendored_optparse.rb create mode 100644 lib/rubygems/vendored_timeout.rb create mode 100644 lib/rubygems/vendored_tsort.rb (limited to 'lib') diff --git a/lib/bundler.rb b/lib/bundler.rb index 7bb6690e52..59a1107bb7 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -200,12 +200,13 @@ module Bundler # # @param unlock [Hash, Boolean, nil] Gems that have been requested # to be updated or true if all gems should be updated + # @param lockfile [Pathname] Path to Gemfile.lock # @return [Bundler::Definition] - def definition(unlock = nil) + def definition(unlock = nil, lockfile = default_lockfile) @definition = nil if unlock @definition ||= begin configure - Definition.build(default_gemfile, default_lockfile, unlock) + Definition.build(default_gemfile, lockfile, unlock) end end diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index d93feb7b55..3640536762 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -620,7 +620,7 @@ module Bundler method_option "major", type: :boolean, banner: "If updating, prefer updating to next major version (default)" method_option "pre", type: :boolean, banner: "If updating, always choose the highest allowed version, regardless of prerelease status" method_option "strict", type: :boolean, banner: "If updating, do not allow any gem to be updated past latest --patch | --minor | --major" - method_option "conservative", type: :boolean, banner: "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated" + method_option "conservative", type: :boolean, banner: "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated" method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" def lock require_relative "cli/lock" @@ -785,7 +785,7 @@ module Bundler return unless SharedHelpers.md5_available? latest = Fetcher::CompactIndex. - new(nil, Source::Rubygems::Remote.new(Bundler::URI("https://rubygems.org")), nil, nil). + new(nil, Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org")), nil, nil). send(:compact_index_client). instance_variable_get(:@cache). dependencies("bundler"). diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb index ad41ebf4b4..8ce138df96 100644 --- a/lib/bundler/cli/binstubs.rb +++ b/lib/bundler/cli/binstubs.rb @@ -45,7 +45,7 @@ module Bundler next end - Bundler.settings.temporary(path: (Bundler.settings[:path] || Bundler.root)) do + Bundler.settings.temporary(path: Bundler.settings[:path] || Bundler.root) do installer.generate_standalone_bundler_executable_stubs(spec, installer_opts) end else diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 98192d952e..f0bb3aab18 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -437,7 +437,7 @@ module Bundler end def required_ruby_version - "2.6.0" + "3.0.0" end def rubocop_version diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index 7247121df5..dac3d2a09a 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -33,8 +33,11 @@ module Bundler update = { bundler: bundler } end + file = options[:lockfile] + file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile + Bundler.settings.temporary(frozen: false) do - definition = Bundler.definition(update) + definition = Bundler.definition(update, file) Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update] @@ -60,10 +63,8 @@ module Bundler if print puts definition.to_lock else - file = options[:lockfile] - file = file ? File.expand_path(file) : Bundler.default_lockfile puts "Writing lockfile to #{file}" - definition.lock(file) + definition.lock end end diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb index 1a33b5fc27..fd61ef0d95 100644 --- a/lib/bundler/cli/plugin.rb +++ b/lib/bundler/cli/plugin.rb @@ -5,14 +5,15 @@ module Bundler class CLI::Plugin < Thor desc "install PLUGINS", "Install the plugin from the source" long_desc <<-D - Install plugins either from the rubygems source provided (with --source option) or from a git source provided with --git (for remote repos) or --local_git (for local repos). If no sources are provided, it uses Gem.sources + Install plugins either from the rubygems source provided (with --source option), from a git source provided with --git, or a local path provided with --path. If no sources are provided, it uses Gem.sources D method_option "source", type: :string, default: nil, banner: "URL of the RubyGems source to fetch the plugin from" method_option "version", type: :string, default: nil, banner: "The version of the plugin to fetch" method_option "git", type: :string, default: nil, banner: "URL of the git repo to fetch from" - method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from" + method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)" method_option "branch", type: :string, default: nil, banner: "The git branch to checkout" method_option "ref", type: :string, default: nil, banner: "The git revision to check out" + method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use" def install(*plugins) Bundler::Plugin.install(plugins, options) end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 0b0e63f77e..c8faf77b3b 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -320,38 +320,26 @@ module Bundler dependencies.map(&:groups).flatten.uniq end - def lock(file, preserve_unknown_sections = false) - return if Definition.no_lock - - contents = to_lock - - # Convert to \r\n if the existing lock has them - # i.e., Windows with `git config core.autocrlf=true` - contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") - - if @locked_bundler_version - locked_major = @locked_bundler_version.segments.first - current_major = bundler_version_to_lock.segments.first - - updating_major = locked_major < current_major - end + def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or_unused = false) + if [true, false, nil].include?(file_or_preserve_unknown_sections) + target_lockfile = lockfile || Bundler.default_lockfile + preserve_unknown_sections = file_or_preserve_unknown_sections + else + target_lockfile = file_or_preserve_unknown_sections + preserve_unknown_sections = preserve_unknown_sections_or_unused - preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + suggestion = if target_lockfile == lockfile + "To fix this warning, remove it from the `Definition#lock` call." + else + "Instead, instantiate a new definition passing `#{target_lockfile}`, and call `lock` without a file argument on that definition" + end - if file && File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) - return if Bundler.frozen_bundle? - SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } - return - end + msg = "`Definition#lock` was passed a target file argument. #{suggestion}" - if Bundler.frozen_bundle? - Bundler.ui.error "Cannot write a changed lockfile while frozen." - return + Bundler::SharedHelpers.major_deprecation 2, msg end - SharedHelpers.filesystem_access(file) do |p| - File.open(p, "wb") {|f| f.puts(contents) } - end + write_lock(target_lockfile, preserve_unknown_sections) end def locked_ruby_version @@ -518,7 +506,45 @@ module Bundler end def lockfile_exists? - lockfile && File.exist?(lockfile) + file_exists?(lockfile) + end + + def file_exists?(file) + file && File.exist?(file) + end + + def write_lock(file, preserve_unknown_sections) + return if Definition.no_lock + + contents = to_lock + + # Convert to \r\n if the existing lock has them + # i.e., Windows with `git config core.autocrlf=true` + contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") + + if @locked_bundler_version + locked_major = @locked_bundler_version.segments.first + current_major = bundler_version_to_lock.segments.first + + updating_major = locked_major < current_major + end + + preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + + if file_exists?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) + return if Bundler.frozen_bundle? + SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } + return + end + + if Bundler.frozen_bundle? + Bundler.ui.error "Cannot write a changed lockfile while frozen." + return + end + + SharedHelpers.filesystem_access(file) do |p| + File.open(p, "wb") {|f| f.puts(contents) } + end end def resolver diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 1460b9f52f..6af80fb31f 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -19,6 +19,7 @@ module Bundler platform platforms type source install_if gemfile force_ruby_platform].freeze GITHUB_PULL_REQUEST_URL = %r{\Ahttps://github\.com/([A-Za-z0-9_\-\.]+/[A-Za-z0-9_\-\.]+)/pull/(\d+)\z} + GITLAB_MERGE_REQUEST_URL = %r{\Ahttps://gitlab\.com/([A-Za-z0-9_\-\./]+)/-/merge_requests/(\d+)\z} attr_reader :gemspecs, :gemfile attr_accessor :dependencies @@ -46,7 +47,7 @@ module Bundler @gemfile = expanded_gemfile_path @gemfiles << expanded_gemfile_path contents ||= Bundler.read_file(@gemfile.to_s) - instance_eval(contents, gemfile.to_s, 1) + instance_eval(contents, @gemfile.to_s, 1) rescue Exception => e # rubocop:disable Lint/RescueException message = "There was an error " \ "#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \ @@ -308,6 +309,20 @@ module Bundler repo_name ||= user_name "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git" end + + git_source(:gitlab) do |repo_name| + if repo_name =~ GITLAB_MERGE_REQUEST_URL + { + "git" => "https://gitlab.com/#{$1}.git", + "branch" => nil, + "ref" => "refs/merge-requests/#{$2}/head", + "tag" => nil, + } + else + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://gitlab.com/#{repo_name}.git" + end + end end def with_source(source) diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb index 57013f5d50..c4c1b53fa4 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -58,9 +58,9 @@ module Bundler env = @original.clone @keys.each do |key| value = env[key] - if !value.nil? && !value.empty? + if !value.nil? env[@prefix + key] ||= value - elsif value.nil? + else env[@prefix + key] ||= INTENTIONALLY_NIL end end @@ -72,7 +72,7 @@ module Bundler env = @original.clone @keys.each do |key| value_original = env[@prefix + key] - next if value_original.nil? || value_original.empty? + next if value_original.nil? if value_original == INTENTIONALLY_NIL env.delete(key) else diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 42bd2c0984..6288b22dcd 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -111,7 +111,7 @@ module Bundler spec -= [nil, "ruby", ""] spec_file_name = "#{spec.join "-"}.gemspec" - uri = Bundler::URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz") + uri = Gem::URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz") spec = if uri.scheme == "file" path = Gem::Util.correct_for_windows_path(uri.path) Bundler.safe_load_marshal Bundler.rubygems.inflate(Gem.read_binary(path)) @@ -255,7 +255,7 @@ module Bundler con = Gem::Net::HTTP::Persistent.new name: "bundler", proxy: :ENV if gem_proxy = Gem.configuration[:http_proxy] - con.proxy = Bundler::URI.parse(gem_proxy) if gem_proxy != :no_proxy + con.proxy = Gem::URI.parse(gem_proxy) if gem_proxy != :no_proxy end if remote_uri.scheme == "https" diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index b5282a322e..868b39b959 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -23,7 +23,7 @@ module Bundler when Gem::Net::HTTPSuccess, Gem::Net::HTTPNotModified response when Gem::Net::HTTPRedirection - new_uri = Bundler::URI.parse(response["location"]) + new_uri = Gem::URI.parse(response["location"]) if new_uri.host == uri.host new_uri.user = uri.user new_uri.password = uri.password diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb index c7eacd1930..ecc65b4956 100644 --- a/lib/bundler/gem_version_promoter.rb +++ b/lib/bundler/gem_version_promoter.rb @@ -45,17 +45,37 @@ module Bundler # Given a Resolver::Package and an Array of Specifications of available # versions for a gem, this method will return the Array of Specifications - # sorted (and possibly truncated if strict is true) in an order to give - # preference to the current level (:major, :minor or :patch) when resolution - # is deciding what versions best resolve all dependencies in the bundle. + # sorted in an order to give preference to the current level (:major, :minor + # or :patch) when resolution is deciding what versions best resolve all + # dependencies in the bundle. # @param package [Resolver::Package] The package being resolved. # @param specs [Specification] An array of Specifications for the package. - # @return [Specification] A new instance of the Specification Array sorted and - # possibly filtered. + # @return [Specification] A new instance of the Specification Array sorted. def sort_versions(package, specs) - specs = filter_dep_specs(specs, package) if strict + locked_version = package.locked_version - sort_dep_specs(specs, package) + result = specs.sort do |a, b| + unless package.prerelease_specified? || pre? + a_pre = a.prerelease? + b_pre = b.prerelease? + + next 1 if a_pre && !b_pre + next -1 if b_pre && !a_pre + end + + if major? || locked_version.nil? + b <=> a + elsif either_version_older_than_locked?(a, b, locked_version) + b <=> a + elsif segments_do_not_match?(a, b, :major) + a <=> b + elsif !minor? && segments_do_not_match?(a, b, :minor) + a <=> b + else + b <=> a + end + end + post_sort(result, package.unlock?, locked_version) end # @return [bool] Convenience method for testing value of level variable. @@ -73,9 +93,18 @@ module Bundler pre == true end - private + # Given a Resolver::Package and an Array of Specifications of available + # versions for a gem, this method will truncate the Array if strict + # is true. That means filtering out downgrades from the version currently + # locked, and filtering out upgrades that go past the selected level (major, + # minor, or patch). + # @param package [Resolver::Package] The package being resolved. + # @param specs [Specification] An array of Specifications for the package. + # @return [Specification] A new instance of the Specification Array + # truncated. + def filter_versions(package, specs) + return specs unless strict - def filter_dep_specs(specs, package) locked_version = package.locked_version return specs if locked_version.nil? || major? @@ -89,32 +118,7 @@ module Bundler end end - def sort_dep_specs(specs, package) - locked_version = package.locked_version - - result = specs.sort do |a, b| - unless package.prerelease_specified? || pre? - a_pre = a.prerelease? - b_pre = b.prerelease? - - next -1 if a_pre && !b_pre - next 1 if b_pre && !a_pre - end - - if major? || locked_version.nil? - a <=> b - elsif either_version_older_than_locked?(a, b, locked_version) - a <=> b - elsif segments_do_not_match?(a, b, :major) - b <=> a - elsif !minor? && segments_do_not_match?(a, b, :minor) - b <=> a - else - a <=> b - end - end - post_sort(result, package.unlock?, locked_version) - end + private def either_version_older_than_locked?(a, b, locked_version) a.version < locked_version || b.version < locked_version @@ -133,13 +137,13 @@ module Bundler if unlock || locked_version.nil? result else - move_version_to_end(result, locked_version) + move_version_to_beginning(result, locked_version) end end - def move_version_to_end(result, version) + def move_version_to_beginning(result, version) move, keep = result.partition {|s| s.version.to_s == version.to_s } - keep.concat(move) + move.concat(keep) end end end diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index 2cf7754ecb..cf561c2ee4 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -50,7 +50,7 @@ module Bundler append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any? # since we resolved successfully, write out the lockfile - @definition.lock(Bundler.default_lockfile) + @definition.lock # invalidate the cached Bundler.definition Bundler.reset_paths! diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index e837f732cf..018324f840 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -260,8 +260,8 @@ module Bundler true end - def lock(opts = {}) - @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections]) + def lock + @definition.lock end end end diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index 210911dcf4..a6cbc88f34 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-ADD" "1" "December 2023" "" +.TH "BUNDLE\-ADD" "1" "March 2024" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index b71347d6e1..2b35bc956a 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-BINSTUBS" "1" "December 2023" "" +.TH "BUNDLE\-BINSTUBS" "1" "March 2024" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 5f03c38abe..3b86b995a6 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CACHE" "1" "December 2023" "" +.TH "BUNDLE\-CACHE" "1" "March 2024" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index bc24dfe2b2..7f18e26537 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CHECK" "1" "December 2023" "" +.TH "BUNDLE\-CHECK" "1" "March 2024" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 00787da5f1..0180eb38a2 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CLEAN" "1" "December 2023" "" +.TH "BUNDLE\-CLEAN" "1" "March 2024" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index c5a976da46..b768f1e3d2 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONFIG" "1" "December 2023" "" +.TH "BUNDLE\-CONFIG" "1" "March 2024" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 14e5f55647..1368a50eb1 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONSOLE" "1" "December 2023" "" +.TH "BUNDLE\-CONSOLE" "1" "March 2024" "" .SH "NAME" \fBbundle\-console\fR \- Deprecated way to open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index aea8bacdad..80eaf2a888 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-DOCTOR" "1" "December 2023" "" +.TH "BUNDLE\-DOCTOR" "1" "March 2024" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index af622f4423..191863c045 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-EXEC" "1" "December 2023" "" +.TH "BUNDLE\-EXEC" "1" "March 2024" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index bc62a883ea..464d8d1126 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-GEM" "1" "December 2023" "" +.TH "BUNDLE\-GEM" "1" "March 2024" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index 3e6d7a851e..3604ad6127 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-HELP" "1" "December 2023" "" +.TH "BUNDLE\-HELP" "1" "March 2024" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 43cc7d23b6..647f5987be 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INFO" "1" "December 2023" "" +.TH "BUNDLE\-INFO" "1" "March 2024" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 9c6f89a6b2..2c41a3c7de 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INIT" "1" "December 2023" "" +.TH "BUNDLE\-INIT" "1" "March 2024" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 index f8c4d4342e..c7269db34d 100644 --- a/lib/bundler/man/bundle-inject.1 +++ b/lib/bundler/man/bundle-inject.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INJECT" "1" "December 2023" "" +.TH "BUNDLE\-INJECT" "1" "March 2024" "" .SH "NAME" \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index a23763889b..3fa1a467e2 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INSTALL" "1" "December 2023" "" +.TH "BUNDLE\-INSTALL" "1" "March 2024" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" @@ -208,8 +208,8 @@ To explicitly update \fBactionpack\fR, including its dependencies which other ge \fBSummary\fR: In general, after making a change to the Gemfile(5) , you should first try to run \fBbundle install\fR, which will guarantee that no other gem in the Gemfile(5) is impacted by the change\. If that does not work, run bundle update(1) \fIbundle\-update\.1\.html\fR\. .SH "SEE ALSO" .IP "\(bu" 4 -Gem install docs \fIhttp://guides\.rubygems\.org/rubygems\-basics/#installing\-gems\fR +Gem install docs \fIhttps://guides\.rubygems\.org/rubygems\-basics/#installing\-gems\fR .IP "\(bu" 4 -Rubygems signing docs \fIhttp://guides\.rubygems\.org/security/\fR +Rubygems signing docs \fIhttps://guides\.rubygems\.org/security/\fR .IP "" 0 diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn index ac0014e24e..ed8169de05 100644 --- a/lib/bundler/man/bundle-install.1.ronn +++ b/lib/bundler/man/bundle-install.1.ronn @@ -379,5 +379,5 @@ does not work, run [bundle update(1)](bundle-update.1.html). ## SEE ALSO -* [Gem install docs](http://guides.rubygems.org/rubygems-basics/#installing-gems) -* [Rubygems signing docs](http://guides.rubygems.org/security/) +* [Gem install docs](https://guides.rubygems.org/rubygems-basics/#installing-gems) +* [Rubygems signing docs](https://guides.rubygems.org/security/) diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index 943f17ab83..f91fd95739 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LIST" "1" "December 2023" "" +.TH "BUNDLE\-LIST" "1" "March 2024" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index 041c2d739e..f992f5ee5f 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LOCK" "1" "December 2023" "" +.TH "BUNDLE\-LOCK" "1" "March 2024" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index a349288987..53d3541555 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OPEN" "1" "December 2023" "" +.TH "BUNDLE\-OPEN" "1" "March 2024" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index a501fec83e..f79eff5ae9 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OUTDATED" "1" "December 2023" "" +.TH "BUNDLE\-OUTDATED" "1" "March 2024" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index 2ff1938585..d2133ec4d3 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLATFORM" "1" "December 2023" "" +.TH "BUNDLE\-PLATFORM" "1" "March 2024" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index 09e0d816ac..cbdfac11b6 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,10 +1,10 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLUGIN" "1" "December 2023" "" +.TH "BUNDLE\-PLUGIN" "1" "March 2024" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" -\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git|\-\-local_git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] +\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] [\-\-path=\fIpath\fR] .br \fBbundle plugin\fR uninstall PLUGINS .br @@ -27,7 +27,7 @@ Install bundler\-graph gem from example\.com\. The global source, specified in s You can specify the version of the gem via \fB\-\-version\fR\. .TP \fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR -Install bundler\-graph gem from Git repository\. \fB\-\-git\fR can be replaced with \fB\-\-local\-git\fR\. You cannot use both \fB\-\-git\fR and \fB\-\-local\-git\fR\. You can use standard Git URLs like: +Install bundler\-graph gem from Git repository\. You can use standard Git URLs like: .IP \fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR .br @@ -37,7 +37,10 @@ Install bundler\-graph gem from Git repository\. \fB\-\-git\fR can be replaced w .br \fBfile:///path/to/repo\fR .IP -When you specify \fB\-\-git\fR/\fB\-\-local\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. When you specify both, only the latter is used\. +When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. +.TP +\fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR +Install bundler\-graph gem from a local path\. .SS "uninstall" Uninstall the plugin(s) specified in PLUGINS\. .SS "list" diff --git a/lib/bundler/man/bundle-plugin.1.ronn b/lib/bundler/man/bundle-plugin.1.ronn index a11df4c162..b0a34660ea 100644 --- a/lib/bundler/man/bundle-plugin.1.ronn +++ b/lib/bundler/man/bundle-plugin.1.ronn @@ -4,7 +4,8 @@ bundle-plugin(1) -- Manage Bundler plugins ## SYNOPSIS `bundle plugin` install PLUGINS [--source=] [--version=] - [--git|--local_git=] [--branch=|--ref=]
+ [--git=] [--branch=|--ref=] + [--path=]
`bundle plugin` uninstall PLUGINS
`bundle plugin` list
`bundle plugin` help [COMMAND] @@ -29,14 +30,17 @@ Install the given plugin(s). You can specify the version of the gem via `--version`. * `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph`: - Install bundler-graph gem from Git repository. `--git` can be replaced with `--local-git`. You cannot use both `--git` and `--local-git`. You can use standard Git URLs like: + Install bundler-graph gem from Git repository. You can use standard Git URLs like: `ssh://[user@]host.xz[:port]/path/to/repo.git`
`http[s]://host.xz[:port]/path/to/repo.git`
`/path/to/repo`
`file:///path/to/repo` - When you specify `--git`/`--local-git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. When you specify both, only the latter is used. + When you specify `--git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. + +* `bundle plugin install bundler-graph --path ../bundler-graph`: + Install bundler-graph gem from a local path. ### uninstall diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 60609f4ee8..faa04d7676 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PRISTINE" "1" "December 2023" "" +.TH "BUNDLE\-PRISTINE" "1" "March 2024" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 31eae269c8..3f8cbbd9b6 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-REMOVE" "1" "December 2023" "" +.TH "BUNDLE\-REMOVE" "1" "March 2024" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 761629c625..bc72c6e3b6 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-SHOW" "1" "December 2023" "" +.TH "BUNDLE\-SHOW" "1" "March 2024" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 838261df0d..d1284c2e72 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-UPDATE" "1" "December 2023" "" +.TH "BUNDLE\-UPDATE" "1" "March 2024" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index d9b0e7c3b1..05905e1347 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VERSION" "1" "December 2023" "" +.TH "BUNDLE\-VERSION" "1" "March 2024" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 index 0e7981ac9a..681563cd4c 100644 --- a/lib/bundler/man/bundle-viz.1 +++ b/lib/bundler/man/bundle-viz.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VIZ" "1" "December 2023" "" +.TH "BUNDLE\-VIZ" "1" "March 2024" "" .SH "NAME" \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 2417348be4..1d2c780060 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE" "1" "December 2023" "" +.TH "BUNDLE" "1" "March 2024" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 9f73687c8c..39503f22a6 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "GEMFILE" "5" "December 2023" "" +.TH "GEMFILE" "5" "March 2024" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" @@ -72,7 +72,7 @@ A Ruby engine is an implementation of the Ruby language\. .IP "\(bu" 4 For background: the reference or original implementation of the Ruby programming language is called Matz's Ruby Interpreter \fIhttps://en\.wikipedia\.org/wiki/Ruby_MRI\fR, or MRI for short\. This is named after Ruby creator Yukihiro Matsumoto, also known as Matz\. MRI is also known as CRuby, because it is written in C\. MRI is the most widely used Ruby engine\. .IP "\(bu" 4 -Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include JRuby \fIhttp://jruby\.org/\fR and TruffleRuby \fIhttps://www\.graalvm\.org/ruby/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM\. +Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include JRuby \fIhttps://www\.jruby\.org/\fR and TruffleRuby \fIhttps://www\.graalvm\.org/ruby/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM\. .IP "" 0 .SS "ENGINE VERSION" Each application \fImay\fR specify a Ruby engine version\. If an engine version is specified, an engine \fImust\fR also be specified\. If the engine is "ruby" the engine version specified \fImust\fR match the Ruby version\. @@ -449,7 +449,7 @@ end .fi .IP "" 0 .SH "GEMSPEC" -The \fB\.gemspec\fR \fIhttp://guides\.rubygems\.org/specification\-reference/\fR file is where you provide metadata about your gem to Rubygems\. Some required Gemspec attributes include the name, description, and homepage of your gem\. This is also where you specify the dependencies your gem needs to run\. +The \fB\.gemspec\fR \fIhttps://guides\.rubygems\.org/specification\-reference/\fR file is where you provide metadata about your gem to Rubygems\. Some required Gemspec attributes include the name, description, and homepage of your gem\. This is also where you specify the dependencies your gem needs to run\. .P If you wish to use Bundler to help install dependencies for a gem while it is being developed, use the \fBgemspec\fR method to pull in the dependencies listed in the \fB\.gemspec\fR file\. .P diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index e8a1f8b79e..7c1e00d13a 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -96,7 +96,7 @@ What exactly is an Engine? - [Other implementations](https://www.ruby-lang.org/en/about/) of Ruby exist. Some of the more well-known implementations include - [JRuby](http://jruby.org/) and [TruffleRuby](https://www.graalvm.org/ruby/). + [JRuby](https://www.jruby.org/) and [TruffleRuby](https://www.graalvm.org/ruby/). Rubinius is an alternative implementation of Ruby written in Ruby. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine. TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM. @@ -509,7 +509,7 @@ software is installed or some other conditions are met. ## GEMSPEC -The [`.gemspec`](http://guides.rubygems.org/specification-reference/) file is where +The [`.gemspec`](https://guides.rubygems.org/specification-reference/) file is where you provide metadata about your gem to Rubygems. Some required Gemspec attributes include the name, description, and homepage of your gem. This is also where you specify the dependencies your gem needs to run. diff --git a/lib/bundler/mirror.rb b/lib/bundler/mirror.rb index 9d437a0951..494a6d6aef 100644 --- a/lib/bundler/mirror.rb +++ b/lib/bundler/mirror.rb @@ -47,7 +47,7 @@ module Bundler def fetch_valid_mirror_for(uri) downcased = uri.to_s.downcase - mirror = @mirrors[downcased] || @mirrors[Bundler::URI(downcased).host] || Mirror.new(uri) + mirror = @mirrors[downcased] || @mirrors[Gem::URI(downcased).host] || Mirror.new(uri) mirror.validate!(@prober) mirror = Mirror.new(uri) unless mirror.valid? mirror @@ -74,7 +74,7 @@ module Bundler @uri = if uri.nil? nil else - Bundler::URI(uri.to_s) + Gem::URI(uri.to_s) end @valid = nil end @@ -126,7 +126,7 @@ module Bundler if uri == "all" @all = true else - @uri = Bundler::URI(uri).absolute? ? Settings.normalize_uri(uri) : uri + @uri = Gem::URI(uri).absolute? ? Settings.normalize_uri(uri) : uri end @value = value end diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb index f051f938c2..8563ee358a 100644 --- a/lib/bundler/plugin/api/source.rb +++ b/lib/bundler/plugin/api/source.rb @@ -107,7 +107,7 @@ module Bundler def install_path @install_path ||= begin - base_name = File.basename(Bundler::URI.parse(uri).normalize.path) + base_name = File.basename(Gem::URI.parse(uri).normalize.path) gem_install_dir.join("#{base_name}-#{uri_hash[0..11]}") end @@ -176,7 +176,7 @@ module Bundler # # This is used by `app_cache_path` def app_cache_dirname - base_name = File.basename(Bundler::URI.parse(uri).normalize.path) + base_name = File.basename(Gem::URI.parse(uri).normalize.path) "#{base_name}-#{uri_hash}" end diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 256dcf526c..4f60862bb4 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -10,6 +10,7 @@ module Bundler class Installer autoload :Rubygems, File.expand_path("installer/rubygems", __dir__) autoload :Git, File.expand_path("installer/git", __dir__) + autoload :Path, File.expand_path("installer/path", __dir__) def install(names, options) check_sources_consistency!(options) @@ -18,8 +19,8 @@ module Bundler if options[:git] install_git(names, version, options) - elsif options[:local_git] - install_local_git(names, version, options) + elsif options[:path] + install_path(names, version, options[:path]) else sources = options[:source] || Gem.sources install_rubygems(names, version, sources) @@ -45,20 +46,40 @@ module Bundler if options.key?(:git) && options.key?(:local_git) raise InvalidOption, "Remote and local plugin git sources can't be both specified" end + + # back-compat; local_git is an alias for git + if options.key?(:local_git) + Bundler::SharedHelpers.major_deprecation(2, "--local_git is deprecated, use --git") + options[:git] = options.delete(:local_git) + end + + if (options.keys & [:source, :git, :path]).length > 1 + raise InvalidOption, "Only one of --source, --git, or --path may be specified" + end + + if (options.key?(:branch) || options.key?(:ref)) && !options.key?(:git) + raise InvalidOption, "--#{options.key?(:branch) ? "branch" : "ref"} can only be used with git sources" + end + + if options.key?(:branch) && options.key?(:ref) + raise InvalidOption, "--branch and --ref can't be both specified" + end end def install_git(names, version, options) - uri = options.delete(:git) - options["uri"] = uri + source_list = SourceList.new + source = source_list.add_git_source({ "uri" => options[:git], + "branch" => options[:branch], + "ref" => options[:ref] }) - install_all_sources(names, version, options, options[:source]) + install_all_sources(names, version, source_list, source) end - def install_local_git(names, version, options) - uri = options.delete(:local_git) - options["uri"] = uri + def install_path(names, version, path) + source_list = SourceList.new + source = source_list.add_path_source({ "path" => path, "root_path" => SharedHelpers.pwd }) - install_all_sources(names, version, options, options[:source]) + install_all_sources(names, version, source_list, source) end # Installs the plugin from rubygems source and returns the path where the @@ -70,16 +91,15 @@ module Bundler # # @return [Hash] map of names to the specs of plugins installed def install_rubygems(names, version, sources) - install_all_sources(names, version, nil, sources) - end - - def install_all_sources(names, version, git_source_options, rubygems_source) source_list = SourceList.new - source_list.add_git_source(git_source_options) if git_source_options - Array(rubygems_source).each {|remote| source_list.add_global_rubygems_remote(remote) } if rubygems_source + Array(sources).each {|remote| source_list.add_global_rubygems_remote(remote) } + + install_all_sources(names, version, source_list) + end - deps = names.map {|name| Dependency.new name, version } + def install_all_sources(names, version, source_list, source = nil) + deps = names.map {|name| Dependency.new(name, version, { "source" => source }) } Bundler.configure_gem_home_and_path(Plugin.root) diff --git a/lib/bundler/plugin/installer/path.rb b/lib/bundler/plugin/installer/path.rb new file mode 100644 index 0000000000..58a8fa7426 --- /dev/null +++ b/lib/bundler/plugin/installer/path.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Bundler + module Plugin + class Installer + class Path < Bundler::Source::Path + def root + SharedHelpers.in_bundle? ? Bundler.root : Plugin.root + end + + def generate_bin(spec, disable_extensions = false) + # Need to find a way without code duplication + # For now, we can ignore this + end + end + end + end +end diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb index 547661cf2f..746996de55 100644 --- a/lib/bundler/plugin/source_list.rb +++ b/lib/bundler/plugin/source_list.rb @@ -9,6 +9,10 @@ module Bundler add_source_to_list Plugin::Installer::Git.new(options), git_sources end + def add_path_source(options = {}) + add_source_to_list Plugin::Installer::Path.new(options), path_sources + end + def add_rubygems_source(options = {}) add_source_to_list Plugin::Installer::Rubygems.new(options), @rubygems_sources end @@ -17,10 +21,6 @@ module Bundler path_sources + git_sources + rubygems_sources + [metadata_source] end - def default_source - git_sources.first || global_rubygems_source - end - private def rubygems_aggregate_class diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index f60069f421..1a6711ea6f 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -50,26 +50,26 @@ module Bundler specs[name] = matches.sort_by {|s| [s.version, s.platform.to_s] } end + @all_versions = Hash.new do |candidates, package| + candidates[package] = all_versions_for(package) + end + @sorted_versions = Hash.new do |candidates, package| - candidates[package] = if package.root? - [root_version] - else - all_versions_for(package).sort - end + candidates[package] = filtered_versions_for(package).sort end + @sorted_versions[root] = [root_version] + root_dependencies = prepare_dependencies(@requirements, @packages) @cached_dependencies = Hash.new do |dependencies, package| - dependencies[package] = if package.root? - { root_version => root_dependencies } - else - Hash.new do |versions, version| - versions[version] = to_dependency_hash(version.dependencies.reject {|d| d.name == package.name }, @packages) - end + dependencies[package] = Hash.new do |versions, version| + versions[version] = to_dependency_hash(version.dependencies.reject {|d| d.name == package.name }, @packages) end end + @cached_dependencies[root] = { root_version => root_dependencies } + logger = Bundler::UI::Shell.new logger.level = debug? ? "debug" : "warn" @@ -156,9 +156,15 @@ module Bundler end def versions_for(package, range=VersionRange.any) - versions = range.select_versions(@sorted_versions[package]) + versions = select_sorted_versions(package, range) - sort_versions(package, versions) + # Conditional avoids (among other things) calling + # sort_versions_by_preferred with the root package + if versions.size > 1 + sort_versions_by_preferred(package, versions) + else + versions + end end def no_versions_incompatibility_for(package, unsatisfied_term) @@ -247,7 +253,7 @@ module Bundler locked_requirement = base_requirements[name] results = filter_matching_specs(results, locked_requirement) if locked_requirement - versions = results.group_by(&:version).reduce([]) do |groups, (version, specs)| + results.group_by(&:version).reduce([]) do |groups, (version, specs)| platform_specs = package.platforms.map {|platform| select_best_platform_match(specs, platform) } # If package is a top-level dependency, @@ -274,8 +280,6 @@ module Bundler groups end - - sort_versions(package, versions) end def source_for(name) @@ -334,6 +338,21 @@ module Bundler private + def filtered_versions_for(package) + @gem_version_promoter.filter_versions(package, @all_versions[package]) + end + + def raise_all_versions_filtered_out!(package) + level = @gem_version_promoter.level + name = package.name + locked_version = package.locked_version + requirement = package.dependency + + raise GemNotFound, + "#{name} is locked to #{locked_version}, while Gemfile is requesting #{requirement}. " \ + "--strict --#{level} was specified, but there are no #{level} level upgrades from #{locked_version} satisfying #{requirement}, so version solving has failed" + end + def filter_matching_specs(specs, requirements) Array(requirements).flat_map do |requirement| specs.select {| spec| requirement_satisfied_by?(requirement, spec) } @@ -357,12 +376,8 @@ module Bundler requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec) end - def sort_versions(package, versions) - if versions.size > 1 - @gem_version_promoter.sort_versions(package, versions).reverse - else - versions - end + def sort_versions_by_preferred(package, versions) + @gem_version_promoter.sort_versions(package, versions) end def repository_for(package) @@ -379,12 +394,19 @@ module Bundler next [dep_package, dep_constraint] if name == "bundler" - versions = versions_for(dep_package, dep_constraint.range) + dep_range = dep_constraint.range + versions = select_sorted_versions(dep_package, dep_range) if versions.empty? && dep_package.ignores_prereleases? + @all_versions.delete(dep_package) @sorted_versions.delete(dep_package) dep_package.consider_prereleases! - versions = versions_for(dep_package, dep_constraint.range) + versions = select_sorted_versions(dep_package, dep_range) end + + if versions.empty? && select_all_versions(dep_package, dep_range).any? + raise_all_versions_filtered_out!(dep_package) + end + next [dep_package, dep_constraint] unless versions.empty? next unless dep_package.current_platform? @@ -393,6 +415,14 @@ module Bundler end.compact.to_h end + def select_sorted_versions(package, range) + range.select_versions(@sorted_versions[package]) + end + + def select_all_versions(package, range) + range.select_versions(@all_versions[package]) + end + def other_specs_matching_message(specs, requirement) message = String.new("The source contains the following gems matching '#{requirement}':\n") message << specs.map {|s| " * #{s.full_name}" }.join("\n") diff --git a/lib/bundler/resolver/candidate.rb b/lib/bundler/resolver/candidate.rb index e695ef08ee..9e8b913335 100644 --- a/lib/bundler/resolver/candidate.rb +++ b/lib/bundler/resolver/candidate.rb @@ -15,7 +15,7 @@ module Bundler # considered separately. # # Some candidates may also keep some information explicitly about the - # package the refer to. These candidates are referred to as "canonical" and + # package they refer to. These candidates are referred to as "canonical" and # are used when materializing resolution results back into RubyGems # specifications that can be installed, written to lock files, and so on. # diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 993f1082c3..ec772cfe7b 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -95,7 +95,7 @@ module Bundler def lock(opts = {}) return if @definition.no_resolve_needed? - @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections]) + @definition.lock(opts[:preserve_unknown_sections]) end alias_method :gems, :specs diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb index 5accda4bcb..bfd000b1a0 100644 --- a/lib/bundler/self_manager.rb +++ b/lib/bundler/self_manager.rb @@ -113,7 +113,7 @@ module Bundler end def local_specs - @local_specs ||= Bundler::Source::Rubygems.new("allow_local" => true).specs.select {|spec| spec.name == "bundler" } + @local_specs ||= Bundler::Source::Rubygems.new("allow_local" => true, "allow_cached" => true).specs.select {|spec| spec.name == "bundler" } end def remote_specs diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 8f941c448d..379abfb24a 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -189,7 +189,7 @@ module Bundler def mirror_for(uri) if uri.is_a?(String) require_relative "vendored_uri" - uri = Bundler::URI(uri) + uri = Gem::URI(uri) end gem_mirrors.for(uri.to_s).uri @@ -492,16 +492,19 @@ module Bundler valid_file = file.exist? && !file.size.zero? return {} unless valid_file serializer_class.load(file.read).inject({}) do |config, (k, v)| - if k.include?("-") - Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \ - "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ - "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)." + unless k.start_with?("#") + if k.include?("-") + Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \ + "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ + "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)." - # string hash keys are frozen - k = k.gsub("-", "___") + # string hash keys are frozen + k = k.gsub("-", "___") + end + + config[k] = v end - config[k] = v config end end @@ -549,7 +552,7 @@ module Bundler end uri = URINormalizer.normalize_suffix(uri) require_relative "vendored_uri" - uri = Bundler::URI(uri) + uri = Gem::URI(uri) unless uri.absolute? raise ArgumentError, format("Gem sources must be absolute. You provided '%s'.", uri) end @@ -564,7 +567,7 @@ module Bundler key when Symbol key.name - when Bundler::URI::HTTP + when Gem::URI::HTTP key.to_s else raise ArgumentError, "Invalid key: #{key.inspect}" @@ -577,7 +580,7 @@ module Bundler key when Symbol key.to_s - when Bundler::URI::HTTP + when Gem::URI::HTTP key.to_s else raise ArgumentError, "Invalid key: #{key.inspect}" diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index 831a13cba3..198e335bb6 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -326,7 +326,7 @@ module Bundler if %r{^\w+://(\w+@)?}.match?(uri) # Downcase the domain component of the URI # and strip off a trailing slash, if one is present - input = Bundler::URI.parse(uri).normalize.to_s.sub(%r{/$}, "") + input = Gem::URI.parse(uri).normalize.to_s.sub(%r{/$}, "") else # If there is no URI scheme, assume it is an ssh/git URI input = uri diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 8b6d420884..645851286c 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -320,7 +320,7 @@ module Bundler # Adds credentials to the URI def configured_uri if /https?:/.match?(uri) - remote = Bundler::URI(uri) + remote = Gem::URI(uri) config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host] remote.userinfo ||= config_auth remote.to_s diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index dfcedb5b16..04cfc0a850 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -17,7 +17,7 @@ module Bundler @remotes = [] @dependency_names = [] @allow_remote = false - @allow_cached = false + @allow_cached = options["allow_cached"] || false @allow_local = options["allow_local"] || false @checksum_store = Checksum::Store.new @@ -133,7 +133,7 @@ module Bundler # sources, and large_idx.merge! small_idx is way faster than # small_idx.merge! large_idx. index = @allow_remote ? remote_specs.dup : Index.new - index.merge!(cached_specs) if @allow_cached || @allow_remote + index.merge!(cached_specs) if @allow_cached index.merge!(installed_specs) if @allow_local index end @@ -349,9 +349,9 @@ module Bundler def normalize_uri(uri) uri = URINormalizer.normalize_suffix(uri.to_s) require_relative "../vendored_uri" - uri = Bundler::URI(uri) + uri = Gem::URI(uri) raise ArgumentError, "The source must be an absolute URI. For example:\n" \ - "source 'https://rubygems.org'" if !uri.absolute? || (uri.is_a?(Bundler::URI::HTTP) && uri.host.nil?) + "source 'https://rubygems.org'" if !uri.absolute? || (uri.is_a?(Gem::URI::HTTP) && uri.host.nil?) uri end diff --git a/lib/bundler/source/rubygems/remote.rb b/lib/bundler/source/rubygems/remote.rb index 82c850ffbb..9c5c06de24 100644 --- a/lib/bundler/source/rubygems/remote.rb +++ b/lib/bundler/source/rubygems/remote.rb @@ -48,7 +48,7 @@ module Bundler end uri - rescue Bundler::URI::InvalidComponentError + rescue Gem::URI::InvalidComponentError error_message = "Please CGI escape your usernames and passwords before " \ "setting them for authentication." raise HTTPError.new(error_message) diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 4419695b7f..d85e1c1c01 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -9,7 +9,7 @@ module Bundler :metadata_source def global_rubygems_source - @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true) + @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true, "allow_cached" => true) end def initialize @@ -174,7 +174,7 @@ module Bundler replacement_source = replacement_sources.find {|s| s == global_rubygems_source } return global_rubygems_source unless replacement_source - replacement_source.local! + replacement_source.cached! replacement_source end diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index cc649abaf8..96e1403bf7 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -65,7 +65,7 @@ module Bundler platforms.concat(new_platforms) - less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && platform === Bundler.local_platform } + less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform } platforms.delete(Bundler.local_platform) if less_specific_platform platforms diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index 51f19a5be9..6e88f4dab1 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -27,9 +27,10 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(__dir__) do - `git ls-files -z`.split("\x0").reject do |f| - (File.expand_path(f) == __FILE__) || + gemspec = File.basename(__FILE__) + spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| + ls.readlines("\x0", chomp: true).reject do |f| + (f == gemspec) || f.start_with?(*%w[bin/ test/ spec/ features/ .git <%= config[:ci_config_path] %>appveyor Gemfile]) end end diff --git a/lib/bundler/templates/newgem/rubocop.yml.tt b/lib/bundler/templates/newgem/rubocop.yml.tt index 9ecec78807..3d1c4ee7b2 100644 --- a/lib/bundler/templates/newgem/rubocop.yml.tt +++ b/lib/bundler/templates/newgem/rubocop.yml.tt @@ -2,12 +2,7 @@ AllCops: TargetRubyVersion: <%= ::Gem::Version.new(config[:required_ruby_version]).segments[0..1].join(".") %> Style/StringLiterals: - Enabled: true EnforcedStyle: double_quotes Style/StringLiteralsInInterpolation: - Enabled: true EnforcedStyle: double_quotes - -Layout/LineLength: - Max: 120 diff --git a/lib/bundler/uri_credentials_filter.rb b/lib/bundler/uri_credentials_filter.rb index ccfaf0bc5d..a83f5304e2 100644 --- a/lib/bundler/uri_credentials_filter.rb +++ b/lib/bundler/uri_credentials_filter.rb @@ -11,7 +11,7 @@ module Bundler return uri if File.exist?(uri) require_relative "vendored_uri" - uri = Bundler::URI(uri) + uri = Gem::URI(uri) end if uri.userinfo @@ -25,7 +25,7 @@ module Bundler end return uri.to_s if uri_to_anonymize.is_a?(String) uri - rescue Bundler::URI::InvalidURIError # uri is not canonical uri scheme + rescue Gem::URI::InvalidURIError # uri is not canonical uri scheme uri end diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb index c702bebc39..c15b346330 100644 --- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb @@ -1,5 +1,5 @@ require_relative '../../../../../vendored_net_http' -require_relative '../../../../uri/lib/uri' +require_relative '../../../../../vendored_uri' require 'cgi' # for escaping require_relative '../../../../connection_pool/lib/connection_pool' @@ -22,7 +22,7 @@ autoload :OpenSSL, 'openssl' # # require 'bundler/vendor/net-http-persistent/lib/net/http/persistent' # -# uri = Bundler::URI 'http://example.com/awesome/web/service' +# uri = Gem::URI 'http://example.com/awesome/web/service' # # http = Gem::Net::HTTP::Persistent.new # @@ -39,17 +39,17 @@ autoload :OpenSSL, 'openssl' # post = Gem::Net::HTTP::Post.new post_uri.path # post.set_form_data 'some' => 'cool data' # -# # perform the POST, the Bundler::URI is always required +# # perform the POST, the Gem::URI is always required # response http.request post_uri, post # # Note that for GET, HEAD and other requests that do not have a body you want -# to use Bundler::URI#request_uri not Bundler::URI#path. The request_uri contains the query +# to use Gem::URI#request_uri not Gem::URI#path. The request_uri contains the query # params which are sent in the body for other requests. # # == TLS/SSL # # TLS connections are automatically created depending upon the scheme of the -# Bundler::URI. TLS connections are automatically verified against the default +# Gem::URI. TLS connections are automatically verified against the default # certificate store for your computer. You can override this by changing # verify_mode or by specifying an alternate cert_store. # @@ -72,7 +72,7 @@ autoload :OpenSSL, 'openssl' # == Proxies # # A proxy can be set through #proxy= or at initialization time by providing a -# second argument to ::new. The proxy may be the Bundler::URI of the proxy server or +# second argument to ::new. The proxy may be the Gem::URI of the proxy server or # :ENV which will consult environment variables. # # See #proxy= and #proxy_from_env for details. @@ -197,7 +197,7 @@ class Gem::Net::HTTP::Persistent # NOTE: This may not work on ruby > 1.9. def self.detect_idle_timeout uri, max = 10 - uri = Bundler::URI uri unless Bundler::URI::Generic === uri + uri = Gem::URI uri unless Gem::URI::Generic === uri uri += '/' req = Gem::Net::HTTP::Head.new uri.request_uri @@ -455,13 +455,13 @@ class Gem::Net::HTTP::Persistent # Set a +name+ for fun. Your library name should be good enough, but this # otherwise has no purpose. # - # +proxy+ may be set to a Bundler::URI::HTTP or :ENV to pick up proxy options from + # +proxy+ may be set to a Gem::URI::HTTP or :ENV to pick up proxy options from # the environment. See proxy_from_env for details. # - # In order to use a Bundler::URI for the proxy you may need to do some extra work - # beyond Bundler::URI parsing if the proxy requires a password: + # In order to use a Gem::URI for the proxy you may need to do some extra work + # beyond Gem::URI parsing if the proxy requires a password: # - # proxy = Bundler::URI 'http://proxy.example' + # proxy = Gem::URI 'http://proxy.example' # proxy.user = 'AzureDiamond' # proxy.password = 'hunter2' # @@ -510,7 +510,7 @@ class Gem::Net::HTTP::Persistent @verify_mode = nil @cert_store = nil - @generation = 0 # incremented when proxy Bundler::URI changes + @generation = 0 # incremented when proxy Gem::URI changes if HAVE_OPENSSL then @verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -720,12 +720,12 @@ class Gem::Net::HTTP::Persistent alias key= private_key= ## - # Sets the proxy server. The +proxy+ may be the Bundler::URI of the proxy server, + # Sets the proxy server. The +proxy+ may be the Gem::URI of the proxy server, # the symbol +:ENV+ which will read the proxy from the environment or nil to # disable use of a proxy. See #proxy_from_env for details on setting the # proxy from the environment. # - # If the proxy Bundler::URI is set after requests have been made, the next request + # If the proxy Gem::URI is set after requests have been made, the next request # will shut-down and re-open all connections. # # The +no_proxy+ query parameter can be used to specify hosts which shouldn't @@ -736,9 +736,9 @@ class Gem::Net::HTTP::Persistent def proxy= proxy @proxy_uri = case proxy when :ENV then proxy_from_env - when Bundler::URI::HTTP then proxy + when Gem::URI::HTTP then proxy when nil then # ignore - else raise ArgumentError, 'proxy must be :ENV or a Bundler::URI::HTTP' + else raise ArgumentError, 'proxy must be :ENV or a Gem::URI::HTTP' end @no_proxy.clear @@ -763,13 +763,13 @@ class Gem::Net::HTTP::Persistent end ## - # Creates a Bundler::URI for an HTTP proxy server from ENV variables. + # Creates a Gem::URI for an HTTP proxy server from ENV variables. # # If +HTTP_PROXY+ is set a proxy will be returned. # - # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the Bundler::URI is given the + # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the Gem::URI is given the # indicated user and password unless HTTP_PROXY contains either of these in - # the Bundler::URI. + # the Gem::URI. # # The +NO_PROXY+ ENV variable can be used to specify hosts which shouldn't # be reached via proxy; if set it should be a comma separated list of @@ -785,7 +785,7 @@ class Gem::Net::HTTP::Persistent return nil if env_proxy.nil? or env_proxy.empty? - uri = Bundler::URI normalize_uri env_proxy + uri = Gem::URI normalize_uri env_proxy env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY'] @@ -863,7 +863,7 @@ class Gem::Net::HTTP::Persistent # +req+ must be a Gem::Net::HTTPGenericRequest subclass (see Gem::Net::HTTP for a list). def request uri, req = nil, &block - uri = Bundler::URI uri + uri = Gem::URI uri req = request_setup req || uri response = nil @@ -896,7 +896,7 @@ class Gem::Net::HTTP::Persistent end ## - # Creates a GET request if +req_or_uri+ is a Bundler::URI and adds headers to the + # Creates a GET request if +req_or_uri+ is a Gem::URI and adds headers to the # request. # # Returns the request. diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb index 4bf61461b2..36ab06254d 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb @@ -1,4 +1,5 @@ require_relative 'package' +require_relative 'rubygems' require_relative 'version_constraint' require_relative 'incompatibility' require_relative 'basic_package_source' diff --git a/lib/bundler/vendored_net_http.rb b/lib/bundler/vendored_net_http.rb index 908ec4bcaf..0dcabaa7d7 100644 --- a/lib/bundler/vendored_net_http.rb +++ b/lib/bundler/vendored_net_http.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true begin - require "rubygems/net/http" + require "rubygems/vendored_net_http" rescue LoadError - require "net/http" - Gem::Net = Net + begin + require "rubygems/net/http" + rescue LoadError + require "net/http" + Gem::Net = Net + end end diff --git a/lib/bundler/vendored_timeout.rb b/lib/bundler/vendored_timeout.rb index 34770f2116..9b2507c0cc 100644 --- a/lib/bundler/vendored_timeout.rb +++ b/lib/bundler/vendored_timeout.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true begin - require "rubygems/timeout" + require "rubygems/vendored_timeout" rescue LoadError - require "timeout" - Gem::Timeout = Timeout + begin + require "rubygems/timeout" + rescue LoadError + require "timeout" + Gem::Timeout = Timeout + end end diff --git a/lib/bundler/vendored_uri.rb b/lib/bundler/vendored_uri.rb index 905e8158e8..2efddc65f9 100644 --- a/lib/bundler/vendored_uri.rb +++ b/lib/bundler/vendored_uri.rb @@ -1,4 +1,21 @@ # frozen_string_literal: true module Bundler; end -require_relative "vendor/uri/lib/uri" + +# Use RubyGems vendored copy when available. Otherwise fallback to Bundler +# vendored copy. The vendored copy in Bundler can be removed once support for +# RubyGems 3.5 is dropped. + +begin + require "rubygems/vendor/uri/lib/uri" +rescue LoadError + require_relative "vendor/uri/lib/uri" + Gem::URI = Bundler::URI + + module Gem + def URI(uri) # rubocop:disable Naming/MethodName + Bundler::URI(uri) + end + module_function :URI + end +end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 05b6c66ce4..cc9550e988 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.5.5".freeze + VERSION = "2.5.9".freeze def self.bundler_major_version @bundler_major_version ||= VERSION.split(".").first.to_i diff --git a/lib/bundler/yaml_serializer.rb b/lib/bundler/yaml_serializer.rb index 37ccc46c26..42e6aaf89d 100644 --- a/lib/bundler/yaml_serializer.rb +++ b/lib/bundler/yaml_serializer.rb @@ -58,6 +58,8 @@ module Bundler 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? @@ -72,6 +74,8 @@ module Bundler 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) @@ -80,6 +84,14 @@ module Bundler 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) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index d4138b2d8f..9e9eca0182 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "3.5.5" + VERSION = "3.5.9" end # Must be first since it unloads the prelude from 1.9.2 @@ -1225,7 +1225,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} def find_unresolved_default_spec(path) default_spec = @path_to_default_spec_map[path] - return default_spec if default_spec && loaded_specs[default_spec.name] != default_spec + default_spec if default_spec && loaded_specs[default_spec.name] != default_spec end ## diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb index 06ec2a5bdd..ec498a8b94 100644 --- a/lib/rubygems/command.rb +++ b/lib/rubygems/command.rb @@ -6,7 +6,7 @@ # See LICENSE.txt for permissions. #++ -require_relative "optparse" +require_relative "vendored_optparse" require_relative "requirement" require_relative "user_interaction" diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index ee28dce626..8e578dc196 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -60,6 +60,7 @@ class Gem::CommandManager :push, :query, :rdoc, + :rebuild, :search, :server, :signin, @@ -106,7 +107,7 @@ class Gem::CommandManager # Register all the subcommands supported by the gem command. def initialize - require_relative "timeout" + require_relative "vendored_timeout" @commands = {} BUILTIN_COMMANDS.each do |name| diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 0ebdec565b..2ec8324141 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true 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" @@ -75,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/help_command.rb b/lib/rubygems/commands/help_command.rb index 8994f1aa09..1619b152e7 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -59,7 +59,7 @@ 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: @@ -172,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 ================================== diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb index 1321bc45f7..977c90b8c4 100644 --- a/lib/rubygems/commands/rdoc_command.rb +++ b/lib/rubygems/commands/rdoc_command.rb @@ -84,14 +84,7 @@ Use --overwrite to force rebuilding of documentation. FileUtils.rm_rf File.join(spec.doc_dir, "rdoc") end - begin - doc.generate - rescue Errno::ENOENT => e - match = / - /.match(e.message) - alert_error "Unable to document #{spec.full_name}, " \ - " #{match.post_match} 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..97f05ef79c --- /dev/null +++ b/lib/rubygems/commands/rebuild_command.rb @@ -0,0 +1,264 @@ +# 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 + + DATE_FORMAT = "%Y-%m-%d %H:%M:%S.%N Z" + + 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 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/sources_command.rb b/lib/rubygems/commands/sources_command.rb index c9c6ee80ed..976f4a4ea2 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -59,7 +59,7 @@ 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 @@ -81,7 +81,7 @@ 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 && uri.scheme.casecmp("http").zero? && uri.host.casecmp("rubygems.org").zero? diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index 1cad6af272..3200beb45f 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -202,21 +202,33 @@ class Gem::ConfigFile @hash = @hash.merge environment_config end + @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 @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 diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index 19cf306f88..ca161a4d95 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -112,7 +112,7 @@ 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 ## @@ -145,21 +145,21 @@ module Gem # 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")) + @state_home ||= ENV["XDG_STATE_HOME"] || File.join(Gem.user_home, ".local", "state") end ## diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index 00eff2dfe7..d1bf074441 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -328,9 +328,9 @@ class Gem::Dependency return active if active unless prerelease? - # Move prereleases to the end of the list for >= 0 requirements + # Consider prereleases only as a fallback pre, matches = matches.partition {|spec| spec.version.prerelease? } - matches += pre if requirement == Gem::Requirement.default + matches = pre if matches.empty? end matches.first diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index 30098ff0b5..ad5e59e8c1 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -6,7 +6,7 @@ # See LICENSE.txt for permissions. #++ -require_relative "tsort" +require_relative "vendored_tsort" require_relative "deprecate" ## diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 5cf32e9af3..3eaf5f4ef8 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -293,7 +293,7 @@ EOF case var_name # On windows, it is assumed that mkmf has setup an exports file for the - # extension, so we have to to create one ourselves. + # extension, so we have to create one ourselves. when "DEFFILE" write_deffile(dest_dir, crate_name) else diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index 7fcc0e037d..a8361b7ff1 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -10,7 +10,8 @@ require_relative "gemcutter_utilities/webauthn_poller" module Gem::GemcutterUtilities ERROR_CODE = 1 - API_SCOPES = [: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 @@ -84,7 +85,7 @@ 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, credentials: {}, &block) - require_relative "net/http" + require_relative "vendored_net_http" self.host = host if host unless self.host @@ -93,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}" @@ -102,7 +103,7 @@ 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) @@ -129,14 +130,14 @@ module Gem::GemcutterUtilities 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.basic_auth identifier, password request["OTP"] = otp if otp - request.body = URI.encode_www_form({ api_key: api_key }.merge(update_scope_params)) + request.body = Gem::URI.encode_www_form({ api_key: api_key }.merge(update_scope_params)) end with_response response do |_resp| @@ -158,25 +159,25 @@ module Gem::GemcutterUtilities 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(email, password) + profile = get_user_profile(identifier, password) mfa_params = get_mfa_params(profile) all_params = scope_params.merge(mfa_params) warning = profile["warning"] - credentials = { email: email, password: password } + credentials = { identifier: identifier, password: password } say "#{warning}\n" if warning response = rubygems_api_request(:post, "api/v1/api_key", sign_in_host, credentials: credentials, scope: scope) do |request| - request.basic_auth email, password + request.basic_auth identifier, password request["OTP"] = otp if otp - request.body = URI.encode_www_form({ name: key_name }.merge(all_params)) + request.body = Gem::URI.encode_www_form({ name: key_name }.merge(all_params)) end with_response response do |resp| @@ -294,7 +295,7 @@ module Gem::GemcutterUtilities if credentials.empty? request.add_field "Authorization", api_key else - request.basic_auth credentials[:email], credentials[:password] + request.basic_auth credentials[:identifier], credentials[:password] end end response.is_a?(Gem::Net::HTTPSuccess) ? response.body : nil @@ -309,15 +310,31 @@ module Gem::GemcutterUtilities 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 |s| - selected = ask_yes_no(s.to_s, false) - scope_params[s] = true if selected + 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 @@ -329,11 +346,11 @@ module Gem::GemcutterUtilities host == Gem::DEFAULT_HOST end - def get_user_profile(email, password) + 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 email, password + request.basic_auth identifier, password end with_response response do |resp| diff --git a/lib/rubygems/gemcutter_utilities/webauthn_listener.rb b/lib/rubygems/gemcutter_utilities/webauthn_listener.rb index bea9d9e397..abf65efe37 100644 --- a/lib/rubygems/gemcutter_utilities/webauthn_listener.rb +++ b/lib/rubygems/gemcutter_utilities/webauthn_listener.rb @@ -51,7 +51,7 @@ module Gem::GemcutterUtilities request_line = socket.gets method, req_uri, _protocol = request_line.split(" ") - req_uri = URI.parse(req_uri) + req_uri = Gem::URI.parse(req_uri) responder = SocketResponder.new(socket) 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/local_remote_options.rb b/lib/rubygems/local_remote_options.rb index e2a008fada..51a61213a5 100644 --- a/lib/rubygems/local_remote_options.rb +++ b/lib/rubygems/local_remote_options.rb @@ -6,7 +6,7 @@ # See LICENSE.txt for permissions. #++ -require "uri" +require_relative "vendor/uri/lib/uri" require_relative "../rubygems" ## @@ -17,10 +17,10 @@ module Gem::LocalRemoteOptions # Allows Gem::OptionParser to handle HTTP URIs. def accept_uri_http - Gem::OptionParser.accept URI::HTTP do |value| + Gem::OptionParser.accept Gem::URI::HTTP do |value| begin - uri = URI.parse value - rescue URI::InvalidURIError + uri = Gem::URI.parse value + rescue Gem::URI::InvalidURIError raise Gem::OptionParser::InvalidArgument, value end @@ -88,7 +88,7 @@ module Gem::LocalRemoteOptions def add_proxy_option accept_uri_http - add_option(:"Local/Remote", "-p", "--[no-]http-proxy [URL]", URI::HTTP, + 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] @@ -101,7 +101,7 @@ module Gem::LocalRemoteOptions def add_source_option accept_uri_http - add_option(:"Local/Remote", "-s", "--source URL", URI::HTTP, + 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?("/") diff --git a/lib/rubygems/net-http/.document b/lib/rubygems/net-http/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/net-http/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/net-http/lib/net/http.rb b/lib/rubygems/net-http/lib/net/http.rb deleted file mode 100644 index 833912e9a2..0000000000 --- a/lib/rubygems/net-http/lib/net/http.rb +++ /dev/null @@ -1,2496 +0,0 @@ -# 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 . -# HTTPS support added by GOTOU Yuuzou . -# -# 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 '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 {URI::Generic}[rdoc-ref:URI::Generic] object - # represents an internet URI. - # It provides, among others, methods - # +scheme+, +hostname+, +path+, +query+, and +fragment+. - # - # === Schemes - # - # An internet \URI has - # a {scheme}[https://en.wikipedia.org/wiki/List_of_URI_schemes]. - # - # The two schemes supported in \Gem::Net::HTTP are 'https' and 'http': - # - # uri.scheme # => "https" - # 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 = URI.encode_www_form(params) - # _uri # => # - # 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(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::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 'http_proxy'. - # - # === 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') - # # => # - # 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 'ENV['http_proxy']' - # - # When environment variable 'http_proxy' - # is set to a \URI string, - # the returned +http+ will have the server at that URI as its proxy; - # note that the \URI string must have a protocol - # such as 'http' or 'https': - # - # 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 \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 'Accept-Encoding' - # 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 'Content-Range'. - # - # 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]: - # - # - 'deflate', 'gzip', or 'x-gzip': - # decompresses the body and deletes the header. - # - 'none' or 'identity': - # 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 Content-Length headers. - # - {:ignore_eof=}[rdoc-ref:Gem::Net::HTTP#ignore_eof=]: - # Sets whether to ignore end-of-file when reading a response body - # with Content-Length 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 = 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) # => # - # 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) # => # - # 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 http.start 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 http.start 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 opts[:use_ssl] 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 ENV['http_proxy'] 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) - # # => # - # 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) - # # => # - # 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 && !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 - # # => "#" - # - 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 # => # - # 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 - # 'ENV['http_proxy']'; - # 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') # => # - # 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 = < 60 - # http.post(_uri.path, data, headers) - # # => # - # 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 Content-Length 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) - # # => # - # http.start - # # => # - # 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 - # # => # - # 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 ||= 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 # => # - # - # 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('/') # => # - # - # 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') # => # - # 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 # => # - # - # 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) # => # - # - # 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 # => # - # - # 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) # => # - # - 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) # => # - # - 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') # => # - # - # 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 # => # - # - # Output: - # - # # - # - 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') # => # - # - 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') - # # => # - # - # 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 # => # - # - # 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') - # # => # - # - 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') - # # => # - # http.send_request('POST', '/todos', 'xyzzy') - # # => # - # - 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) - # # => # - # - # req = Gem::Net::HTTP::Post.new('/todos') - # http.request(req, 'xyzzy') - # # => # - # - # 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 # => # - # - # Output: - # - # # - # - 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/net-http/lib/net/http/backward.rb b/lib/rubygems/net-http/lib/net/http/backward.rb deleted file mode 100644 index 10dbc16224..0000000000 --- a/lib/rubygems/net-http/lib/net/http/backward.rb +++ /dev/null @@ -1,40 +0,0 @@ -# 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/net-http/lib/net/http/exceptions.rb b/lib/rubygems/net-http/lib/net/http/exceptions.rb deleted file mode 100644 index c629c0113b..0000000000 --- a/lib/rubygems/net-http/lib/net/http/exceptions.rb +++ /dev/null @@ -1,34 +0,0 @@ -# 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/net-http/lib/net/http/generic_request.rb b/lib/rubygems/net-http/lib/net/http/generic_request.rb deleted file mode 100644 index a83f4761f2..0000000000 --- a/lib/rubygems/net-http/lib/net/http/generic_request.rb +++ /dev/null @@ -1,414 +0,0 @@ -# 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 URI === uri_or_path then - raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path - hostname = uri_or_path.hostname - raise ArgumentError, "no host component for 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 URI object for the request, or +nil+ if none: - # - # Gem::Net::HTTP::Get.new(uri).uri - # # => # - # Gem::Net::HTTP::Get.new('example.com').uri # => nil - # - attr_reader :uri - - # Returns +false+ if the request's header 'Accept-Encoding' - # 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) # => # - # 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 # => "#" - # - 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) # => # - # req.body_stream # => nil - # require 'stringio' - # req.body_stream = StringIO.new('xyzzy') # => # - # req.body_stream # => # - # - attr_reader :body_stream - - # Sets the body stream for the request: - # - # req = Gem::Net::HTTP::Post.new(uri) # => # - # req.body_stream # => nil - # require 'stringio' - # req.body_stream = StringIO.new('xyzzy') # => # - # req.body_stream # => # - # - 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 = URI::HTTPS - else - scheme = 'http' - klass = URI::HTTP - end - - if host = self['host'] - host.sub!(/:.*/m, '') - elsif host = @uri.host - else - host = addr - end - # convert the class of the 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, 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/net-http/lib/net/http/header.rb b/lib/rubygems/net-http/lib/net/http/header.rb deleted file mode 100644 index 918ffa9c08..0000000000 --- a/lib/rubygems/net-http/lib/net/http/header.rb +++ /dev/null @@ -1,981 +0,0 @@ -# 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 'Accept' is treated as if it were -# 'Accept'.downcase; i.e., 'accept'. -# - A symbol: Key :Accept is treated as if it were -# :Accept.to_s.downcase; i.e., 'accept'. -# -# 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 -# self[key.downcase.to_s].join(', ')): -# -# - #[]: 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 'Authorization'. -# - #content_length=: Sets the integer length for field 'Content-Length. -# - #content_type=: Sets the string value for field 'Content-Type'. -# - #proxy_basic_auth: Sets the string authorization header for 'Proxy-Authorization'. -# - #set_range: Sets the value for field 'Range'. -# -# === 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 'Content-Length'. -# - #content_range: Returns the Range value of field 'Content-Range'. -# - #content_type: Returns the string value of field 'Content-Type'. -# - #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 'Content-Type'. -# - #sub_type: Returns second part of the string value of field 'Content-Type'. -# - #range: Returns an array of Range objects of field 'Range', or +nil+. -# - #range_length: Returns the integer length of the range given in field 'Content-Range'. -# - #type_params: Returns the string parameters for 'Content-Type'. -# -# === Queries -# -# - #chunked?: Returns whether field 'Transfer-Encoding' is set to 'chunked'. -# - #connection_close?: Returns whether field 'Connection' is set to 'close'. -# - #connection_keep_alive?: Returns whether field 'Connection' is set to 'keep-alive'. -# - #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 'Range', - # 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 'Range'; - # 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 'Content-Length' 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 'Content-Length' 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) # => # - # 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 # => # - # - 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 'Transfer-Encoding' - # exists and has value 'chunked', - # +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 - # 'Content-Range', 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 - # 'Content-Range', 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 'Content-Type', - # 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 'Content-Type', - # 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 'Content-Type', - # 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 'Content-Type', - # 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 'Content-Type'; - # 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 'Content-Type' - # to 'application/x-www-form-urlencoded'. - # - # The resulting request is suitable for HTTP request +POST+ or +PUT+. - # - # Argument +params+ must be suitable for use as argument +enum+ to - # {URI.encode_www_form}[https://docs.ruby-lang.org/en/master/URI.html#method-c-encode_www_form]. - # - # With only argument +params+ given, - # sets the body to a URL-encoded string with the default separator '&': - # - # 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 = 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 params.map 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) - # - # Argument +params+ As an Array - # - # 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 - # 'multipart/form-data': - # - # - 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]]) - # - # Argument +params+ As a Hash - # - # 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 - # 'multipart/form-data'). - # - # 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 'Content-Type', and must be one of: - # - # - 'application/x-www-form-urlencoded' (the default). - # - 'multipart/form-data'; - # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578]. - # - # Optional argument +formopt+ is a hash of options - # (applicable only when argument +enctype+ - # is 'multipart/form-data') - # 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 'Authorization' 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 'Proxy-Authorization' 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/net-http/lib/net/http/proxy_delta.rb b/lib/rubygems/net-http/lib/net/http/proxy_delta.rb deleted file mode 100644 index 137295a883..0000000000 --- a/lib/rubygems/net-http/lib/net/http/proxy_delta.rb +++ /dev/null @@ -1,17 +0,0 @@ -# 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/net-http/lib/net/http/request.rb b/lib/rubygems/net-http/lib/net/http/request.rb deleted file mode 100644 index 857cbc7f67..0000000000 --- a/lib/rubygems/net-http/lib/net/http/request.rb +++ /dev/null @@ -1,88 +0,0 @@ -# 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 URI or a string hostname: -# -# require 'rubygems/net-http/lib/net/http' -# uri = URI('https://jsonplaceholder.typicode.com/') -# req = Gem::Net::HTTP::Get.new(uri) # => # -# req = Gem::Net::HTTP::Get.new(uri.hostname) # => # -# -# And with any of the subclasses: -# -# req = Gem::Net::HTTP::Head.new(uri) # => # -# req = Gem::Net::HTTP::Post.new(uri) # => # -# req = Gem::Net::HTTP::Put.new(uri) # => # -# # ... -# -# 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/net-http/lib/net/http/requests.rb b/lib/rubygems/net-http/lib/net/http/requests.rb deleted file mode 100644 index 3e26be949b..0000000000 --- a/lib/rubygems/net-http/lib/net/http/requests.rb +++ /dev/null @@ -1,425 +0,0 @@ -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Get.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Head.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# uri.path = '/posts' -# req = Gem::Net::HTTP::Post.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# uri.path = '/posts' -# req = Gem::Net::HTTP::Put.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# uri.path = '/posts/1' -# req = Gem::Net::HTTP::Delete.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Options.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Trace.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# uri.path = '/posts' -# req = Gem::Net::HTTP::Patch.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Propfind.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Proppatch.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Mkcol.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Copy.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Move.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Lock.new(uri) # => # -# 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/net-http/lib/net/http' -# uri = URI('http://example.com') -# hostname = uri.hostname # => "example.com" -# req = Gem::Net::HTTP::Unlock.new(uri) # => # -# 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/net-http/lib/net/http/response.rb b/lib/rubygems/net-http/lib/net/http/response.rb deleted file mode 100644 index 28ee365978..0000000000 --- a/lib/rubygems/net-http/lib/net/http/response.rb +++ /dev/null @@ -1,738 +0,0 @@ -# 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::HTTP.get_response(hostname, '/nosuch') -# # => # -# -# 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 # => # -# -# \Class \Gem::Net::HTTPResponse includes module Gem::Net::HTTPHeader, -# which provides access to response header values via (among others): -# -# - \Hash-like method []. -# - Specific reader methods, such as +content_type+. -# -# Examples: -# -# res = Gem::Net::HTTP.get_response(uri) # => # -# 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 URI used to fetch this response. The response URI is only available - # if a 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 # => # - # end - # - # http.request(req) do |res| - # res.body_encoding = "UTF-8" - # p res.body.encoding # => # - # 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 dest#<< method (it could be String or IO, or any - # other object responding to <<). - # - # 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' - 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/net-http/lib/net/http/responses.rb b/lib/rubygems/net-http/lib/net/http/responses.rb deleted file mode 100644 index 95ce9dd46a..0000000000 --- a/lib/rubygems/net-http/lib/net/http/responses.rb +++ /dev/null @@ -1,1174 +0,0 @@ -# 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 Switching Protocol responses (status code 101). - # - # The Switching Protocol 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 Early Hints responses (status code 103). - # - # The Early Hints 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 Non-Authoritative Information responses (status code 203). - # - # The Non-Authoritative Information 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 No Content responses (status code 204). - # - # The No Content 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 Reset Content responses (status code 205). - # - # The Reset Content 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 Partial Content responses (status code 206). - # - # The Partial Content 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 Multi-Status (WebDAV) responses (status code 207). - # - # The Multi-Status (WebDAV) 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 Already Reported (WebDAV) responses (status code 208). - # - # The Already Reported (WebDAV) 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 IM Used responses (status code 226). - # - # The IM Used 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 Multiple Choices responses (status code 300). - # - # The Multiple Choices 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 Moved Permanently responses (status code 301). - # - # The Moved Permanently 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 Found responses (status code 302). - # - # The Found 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 See Other responses (status code 303). - # - # The response to the request can be found under another 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 Not Modified 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 Use Proxy 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 Temporary Redirect responses (status code 307). - # - # The request should be repeated with another URI; - # however, future requests should still use the original 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 Permanent Redirect responses (status code 308). - # - # This and all future requests should be directed to the given 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 Bad Request 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 Unauthorized 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 Payment Required 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 Forbidden 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 Not Found 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 Method Not Allowed 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 Not Acceptable 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 Proxy Authentication Required 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 Request Gem::Timeout 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 Conflict 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 Gone 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 Length Required 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 Precondition Failed 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 Payload Too Large 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 URI Too Long responses (status code 414). - # - # The 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 Unsupported Media Type 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 Range Not Satisfiable 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 Expectation Failed 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 Misdirected Request 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 Unprocessable Entity 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 Locked (WebDAV) 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 Failed Dependency (WebDAV) 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 Upgrade Required 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 Precondition Required 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 Too Many Requests 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 Request Header Fields Too Large 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 Unavailable For Legal Reasons 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 Internal Server Error 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 Not Implemented 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 Bad Gateway 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 Service Unavailable 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 Gateway Gem::Timeout 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 HTTP Version Not Supported 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 Variant Also Negotiates 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 Insufficient Storage (WebDAV) 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 Loop Detected (WebDAV) 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 Not Extended 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 Network Authentication Required 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/net-http/lib/net/http/status.rb b/lib/rubygems/net-http/lib/net/http/status.rb deleted file mode 100644 index 10cbc8e3ee..0000000000 --- a/lib/rubygems/net-http/lib/net/http/status.rb +++ /dev/null @@ -1,84 +0,0 @@ -# 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" - 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/net-http/lib/net/https.rb b/lib/rubygems/net-http/lib/net/https.rb deleted file mode 100644 index 401d64a87c..0000000000 --- a/lib/rubygems/net-http/lib/net/https.rb +++ /dev/null @@ -1,23 +0,0 @@ -# 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/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 - 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/net-protocol/.document b/lib/rubygems/net-protocol/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/net-protocol/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/net-protocol/lib/net/protocol.rb b/lib/rubygems/net-protocol/lib/net/protocol.rb deleted file mode 100644 index 53d34d8d98..0000000000 --- a/lib/rubygems/net-protocol/lib/net/protocol.rb +++ /dev/null @@ -1,544 +0,0 @@ -# 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 -# -# 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/net/http.rb b/lib/rubygems/net/http.rb deleted file mode 100644 index 1a21c05191..0000000000 --- a/lib/rubygems/net/http.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require_relative "../net-http/lib/net/http" diff --git a/lib/rubygems/optparse.rb b/lib/rubygems/optparse.rb deleted file mode 100644 index 6ed718423c..0000000000 --- a/lib/rubygems/optparse.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require_relative "optparse/lib/optparse" diff --git a/lib/rubygems/optparse/.document b/lib/rubygems/optparse/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/optparse/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/optparse/lib/optionparser.rb b/lib/rubygems/optparse/lib/optionparser.rb deleted file mode 100644 index 4b9b40d82a..0000000000 --- a/lib/rubygems/optparse/lib/optionparser.rb +++ /dev/null @@ -1,2 +0,0 @@ -# frozen_string_literal: false -require_relative 'optparse' diff --git a/lib/rubygems/optparse/lib/optparse.rb b/lib/rubygems/optparse/lib/optparse.rb deleted file mode 100644 index 8e700016b0..0000000000 --- a/lib/rubygems/optparse/lib/optparse.rb +++ /dev/null @@ -1,2330 +0,0 @@ -# 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/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/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/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 `
': 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 +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/optparse/lib/optparse' -# require 'rubygems/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/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 -# # -# $ ruby optparse-test.rb --user 2 -# # -# $ 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/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/optparse/lib/optparse' -# require 'rubygems/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/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 - # []= 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 - # []= 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 []= 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/optparse/lib/optparse/ac.rb b/lib/rubygems/optparse/lib/optparse/ac.rb deleted file mode 100644 index e84d01bf91..0000000000 --- a/lib/rubygems/optparse/lib/optparse/ac.rb +++ /dev/null @@ -1,54 +0,0 @@ -# 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/optparse/lib/optparse/date.rb b/lib/rubygems/optparse/lib/optparse/date.rb deleted file mode 100644 index d9a9f4f48a..0000000000 --- a/lib/rubygems/optparse/lib/optparse/date.rb +++ /dev/null @@ -1,18 +0,0 @@ -# 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/optparse/lib/optparse/kwargs.rb b/lib/rubygems/optparse/lib/optparse/kwargs.rb deleted file mode 100644 index 6987a5ed62..0000000000 --- a/lib/rubygems/optparse/lib/optparse/kwargs.rb +++ /dev/null @@ -1,22 +0,0 @@ -# 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/optparse/lib/optparse/shellwords.rb b/lib/rubygems/optparse/lib/optparse/shellwords.rb deleted file mode 100644 index d47ad60255..0000000000 --- a/lib/rubygems/optparse/lib/optparse/shellwords.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: false -# -*- ruby -*- - -require 'shellwords' -require_relative '../optparse' - -Gem::OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)} diff --git a/lib/rubygems/optparse/lib/optparse/time.rb b/lib/rubygems/optparse/lib/optparse/time.rb deleted file mode 100644 index c59e1e4ced..0000000000 --- a/lib/rubygems/optparse/lib/optparse/time.rb +++ /dev/null @@ -1,11 +0,0 @@ -# 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/optparse/lib/optparse/uri.rb b/lib/rubygems/optparse/lib/optparse/uri.rb deleted file mode 100644 index 664d7f2af4..0000000000 --- a/lib/rubygems/optparse/lib/optparse/uri.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: false -# -*- ruby -*- - -require_relative '../optparse' -require 'uri' - -Gem::OptionParser.accept(URI) {|s,| URI.parse(s) if s} diff --git a/lib/rubygems/optparse/lib/optparse/version.rb b/lib/rubygems/optparse/lib/optparse/version.rb deleted file mode 100644 index 5d79e9db44..0000000000 --- a/lib/rubygems/optparse/lib/optparse/version.rb +++ /dev/null @@ -1,71 +0,0 @@ -# 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/package.rb b/lib/rubygems/package.rb index 387e40ffd7..1d5d764237 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -59,7 +59,7 @@ class Gem::Package def initialize(message, source = nil) if source - @path = source.path + @path = source.is_a?(String) ? source : source.path message += " in #{path}" if path end @@ -454,7 +454,7 @@ EOM if entry.file? File.open(destination, "wb") {|out| copy_stream(entry, out) } - FileUtils.chmod file_mode(entry.header.mode), destination + FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination end verbose destination diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 8f14d7bc8d..c3a41592f6 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -74,9 +74,9 @@ class Gem::RemoteFetcher def initialize(proxy=nil, dns=nil, headers={}) require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled - require_relative "net/http" + require_relative "vendored_net_http" require "stringio" - require "uri" + require_relative "vendor/uri/lib/uri" Socket.do_not_reverse_lookup = true @@ -135,7 +135,7 @@ class Gem::RemoteFetcher scheme = source_uri.scheme - # URI.parse gets confused by MS Windows paths with forward slashes. + # 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) diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 8702e223d6..9116785231 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "net/http" +require_relative "vendored_net_http" require_relative "user_interaction" class Gem::Request @@ -18,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 @@ -176,7 +176,7 @@ class Gem::Request end require "uri" - uri = URI(Gem::UriFormatter.new(env_proxy).normalize) + uri = Gem::URI(Gem::UriFormatter.new(env_proxy).normalize) if uri && uri.user.nil? && uri.password.nil? user = ENV["#{downcase_scheme}_proxy_user"] || ENV["#{upcase_scheme}_PROXY_USER"] diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 02b3477661..875df7e019 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "tsort" +require_relative "vendored_tsort" ## # A RequestSet groups a request to activate a set of dependencies. diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 4ebafb065f..02543cb14a 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -284,6 +284,11 @@ class Gem::Requirement def _tilde_requirements @_tilde_requirements ||= _sorted_requirements.select {|r| r.first == "~>" } end + + def initialize_copy(other) # :nodoc: + @requirements = other.requirements.dup + super + end end class Gem::Version diff --git a/lib/rubygems/resolv/.document b/lib/rubygems/resolv/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/resolv/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/resolv/lib/resolv.rb b/lib/rubygems/resolv/lib/resolv.rb deleted file mode 100644 index 1209d5167a..0000000000 --- a/lib/rubygems/resolv/lib/resolv.rb +++ /dev/null @@ -1,3387 +0,0 @@ -# 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.3.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, hostname, *aliases = line.split(/\s+/) - next unless addr - @addr2name[addr] = [] unless @addr2name.include? addr - @addr2name[addr] << hostname - @addr2name[addr].concat(aliases) - @name2addr[hostname] = [] unless @name2addr.include? hostname - @name2addr[hostname] << addr - aliases.each {|n| - @name2addr[n] = [] unless @name2addr.include? n - @name2addr[n] << 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(?\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 - - ClassInsensitiveTypes = [ # :nodoc: - NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY - ] - - ## - # 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 paramters 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/resolver.rb b/lib/rubygems/resolver.rb index 620ba921bc..115c716b6b 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -11,7 +11,7 @@ require_relative "util/list" # all the requirements. class Gem::Resolver - require_relative "resolver/molinillo" + require_relative "vendored_molinillo" ## # If the DEBUG_RESOLVER environment variable is set then debugging mode is @@ -167,7 +167,7 @@ class Gem::Resolver reqs end - include Molinillo::UI + include Gem::Molinillo::UI def output @output ||= debug? ? $stdout : File.open(IO::NULL, "w") @@ -177,14 +177,14 @@ class Gem::Resolver DEBUG_RESOLVER end - include Molinillo::SpecificationProvider + include Gem::Molinillo::SpecificationProvider ## # Proceed with resolution! Returns an array of ActivationRequest objects. def resolve - Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }).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 diff --git a/lib/rubygems/resolver/api_set.rb b/lib/rubygems/resolver/api_set.rb index e8e3747361..3e4dadc40f 100644 --- a/lib/rubygems/resolver/api_set.rb +++ b/lib/rubygems/resolver/api_set.rb @@ -30,7 +30,7 @@ class Gem::Resolver::APISet < Gem::Resolver::Set 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 + ".." diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb index c2e8982047..a983f8c6b6 100644 --- a/lib/rubygems/resolver/best_set.rb +++ b/lib/rubygems/resolver/best_set.rb @@ -60,7 +60,7 @@ 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 = Gem::URI uri unless Gem::URI === uri uri += "." raise error unless api_set = @sets.find do |set| diff --git a/lib/rubygems/resolver/molinillo.rb b/lib/rubygems/resolver/molinillo.rb deleted file mode 100644 index d703505410..0000000000 --- a/lib/rubygems/resolver/molinillo.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require_relative "molinillo/lib/molinillo" diff --git a/lib/rubygems/resolver/molinillo/.document b/lib/rubygems/resolver/molinillo/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/resolver/molinillo/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo.rb b/lib/rubygems/resolver/molinillo/lib/molinillo.rb deleted file mode 100644 index f67badbde7..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -require_relative 'molinillo/gem_metadata' -require_relative 'molinillo/errors' -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 -end 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/delegates/specification_provider.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb deleted file mode 100644 index b765226fb0..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::Molinillo - module Delegates - # Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a - # `#specification_provider` property. - module SpecificationProvider - # (see Gem::Resolver::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) - 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?) - 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?) - 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) - 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) - 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) - 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) - 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?) - def allow_missing?(dependency) - with_no_such_dependency_error_handling do - specification_provider.allow_missing?(dependency) - end - end - - private - - # Ensures any raised {NoSuchDependencyError} has its - # {NoSuchDependencyError#required_by} set. - # @yield - def with_no_such_dependency_error_handling - yield - rescue NoSuchDependencyError => error - if state - vertex = activated.vertex_named(name_for(error.dependency)) - error.required_by += vertex.incoming_edges.map { |e| e.origin.name } - error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? - end - raise - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb deleted file mode 100644 index 731a9e3e90..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb +++ /dev/null @@ -1,255 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../../tsort' - -require_relative 'dependency_graph/log' -require_relative 'dependency_graph/vertex' - -module Gem::Resolver::Molinillo - # A directed acyclic graph that is tuned to hold named dependencies - class DependencyGraph - include Enumerable - - # Enumerates through the vertices of the graph. - # @return [Array] The graph's vertices. - def each - return vertices.values.each unless block_given? - vertices.values.each { |v| yield v } - end - - include Gem::TSort - - # @!visibility private - alias tsort_each_node each - - # @!visibility private - def tsort_each_child(vertex, &block) - vertex.successors.each(&block) - end - - # Topologically sorts the given vertices. - # @param [Enumerable] vertices the vertices to be sorted, which must - # all belong to the same graph. - # @return [Array] The sorted vertices. - def self.tsort(vertices) - Gem::TSort.tsort( - lambda { |b| vertices.each(&b) }, - lambda { |v, &b| (v.successors & vertices).each(&b) } - ) - end - - # A directed edge of a {DependencyGraph} - # @attr [Vertex] origin The origin of the directed edge - # @attr [Vertex] destination The destination of the directed edge - # @attr [Object] requirement The requirement the directed edge represents - Edge = Struct.new(:origin, :destination, :requirement) - - # @return [{String => Vertex}] the vertices of the dependency graph, keyed - # by {Vertex#name} - attr_reader :vertices - - # @return [Log] the op log for this graph - attr_reader :log - - # Initializes an empty dependency graph - def initialize - @vertices = {} - @log = Log.new - end - - # Tags the current state of the dependency as the given tag - # @param [Object] tag an opaque tag for the current state of the graph - # @return [Void] - def tag(tag) - log.tag(self, tag) - end - - # Rewinds the graph to the state tagged as `tag` - # @param [Object] tag the tag to rewind to - # @return [Void] - def rewind_to(tag) - log.rewind_to(self, tag) - end - - # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} - # are properly copied. - # @param [DependencyGraph] other the graph to copy. - def initialize_copy(other) - super - @vertices = {} - @log = other.log.dup - traverse = lambda do |new_v, old_v| - return if new_v.outgoing_edges.size == old_v.outgoing_edges.size - old_v.outgoing_edges.each do |edge| - destination = add_vertex(edge.destination.name, edge.destination.payload) - add_edge_no_circular(new_v, destination, edge.requirement) - traverse.call(destination, edge.destination) - end - end - other.vertices.each do |name, vertex| - new_vertex = add_vertex(name, vertex.payload, vertex.root?) - new_vertex.explicit_requirements.replace(vertex.explicit_requirements) - traverse.call(new_vertex, vertex) - end - end - - # @return [String] a string suitable for debugging - def inspect - "#{self.class}:#{vertices.values.inspect}" - end - - # @param [Hash] options options for dot output. - # @return [String] Returns a dot format representation of the graph - def to_dot(options = {}) - edge_label = options.delete(:edge_label) - raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? - - dot_vertices = [] - dot_edges = [] - vertices.each do |n, v| - dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" - v.outgoing_edges.each do |e| - label = edge_label ? edge_label.call(e) : e.requirement - dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" - end - end - - dot_vertices.uniq! - dot_vertices.sort! - dot_edges.uniq! - dot_edges.sort! - - dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') - dot.join("\n") - end - - # @param [DependencyGraph] other - # @return [Boolean] whether the two dependency graphs are equal, determined - # by a recursive traversal of each {#root_vertices} and its - # {Vertex#successors} - def ==(other) - return false unless other - return true if equal?(other) - vertices.each do |name, vertex| - other_vertex = other.vertex_named(name) - return false unless other_vertex - return false unless vertex.payload == other_vertex.payload - return false unless other_vertex.successors.to_set == vertex.successors.to_set - end - end - - # @param [String] name - # @param [Object] payload - # @param [Array] parent_names - # @param [Object] requirement the requirement that is requiring the child - # @return [void] - def add_child_vertex(name, payload, parent_names, requirement) - root = !parent_names.delete(nil) { true } - vertex = add_vertex(name, payload, root) - vertex.explicit_requirements << requirement if root - parent_names.each do |parent_name| - parent_vertex = vertex_named(parent_name) - add_edge(parent_vertex, vertex, requirement) - end - vertex - end - - # Adds a vertex with the given name, or updates the existing one. - # @param [String] name - # @param [Object] payload - # @return [Vertex] the vertex that was added to `self` - def add_vertex(name, payload, root = false) - log.add_vertex(self, name, payload, root) - end - - # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively - # removing any non-root vertices that were orphaned in the process - # @param [String] name - # @return [Array] the vertices which have been detached - def detach_vertex_named(name) - log.detach_vertex_named(self, name) - end - - # @param [String] name - # @return [Vertex,nil] the vertex with the given name - def vertex_named(name) - vertices[name] - end - - # @param [String] name - # @return [Vertex,nil] the root vertex with the given name - def root_vertex_named(name) - vertex = vertex_named(name) - vertex if vertex && vertex.root? - end - - # Adds a new {Edge} to the dependency graph - # @param [Vertex] origin - # @param [Vertex] destination - # @param [Object] requirement the requirement that this edge represents - # @return [Edge] the added edge - def add_edge(origin, destination, requirement) - if destination.path_to?(origin) - raise CircularDependencyError.new(path(destination, origin)) - end - add_edge_no_circular(origin, destination, requirement) - end - - # Deletes an {Edge} from the dependency graph - # @param [Edge] edge - # @return [Void] - def delete_edge(edge) - log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) - end - - # Sets the payload of the vertex with the given name - # @param [String] name the name of the vertex - # @param [Object] payload the payload - # @return [Void] - def set_payload(name, payload) - log.set_payload(self, name, payload) - end - - private - - # Adds a new {Edge} to the dependency graph without checking for - # circularity. - # @param (see #add_edge) - # @return (see #add_edge) - def add_edge_no_circular(origin, destination, requirement) - log.add_edge_no_circular(self, origin.name, destination.name, requirement) - end - - # Returns the path between two vertices - # @raise [ArgumentError] if there is no path between the vertices - # @param [Vertex] from - # @param [Vertex] to - # @return [Array] the shortest path from `from` to `to` - def path(from, to) - distances = Hash.new(vertices.size + 1) - distances[from.name] = 0 - predecessors = {} - each do |vertex| - vertex.successors.each do |successor| - if distances[successor.name] > distances[vertex.name] + 1 - distances[successor.name] = distances[vertex.name] + 1 - predecessors[successor] = vertex - end - end - end - - path = [to] - while before = predecessors[to] - path << before - to = before - break if to == from - end - - unless path.last.equal?(from) - raise ArgumentError, "There is no path from #{from.name} to #{to.name}" - end - - path.reverse - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb deleted file mode 100644 index cc140031b3..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::Molinillo - class DependencyGraph - # An action that modifies a {DependencyGraph} that is reversible. - # @abstract - class Action - # rubocop:disable Lint/UnusedMethodArgument - - # @return [Symbol] The name of the action. - def self.action_name - raise 'Abstract' - end - - # Performs the action on the given graph. - # @param [DependencyGraph] graph the graph to perform the action on. - # @return [Void] - def up(graph) - raise 'Abstract' - end - - # Reverses the action on the given graph. - # @param [DependencyGraph] graph the graph to reverse the action on. - # @return [Void] - def down(graph) - raise 'Abstract' - end - - # @return [Action,Nil] The previous action - attr_accessor :previous - - # @return [Action,Nil] The next action - attr_accessor :next - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb deleted file mode 100644 index 5570483253..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Gem::Resolver::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#add_edge_no_circular) - class AddEdgeNoCircular < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges << edge - edge.destination.incoming_edges << edge - edge - end - - # (see Action#down) - def down(graph) - edge = make_edge(graph) - delete_first(edge.origin.outgoing_edges, edge) - delete_first(edge.destination.incoming_edges, edge) - end - - # @!group AddEdgeNoCircular - - # @return [String] the name of the origin of the edge - attr_reader :origin - - # @return [String] the name of the destination of the edge - attr_reader :destination - - # @return [Object] the requirement that the edge represents - attr_reader :requirement - - # @param [DependencyGraph] graph the graph to find vertices from - # @return [Edge] The edge this action adds - def make_edge(graph) - Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement) - end - - # Initialize an action to add an edge to a dependency graph - # @param [String] origin the name of the origin of the edge - # @param [String] destination the name of the destination of the edge - # @param [Object] requirement the requirement that the edge represents - def initialize(origin, destination, requirement) - @origin = origin - @destination = destination - @requirement = requirement - end - - private - - def delete_first(array, item) - return unless index = array.index(item) - array.delete_at(index) - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb deleted file mode 100644 index f1411d5efa..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Gem::Resolver::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#add_vertex) - class AddVertex < Action # :nodoc: - # @!group Action - - # (see Action.action_name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - if existing = graph.vertices[name] - @existing_payload = existing.payload - @existing_root = existing.root - end - vertex = existing || Vertex.new(name, payload) - graph.vertices[vertex.name] = vertex - vertex.payload ||= payload - vertex.root ||= root - vertex - end - - # (see Action#down) - def down(graph) - if defined?(@existing_payload) - vertex = graph.vertices[name] - vertex.payload = @existing_payload - vertex.root = @existing_root - else - graph.vertices.delete(name) - end - end - - # @!group AddVertex - - # @return [String] the name of the vertex - attr_reader :name - - # @return [Object] the payload for the vertex - attr_reader :payload - - # @return [Boolean] whether the vertex is root or not - attr_reader :root - - # Initialize an action to add a vertex to a dependency graph - # @param [String] name the name of the vertex - # @param [Object] payload the payload for the vertex - # @param [Boolean] root whether the vertex is root or not - def initialize(name, payload, root) - @name = name - @payload = payload - @root = root - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb deleted file mode 100644 index 3b48d77a50..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Gem::Resolver::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#delete_edge) - class DeleteEdge < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :delete_edge - end - - # (see Action#up) - def up(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges.delete(edge) - edge.destination.incoming_edges.delete(edge) - end - - # (see Action#down) - def down(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges << edge - edge.destination.incoming_edges << edge - edge - end - - # @!group DeleteEdge - - # @return [String] the name of the origin of the edge - attr_reader :origin_name - - # @return [String] the name of the destination of the edge - attr_reader :destination_name - - # @return [Object] the requirement that the edge represents - attr_reader :requirement - - # @param [DependencyGraph] graph the graph to find vertices from - # @return [Edge] The edge this action adds - def make_edge(graph) - Edge.new( - graph.vertex_named(origin_name), - graph.vertex_named(destination_name), - requirement - ) - end - - # Initialize an action to add an edge to a dependency graph - # @param [String] origin_name the name of the origin of the edge - # @param [String] destination_name the name of the destination of the edge - # @param [Object] requirement the requirement that the edge represents - def initialize(origin_name, destination_name, requirement) - @origin_name = origin_name - @destination_name = destination_name - @requirement = requirement - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb deleted file mode 100644 index 92f60d5be8..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Gem::Resolver::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#detach_vertex_named - class DetachVertexNamed < Action - # @!group Action - - # (see Action#name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - return [] unless @vertex = graph.vertices.delete(name) - - removed_vertices = [@vertex] - @vertex.outgoing_edges.each do |e| - v = e.destination - v.incoming_edges.delete(e) - if !v.root? && v.incoming_edges.empty? - removed_vertices.concat graph.detach_vertex_named(v.name) - end - end - - @vertex.incoming_edges.each do |e| - v = e.origin - v.outgoing_edges.delete(e) - end - - removed_vertices - end - - # (see Action#down) - def down(graph) - return unless @vertex - graph.vertices[@vertex.name] = @vertex - @vertex.outgoing_edges.each do |e| - e.destination.incoming_edges << e - end - @vertex.incoming_edges.each do |e| - e.origin.outgoing_edges << e - end - end - - # @!group DetachVertexNamed - - # @return [String] the name of the vertex to detach - attr_reader :name - - # Initialize an action to detach a vertex from a dependency graph - # @param [String] name the name of the vertex to detach - def initialize(name) - @name = name - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb deleted file mode 100644 index 7aeb8847ec..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: true - -require_relative 'add_edge_no_circular' -require_relative 'add_vertex' -require_relative 'delete_edge' -require_relative 'detach_vertex_named' -require_relative 'set_payload' -require_relative 'tag' - -module Gem::Resolver::Molinillo - class DependencyGraph - # A log for dependency graph actions - class Log - # Initializes an empty log - def initialize - @current_action = @first_action = nil - end - - # @!macro [new] action - # {include:DependencyGraph#$0} - # @param [Graph] graph the graph to perform the action on - # @param (see DependencyGraph#$0) - # @return (see DependencyGraph#$0) - - # @macro action - def tag(graph, tag) - push_action(graph, Tag.new(tag)) - end - - # @macro action - def add_vertex(graph, name, payload, root) - push_action(graph, AddVertex.new(name, payload, root)) - end - - # @macro action - def detach_vertex_named(graph, name) - push_action(graph, DetachVertexNamed.new(name)) - end - - # @macro action - def add_edge_no_circular(graph, origin, destination, requirement) - push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) - end - - # {include:DependencyGraph#delete_edge} - # @param [Graph] graph the graph to perform the action on - # @param [String] origin_name - # @param [String] destination_name - # @param [Object] requirement - # @return (see DependencyGraph#delete_edge) - def delete_edge(graph, origin_name, destination_name, requirement) - push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) - end - - # @macro action - def set_payload(graph, name, payload) - push_action(graph, SetPayload.new(name, payload)) - end - - # Pops the most recent action from the log and undoes the action - # @param [DependencyGraph] graph - # @return [Action] the action that was popped off the log - def pop!(graph) - return unless action = @current_action - unless @current_action = action.previous - @first_action = nil - end - action.down(graph) - action - end - - extend Enumerable - - # @!visibility private - # Enumerates each action in the log - # @yield [Action] - def each - return enum_for unless block_given? - action = @first_action - loop do - break unless action - yield action - action = action.next - end - self - end - - # @!visibility private - # Enumerates each action in the log in reverse order - # @yield [Action] - def reverse_each - return enum_for(:reverse_each) unless block_given? - action = @current_action - loop do - break unless action - yield action - action = action.previous - end - self - end - - # @macro action - def rewind_to(graph, tag) - loop do - action = pop!(graph) - raise "No tag #{tag.inspect} found" unless action - break if action.class.action_name == :tag && action.tag == tag - end - end - - private - - # Adds the given action to the log, running the action - # @param [DependencyGraph] graph - # @param [Action] action - # @return The value returned by `action.up` - def push_action(graph, action) - action.previous = @current_action - @current_action.next = action if @current_action - @current_action = action - @first_action ||= action - action.up(graph) - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb deleted file mode 100644 index 726292a2c3..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Gem::Resolver::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#set_payload - class SetPayload < Action # :nodoc: - # @!group Action - - # (see Action.action_name) - def self.action_name - :set_payload - end - - # (see Action#up) - def up(graph) - vertex = graph.vertex_named(name) - @old_payload = vertex.payload - vertex.payload = payload - end - - # (see Action#down) - def down(graph) - graph.vertex_named(name).payload = @old_payload - end - - # @!group SetPayload - - # @return [String] the name of the vertex - attr_reader :name - - # @return [Object] the payload for the vertex - attr_reader :payload - - # Initialize an action to add set the payload for a vertex in a dependency - # graph - # @param [String] name the name of the vertex - # @param [Object] payload the payload for the vertex - def initialize(name, payload) - @name = name - @payload = payload - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb deleted file mode 100644 index bfe6fd31f8..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Gem::Resolver::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#tag - class Tag < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :tag - end - - # (see Action#up) - def up(graph) - end - - # (see Action#down) - def down(graph) - end - - # @!group Tag - - # @return [Object] An opaque tag - attr_reader :tag - - # Initialize an action to tag a state of a dependency graph - # @param [Object] tag an opaque tag - def initialize(tag) - @tag = tag - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb deleted file mode 100644 index 77114951b2..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::Molinillo - class DependencyGraph - # A vertex in a {DependencyGraph} that encapsulates a {#name} and a - # {#payload} - class Vertex - # @return [String] the name of the vertex - attr_accessor :name - - # @return [Object] the payload the vertex holds - attr_accessor :payload - - # @return [Array] the explicit requirements that required - # this vertex - attr_reader :explicit_requirements - - # @return [Boolean] whether the vertex is considered a root vertex - attr_accessor :root - alias root? root - - # Initializes a vertex with the given name and payload. - # @param [String] name see {#name} - # @param [Object] payload see {#payload} - def initialize(name, payload) - @name = name.frozen? ? name : name.dup.freeze - @payload = payload - @explicit_requirements = [] - @outgoing_edges = [] - @incoming_edges = [] - end - - # @return [Array] all of the requirements that required - # this vertex - def requirements - (incoming_edges.map(&:requirement) + explicit_requirements).uniq - end - - # @return [Array] the edges of {#graph} that have `self` as their - # {Edge#origin} - attr_accessor :outgoing_edges - - # @return [Array] the edges of {#graph} that have `self` as their - # {Edge#destination} - attr_accessor :incoming_edges - - # @return [Array] the vertices of {#graph} that have an edge with - # `self` as their {Edge#destination} - def predecessors - incoming_edges.map(&:origin) - end - - # @return [Set] the vertices of {#graph} where `self` is a - # {#descendent?} - def recursive_predecessors - _recursive_predecessors - end - - # @param [Set] vertices the set to add the predecessors to - # @return [Set] the vertices of {#graph} where `self` is a - # {#descendent?} - def _recursive_predecessors(vertices = new_vertex_set) - incoming_edges.each do |edge| - vertex = edge.origin - next unless vertices.add?(vertex) - vertex._recursive_predecessors(vertices) - end - - vertices - end - protected :_recursive_predecessors - - # @return [Array] the vertices of {#graph} that have an edge with - # `self` as their {Edge#origin} - def successors - outgoing_edges.map(&:destination) - end - - # @return [Set] the vertices of {#graph} where `self` is an - # {#ancestor?} - def recursive_successors - _recursive_successors - end - - # @param [Set] vertices the set to add the successors to - # @return [Set] the vertices of {#graph} where `self` is an - # {#ancestor?} - def _recursive_successors(vertices = new_vertex_set) - outgoing_edges.each do |edge| - vertex = edge.destination - next unless vertices.add?(vertex) - vertex._recursive_successors(vertices) - end - - vertices - end - protected :_recursive_successors - - # @return [String] a string suitable for debugging - def inspect - "#{self.class}:#{name}(#{payload.inspect})" - end - - # @return [Boolean] whether the two vertices are equal, determined - # by a recursive traversal of each {Vertex#successors} - def ==(other) - return true if equal?(other) - shallow_eql?(other) && - successors.to_set == other.successors.to_set - end - - # @param [Vertex] other the other vertex to compare to - # @return [Boolean] whether the two vertices are equal, determined - # solely by {#name} and {#payload} equality - def shallow_eql?(other) - return true if equal?(other) - other && - name == other.name && - payload == other.payload - end - - alias eql? == - - # @return [Fixnum] a hash for the vertex based upon its {#name} - def hash - name.hash - end - - # Is there a path from `self` to `other` following edges in the - # dependency graph? - # @return whether there is a path following edges within this {#graph} - def path_to?(other) - _path_to?(other) - end - - alias descendent? path_to? - - # @param [Vertex] other the vertex to check if there's a path to - # @param [Set] visited the vertices of {#graph} that have been visited - # @return [Boolean] whether there is a path to `other` from `self` - def _path_to?(other, visited = new_vertex_set) - return false unless visited.add?(self) - return true if equal?(other) - successors.any? { |v| v._path_to?(other, visited) } - end - protected :_path_to? - - # Is there a path from `other` to `self` following edges in the - # dependency graph? - # @return whether there is a path following edges within this {#graph} - def ancestor?(other) - other.path_to?(self) - end - - alias is_reachable_from? ancestor? - - def new_vertex_set - require 'set' - Set.new - end - private :new_vertex_set - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb deleted file mode 100644 index 4289902828..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::Molinillo - # An error that occurred during the resolution process - class ResolverError < StandardError; end - - # An error caused by searching for a dependency that is completely unknown, - # i.e. has no versions available whatsoever. - class NoSuchDependencyError < ResolverError - # @return [Object] the dependency that could not be found - attr_accessor :dependency - - # @return [Array] the specifications that depended upon {#dependency} - attr_accessor :required_by - - # Initializes a new error with the given missing dependency. - # @param [Object] dependency @see {#dependency} - # @param [Array] required_by @see {#required_by} - def initialize(dependency, required_by = []) - @dependency = dependency - @required_by = required_by.uniq - super() - end - - # The error message for the missing dependency, including the specifications - # that had this dependency. - def message - sources = required_by.map { |r| "`#{r}`" }.join(' and ') - message = "Unable to find a specification for `#{dependency}`" - message += " depended upon by #{sources}" unless sources.empty? - message - end - end - - # An error caused by attempting to fulfil a dependency that was circular - # - # @note This exception will be thrown if and only if a {Vertex} is added to a - # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an - # existing {DependencyGraph::Vertex} - class CircularDependencyError < ResolverError - # [Set] the dependencies responsible for causing the error - attr_reader :dependencies - - # Initializes a new error with the given circular vertices. - # @param [Array] vertices the vertices in the dependency - # that caused the error - def initialize(vertices) - super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" - @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set - end - end - - # An error caused by conflicts in version - class VersionConflict < ResolverError - # @return [{String => Resolution::Conflict}] the conflicts that caused - # resolution to fail - attr_reader :conflicts - - # @return [SpecificationProvider] the specification provider used during - # resolution - attr_reader :specification_provider - - # Initializes a new error with the given version conflicts. - # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} - # @param [SpecificationProvider] specification_provider see {#specification_provider} - def initialize(conflicts, specification_provider) - pairs = [] - conflicts.values.flat_map(&:requirements).each do |conflicting| - conflicting.each do |source, conflict_requirements| - conflict_requirements.each do |c| - pairs << [c, source] - end - end - end - - super "Unable to satisfy the following requirements:\n\n" \ - "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" - - @conflicts = conflicts - @specification_provider = specification_provider - end - - require_relative 'delegates/specification_provider' - include Delegates::SpecificationProvider - - # @return [String] An error message that includes requirement trees, - # which is much more detailed & customizable than the default message - # @param [Hash] opts the options to create a message with. - # @option opts [String] :solver_name The user-facing name of the solver - # @option opts [String] :possibility_type The generic name of a possibility - # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees - # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements - # @option opts [Proc] :additional_message_for_conflict A proc that appends additional - # messages for each conflict - # @option opts [Proc] :version_for_spec A proc that returns the version number for a - # possibility - def message_with_trees(opts = {}) - solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } - possibility_type = opts.delete(:possibility_type) { 'possibility named' } - reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } - printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } - additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } - version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } - incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do - proc do |name, _conflict| - %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) - end - end - - 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 << %(\n) - depth += 1 - end - t - end.join("\n") - - additional_message_for_conflict.call(o, name, conflict) - - o - end - end - - conflicts.sort.reduce(''.dup) do |o, (name, conflict)| - o << full_message_for_conflict.call(name, conflict) - end.strip - 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 86c249c404..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.8.0'.freeze -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb deleted file mode 100644 index 1067bf7439..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::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, - # using knowledge of their own model classes. - module SpecificationProvider - # Search for the specifications that match the given dependency. - # The specifications in the returned array will be considered in reverse - # order, so the latest version ought to be last. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `dependency` parameter. - # - # @param [Object] dependency - # @return [Array] the specifications that satisfy the given - # `dependency`. - def search_for(dependency) - [] - end - - # Returns the dependencies of `specification`. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `specification` parameter. - # - # @param [Object] specification - # @return [Array] the dependencies that are required by the given - # `specification`. - def dependencies_for(specification) - [] - end - - # Determines whether the given `requirement` is satisfied by the given - # `spec`, in the context of the current `activated` dependency graph. - # - # @param [Object] requirement - # @param [DependencyGraph] activated the current dependency graph in the - # resolution process. - # @param [Object] spec - # @return [Boolean] whether `requirement` is satisfied by `spec` in the - # context of the current `activated` dependency graph. - def requirement_satisfied_by?(requirement, activated, spec) - true - end - - # Determines whether two arrays of dependencies are equal, and thus can be - # grouped. - # - # @param [Array] dependencies - # @param [Array] other_dependencies - # @return [Boolean] whether `dependencies` and `other_dependencies` should - # be considered equal. - def dependencies_equal?(dependencies, other_dependencies) - dependencies == other_dependencies - end - - # Returns the name for the given `dependency`. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `dependency` parameter. - # - # @param [Object] dependency - # @return [String] the name for the given `dependency`. - def name_for(dependency) - dependency.to_s - end - - # @return [String] the name of the source of explicit dependencies, i.e. - # those passed to {Resolver#resolve} directly. - def name_for_explicit_dependency_source - 'user-specified dependency' - end - - # @return [String] the name of the source of 'locked' dependencies, i.e. - # those passed to {Resolver#resolve} directly as the `base` - def name_for_locking_dependency_source - 'Lockfile' - end - - # Sort dependencies so that the ones that are easiest to resolve are first. - # Easiest to resolve is (usually) defined by: - # 1) Is this dependency already activated? - # 2) How relaxed are the requirements? - # 3) Are there any conflicts for this dependency? - # 4) How many possibilities are there to satisfy this dependency? - # - # @param [Array] dependencies - # @param [DependencyGraph] activated the current dependency graph in the - # resolution process. - # @param [{String => Array}] conflicts - # @return [Array] a sorted copy of `dependencies`. - def sort_dependencies(dependencies, activated, conflicts) - dependencies.sort_by do |dependency| - name = name_for(dependency) - [ - activated.vertex_named(name).payload ? 0 : 1, - conflicts[name] ? 0 : 1, - ] - end - end - - # Returns whether this dependency, which has no possible matching - # specifications, can safely be ignored. - # - # @param [Object] dependency - # @return [Boolean] whether this dependency can safely be skipped. - def allow_missing?(dependency) - false - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb deleted file mode 100644 index a810fd519c..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::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. - # - # @return [IO] - def output - STDOUT - end - - # Called roughly every {#progress_rate}, this method should convey progress - # to the user. - # - # @return [void] - def indicate_progress - output.print '.' unless debug? - end - - # How often progress should be conveyed to the user via - # {#indicate_progress}, in seconds. A third of a second, by default. - # - # @return [Float] - def progress_rate - 0.33 - end - - # Called before resolution begins. - # - # @return [void] - def before_resolution - output.print 'Resolving dependencies...' - end - - # Called after resolution ends (either successfully or with an error). - # By default, prints a newline. - # - # @return [void] - def after_resolution - output.puts - end - - # Conveys debug information to the user. - # - # @param [Integer] depth the current depth of the resolution process. - # @return [void] - def debug(depth = 0) - if debug? - debug_info = yield - debug_info = debug_info.inspect unless debug_info.is_a?(String) - debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } - output.puts debug_info - end - end - - # Whether or not debug messages should be printed. - # By default, whether or not the `MOLINILLO_DEBUG` environment variable is - # set. - # - # @return [Boolean] - def debug? - return @debug_mode if defined?(@debug_mode) - @debug_mode = ENV['MOLINILLO_DEBUG'] - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb deleted file mode 100644 index 8b40e59e42..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb +++ /dev/null @@ -1,839 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::Molinillo - class Resolver - # A specific resolution from a given {Resolver} - class Resolution - # A conflict that the resolution process encountered - # @attr [Object] requirement the requirement that immediately led to the conflict - # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict - # @attr [Object, nil] existing the existing spec that was in conflict with - # the {#possibility} - # @attr [Object] possibility_set the set of specs that was unable to be - # activated due to a conflict. - # @attr [Object] locked_requirement the relevant locking requirement. - # @attr [Array>] requirement_trees the different requirement - # trees that led to every requirement for the conflicting name. - # @attr [{String=>Object}] activated_by_name the already-activated specs. - # @attr [Object] underlying_error an error that has occurred during resolution, and - # will be raised at the end of it if no resolution is found. - Conflict = Struct.new( - :requirement, - :requirements, - :existing, - :possibility_set, - :locked_requirement, - :requirement_trees, - :activated_by_name, - :underlying_error - ) - - class Conflict - # @return [Object] a spec that was unable to be activated due to a conflict - def possibility - possibility_set && possibility_set.latest_version - end - end - - # A collection of possibility states that share the same dependencies - # @attr [Array] dependencies the dependencies for this set of possibilities - # @attr [Array] possibilities the possibilities - PossibilitySet = Struct.new(:dependencies, :possibilities) - - class PossibilitySet - # String representation of the possibility set, for debugging - def to_s - "[#{possibilities.join(', ')}]" - end - - # @return [Object] most up-to-date dependency in the possibility set - def latest_version - possibilities.last - end - end - - # Details of the state to unwind to when a conflict occurs, and the cause of the unwind - # @attr [Integer] state_index the index of the state to unwind to - # @attr [Object] state_requirement the requirement of the state we're unwinding to - # @attr [Array] requirement_tree for the requirement we're relaxing - # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict - # @attr [Array] requirement_trees for the conflict - # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind - UnwindDetails = Struct.new( - :state_index, - :state_requirement, - :requirement_tree, - :conflicting_requirements, - :requirement_trees, - :requirements_unwound_to_instead - ) - - class UnwindDetails - include Comparable - - # We compare UnwindDetails when choosing which state to unwind to. If - # two options have the same state_index we prefer the one most - # removed from a requirement that caused the conflict. Both options - # would unwind to the same state, but a `grandparent` option will - # filter out fewer of its possibilities after doing so - where a state - # is both a `parent` and a `grandparent` to requirements that have - # caused a conflict this is the correct behaviour. - # @param [UnwindDetail] other UnwindDetail to be compared - # @return [Integer] integer specifying ordering - def <=>(other) - if state_index > other.state_index - 1 - elsif state_index == other.state_index - reversed_requirement_tree_index <=> other.reversed_requirement_tree_index - else - -1 - end - end - - # @return [Integer] index of state requirement in reversed requirement tree - # (the conflicting requirement itself will be at position 0) - def reversed_requirement_tree_index - @reversed_requirement_tree_index ||= - if state_requirement - requirement_tree.reverse.index(state_requirement) - else - 999_999 - end - end - - # @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 - # any of the other conflicting requirements (or we would have - # circularity) - def unwinding_to_primary_requirement? - requirement_tree.last == state_requirement - end - - # @return [Array] array of sub-dependencies to avoid when choosing a - # new possibility for the state we've unwound to. Only relevant for - # non-primary unwinds - def sub_dependencies_to_avoid - @requirements_to_avoid ||= - requirement_trees.map do |tree| - index = tree.index(state_requirement) - tree[index + 1] if index - end.compact - end - - # @return [Array] array of all the requirements that led to the need for - # this unwind - def all_requirements - @all_requirements ||= requirement_trees.flatten(1) - end - end - - # @return [SpecificationProvider] the provider that knows about - # dependencies, requirements, specifications, versions, etc. - attr_reader :specification_provider - - # @return [UI] the UI that knows how to communicate feedback about the - # resolution process back to the user - attr_reader :resolver_ui - - # @return [DependencyGraph] the base dependency graph to which - # dependencies should be 'locked' - attr_reader :base - - # @return [Array] the dependencies that were explicitly required - attr_reader :original_requested - - # Initializes a new resolution. - # @param [SpecificationProvider] specification_provider - # see {#specification_provider} - # @param [UI] resolver_ui see {#resolver_ui} - # @param [Array] requested see {#original_requested} - # @param [DependencyGraph] base see {#base} - def initialize(specification_provider, resolver_ui, requested, base) - @specification_provider = specification_provider - @resolver_ui = resolver_ui - @original_requested = requested - @base = base - @states = [] - @iteration_counter = 0 - @parents_of = Hash.new { |h, k| h[k] = [] } - end - - # Resolves the {#original_requested} dependencies into a full dependency - # graph - # @raise [ResolverError] if successful resolution is impossible - # @return [DependencyGraph] the dependency graph of successfully resolved - # dependencies - def resolve - start_resolution - - while state - break if !state.requirement && state.requirements.empty? - indicate_progress - if state.respond_to?(:pop_possibility_state) # DependencyState - debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } - state.pop_possibility_state.tap do |s| - if s - states.push(s) - activated.tag(s) - end - end - end - process_topmost_state - end - - resolve_activated_specs - ensure - end_resolution - end - - # @return [Integer] the number of resolver iterations in between calls to - # {#resolver_ui}'s {UI#indicate_progress} method - attr_accessor :iteration_rate - private :iteration_rate - - # @return [Time] the time at which resolution began - attr_accessor :started_at - private :started_at - - # @return [Array] the stack of states for the resolution - attr_accessor :states - private :states - - private - - # Sets up the resolution process - # @return [void] - def start_resolution - @started_at = Time.now - - push_initial_state - - debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } - resolver_ui.before_resolution - end - - def resolve_activated_specs - activated.vertices.each do |_, vertex| - next unless vertex.payload - - latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| - vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } - end - - activated.set_payload(vertex.name, latest_version) - end - activated.freeze - end - - # Ends the resolution process - # @return [void] - def end_resolution - resolver_ui.after_resolution - debug do - "Finished resolution (#{@iteration_counter} steps) " \ - "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" - end - debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state - debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state - end - - require_relative 'state' - require_relative 'modules/specification_provider' - - require_relative 'delegates/resolution_state' - require_relative 'delegates/specification_provider' - - include Gem::Resolver::Molinillo::Delegates::ResolutionState - include Gem::Resolver::Molinillo::Delegates::SpecificationProvider - - # Processes the topmost available {RequirementState} on the stack - # @return [void] - def process_topmost_state - if possibility - attempt_to_activate - else - create_conflict - unwind_for_conflict - end - rescue CircularDependencyError => underlying_error - create_conflict(underlying_error) - unwind_for_conflict - end - - # @return [Object] the current possibility that the resolution is trying - # to activate - def possibility - possibilities.last - end - - # @return [RequirementState] the current state the resolution is - # operating upon - def state - states.last - end - - # Creates and pushes the initial state for the resolution, based upon the - # {#requested} dependencies - # @return [void] - def push_initial_state - graph = DependencyGraph.new.tap do |dg| - original_requested.each do |requested| - vertex = dg.add_vertex(name_for(requested), nil, true) - vertex.explicit_requirements << requested - end - dg.tag(:initial_state) - end - - push_state_for_requirements(original_requested, true, graph) - end - - # Unwinds the states stack because a conflict has been encountered - # @return [void] - def unwind_for_conflict - details_for_unwind = build_details_for_unwind - unwind_options = unused_unwind_options - debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } - conflicts.tap do |c| - sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) - raise_error_unless_state(c) - activated.rewind_to(sliced_states.first || :initial_state) if sliced_states - state.conflicts = c - state.unused_unwind_options = unwind_options - filter_possibilities_after_unwind(details_for_unwind) - index = states.size - 1 - @parents_of.each { |_, a| a.reject! { |i| i >= index } } - state.unused_unwind_options.reject! { |uw| uw.state_index >= index } - end - end - - # Raises a VersionConflict error, or any underlying error, if there is no - # current state - # @return [void] - def raise_error_unless_state(conflicts) - return if state - - error = conflicts.values.map(&:underlying_error).compact.first - raise error || VersionConflict.new(conflicts, specification_provider) - end - - # @return [UnwindDetails] Details of the nearest index to which we could unwind - def build_details_for_unwind - # Get the possible unwinds for the current conflict - current_conflict = conflicts[name] - binding_requirements = binding_requirements_for_conflict(current_conflict) - unwind_details = unwind_options_for_requirements(binding_requirements) - - last_detail_for_current_unwind = unwind_details.sort.last - current_detail = last_detail_for_current_unwind - - # Look for past conflicts that could be unwound to affect the - # requirement tree for the current conflict - all_reqs = last_detail_for_current_unwind.all_requirements - all_reqs_size = all_reqs.size - relevant_unused_unwinds = unused_unwind_options.select do |alternative| - diff_reqs = all_reqs - alternative.requirements_unwound_to_instead - next if diff_reqs.size == all_reqs_size - # Find the highest index unwind whilst looping through - current_detail = alternative if alternative > current_detail - alternative - end - - # Add the current unwind options to the `unused_unwind_options` array. - # The "used" option will be filtered out during `unwind_for_conflict`. - state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } - - # Update the requirements_unwound_to_instead on any relevant unused unwinds - relevant_unused_unwinds.each do |d| - (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! - end - unwind_details.each do |d| - (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! - end - - current_detail - end - - # @param [Array] binding_requirements array of requirements that combine to create a conflict - # @return [Array] array of UnwindDetails that have a chance - # of resolving the passed requirements - def unwind_options_for_requirements(binding_requirements) - unwind_details = [] - - trees = [] - binding_requirements.reverse_each do |r| - partial_tree = [r] - trees << partial_tree - unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) - - # If this requirement has alternative possibilities, check if any would - # satisfy the other requirements that created this conflict - requirement_state = find_state_for(r) - if conflict_fixing_possibilities?(requirement_state, binding_requirements) - unwind_details << UnwindDetails.new( - states.index(requirement_state), - r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - - # Next, look at the parent of this requirement, and check if the requirement - # could have been avoided if an alternative PossibilitySet had been chosen - parent_r = parent_of(r) - next if parent_r.nil? - partial_tree.unshift(parent_r) - requirement_state = find_state_for(parent_r) - if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } - unwind_details << UnwindDetails.new( - states.index(requirement_state), - parent_r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - - # Finally, look at the grandparent and up of this requirement, looking - # for any possibilities that wouldn't create their parent requirement - grandparent_r = parent_of(parent_r) - until grandparent_r.nil? - partial_tree.unshift(grandparent_r) - requirement_state = find_state_for(grandparent_r) - if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } - unwind_details << UnwindDetails.new( - states.index(requirement_state), - grandparent_r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - parent_r = grandparent_r - grandparent_r = parent_of(parent_r) - end - end - - unwind_details - end - - # @param [DependencyState] state - # @param [Array] binding_requirements array of requirements - # @return [Boolean] whether or not the given state has any possibilities - # that could satisfy the given requirements - def conflict_fixing_possibilities?(state, binding_requirements) - return false unless state - - state.possibilities.any? do |possibility_set| - possibility_set.possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, binding_requirements) - end - end - end - - # Filter's a state's possibilities to remove any that would not fix the - # conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just - # unwound from - # @return [void] - def filter_possibilities_after_unwind(unwind_details) - return unless state && !state.possibilities.empty? - - if unwind_details.unwinding_to_primary_requirement? - filter_possibilities_for_primary_unwind(unwind_details) - else - filter_possibilities_for_parent_unwind(unwind_details) - end - end - - # Filter's a state's possibilities to remove any that would not satisfy - # the requirements in the conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just unwound from - # @return [void] - def filter_possibilities_for_primary_unwind(unwind_details) - unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } - unwinds_to_state << unwind_details - unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) - - state.possibilities.reject! do |possibility_set| - possibility_set.possibilities.none? do |poss| - unwind_requirement_sets.any? do |requirements| - possibility_satisfies_requirements?(poss, requirements) - end - end - end - end - - # @param [Object] possibility a single possibility - # @param [Array] requirements an array of requirements - # @return [Boolean] whether the possibility satisfies all of the - # given requirements - def possibility_satisfies_requirements?(possibility, requirements) - name = name_for(possibility) - - activated.tag(:swap) - activated.set_payload(name, possibility) if activated.vertex_named(name) - satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } - activated.rewind_to(:swap) - - satisfied - end - - # Filter's a state's possibilities to remove any that would (eventually) - # create a requirement in the conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just unwound from - # @return [void] - def filter_possibilities_for_parent_unwind(unwind_details) - unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } - unwinds_to_state << unwind_details - - primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq - parent_unwinds = unwinds_to_state.uniq - primary_unwinds - - allowed_possibility_sets = primary_unwinds.flat_map do |unwind| - states[unwind.state_index].possibilities.select do |possibility_set| - possibility_set.possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) - end - end - end - - requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) - - state.possibilities.reject! do |possibility_set| - !allowed_possibility_sets.include?(possibility_set) && - (requirements_to_avoid - possibility_set.dependencies).empty? - end - end - - # @param [Conflict] conflict - # @return [Array] minimal array of requirements that would cause the passed - # conflict to occur. - def binding_requirements_for_conflict(conflict) - return [conflict.requirement] if conflict.possibility.nil? - - possible_binding_requirements = conflict.requirements.values.flatten(1).uniq - - # When there's a `CircularDependency` error the conflicting requirement - # (the one causing the circular) won't be `conflict.requirement` - # (which won't be for the right state, because we won't have created it, - # because it's circular). - # We need to make sure we have that requirement in the conflict's list, - # otherwise we won't be able to unwind properly, so we just return all - # the requirements for the conflict. - return possible_binding_requirements if conflict.underlying_error - - possibilities = search_for(conflict.requirement) - - # If all the requirements together don't filter out all possibilities, - # then the only two requirements we need to consider are the initial one - # (where the dependency's version was first chosen) and the last - if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) - return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact - end - - # Loop through the possible binding requirements, removing each one - # that doesn't bind. Use a `reverse_each` as we want the earliest set of - # binding requirements, and don't use `reject!` as we wish to refine the - # array *on each iteration*. - binding_requirements = possible_binding_requirements.dup - possible_binding_requirements.reverse_each do |req| - next if req == conflict.requirement - unless binding_requirement_in_set?(req, binding_requirements, possibilities) - binding_requirements -= [req] - end - end - - binding_requirements - end - - # @param [Object] requirement we wish to check - # @param [Array] possible_binding_requirements array of requirements - # @param [Array] possibilities array of possibilities the requirements will be used to filter - # @return [Boolean] whether or not the given requirement is required to filter - # out all elements of the array of possibilities. - def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) - possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) - end - end - - # @param [Object] requirement - # @return [Object] the requirement that led to `requirement` being added - # to the list of requirements. - def parent_of(requirement) - return unless requirement - return unless index = @parents_of[requirement].last - return unless parent_state = @states[index] - parent_state.requirement - end - - # @param [String] name - # @return [Object] the requirement that led to a version of a possibility - # with the given name being activated. - def requirement_for_existing_name(name) - return nil unless vertex = activated.vertex_named(name) - return nil unless vertex.payload - states.find { |s| s.name == name }.requirement - end - - # @param [Object] requirement - # @return [ResolutionState] the state whose `requirement` is the given - # `requirement`. - def find_state_for(requirement) - return nil unless requirement - states.find { |i| requirement == i.requirement } - end - - # @param [Object] underlying_error - # @return [Conflict] a {Conflict} that reflects the failure to activate - # the {#possibility} in conjunction with the current {#state} - def create_conflict(underlying_error = nil) - vertex = activated.vertex_named(name) - locked_requirement = locked_requirement_named(name) - - requirements = {} - unless vertex.explicit_requirements.empty? - requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements - end - requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement - vertex.incoming_edges.each do |edge| - (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) - end - - activated_by_name = {} - activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } - conflicts[name] = Conflict.new( - requirement, - requirements, - vertex.payload && vertex.payload.latest_version, - possibility, - locked_requirement, - requirement_trees, - activated_by_name, - underlying_error - ) - end - - # @return [Array>] The different requirement - # trees that led to every requirement for the current spec. - def requirement_trees - vertex = activated.vertex_named(name) - vertex.requirements.map { |r| requirement_tree_for(r) } - end - - # @param [Object] requirement - # @return [Array] the list of requirements that led to - # `requirement` being required. - def requirement_tree_for(requirement) - tree = [] - while requirement - tree.unshift(requirement) - requirement = parent_of(requirement) - end - tree - end - - # Indicates progress roughly once every second - # @return [void] - def indicate_progress - @iteration_counter += 1 - @progress_rate ||= resolver_ui.progress_rate - if iteration_rate.nil? - if Time.now - started_at >= @progress_rate - self.iteration_rate = @iteration_counter - end - end - - if iteration_rate && (@iteration_counter % iteration_rate) == 0 - resolver_ui.indicate_progress - end - end - - # Calls the {#resolver_ui}'s {UI#debug} method - # @param [Integer] depth the depth of the {#states} stack - # @param [Proc] block a block that yields a {#to_s} - # @return [void] - def debug(depth = 0, &block) - resolver_ui.debug(depth, &block) - end - - # Attempts to activate the current {#possibility} - # @return [void] - def attempt_to_activate - debug(depth) { 'Attempting to activate ' + possibility.to_s } - existing_vertex = activated.vertex_named(name) - if existing_vertex.payload - debug(depth) { "Found existing spec (#{existing_vertex.payload})" } - attempt_to_filter_existing_spec(existing_vertex) - else - latest = possibility.latest_version - possibility.possibilities.select! do |possibility| - requirement_satisfied_by?(requirement, activated, possibility) - end - if possibility.latest_version.nil? - # ensure there's a possibility for better error messages - possibility.possibilities << latest if latest - create_conflict - unwind_for_conflict - else - activate_new_spec - end - end - end - - # Attempts to update the existing vertex's `PossibilitySet` with a filtered version - # @return [void] - def attempt_to_filter_existing_spec(vertex) - filtered_set = filtered_possibility_set(vertex) - if !filtered_set.possibilities.empty? - activated.set_payload(name, filtered_set) - new_requirements = requirements.dup - push_state_for_requirements(new_requirements, false) - else - create_conflict - debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } - unwind_for_conflict - end - end - - # Generates a filtered version of the existing vertex's `PossibilitySet` using the - # current state's `requirement` - # @param [Object] vertex existing vertex - # @return [PossibilitySet] filtered possibility set - def filtered_possibility_set(vertex) - PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) - end - - # @param [String] requirement_name the spec name to search for - # @return [Object] the locked spec named `requirement_name`, if one - # is found on {#base} - def locked_requirement_named(requirement_name) - vertex = base.vertex_named(requirement_name) - vertex && vertex.payload - end - - # Add the current {#possibility} to the dependency graph of the current - # {#state} - # @return [void] - def activate_new_spec - conflicts.delete(name) - debug(depth) { "Activated #{name} at #{possibility}" } - activated.set_payload(name, possibility) - require_nested_dependencies_for(possibility) - end - - # Requires the dependencies that the recently activated spec has - # @param [Object] possibility_set the PossibilitySet that has just been - # activated - # @return [void] - def require_nested_dependencies_for(possibility_set) - nested_dependencies = dependencies_for(possibility_set.latest_version) - debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } - nested_dependencies.each do |d| - activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) - parent_index = states.size - 1 - parents = @parents_of[d] - parents << parent_index if parents.empty? - end - - push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) - end - - # Pushes a new {DependencyState} that encapsulates both existing and new - # requirements - # @param [Array] new_requirements - # @param [Boolean] requires_sort - # @param [Object] new_activated - # @return [void] - def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) - new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort - new_requirement = nil - loop do - new_requirement = new_requirements.shift - break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } - end - new_name = new_requirement ? name_for(new_requirement) : ''.freeze - possibilities = possibilities_for_requirement(new_requirement) - handle_missing_or_push_dependency_state DependencyState.new( - new_name, new_requirements, new_activated, - new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup - ) - end - - # Checks a proposed requirement with any existing locked requirement - # before generating an array of possibilities for it. - # @param [Object] requirement the proposed requirement - # @param [Object] activated - # @return [Array] possibilities - def possibilities_for_requirement(requirement, activated = self.activated) - return [] unless requirement - if locked_requirement_named(name_for(requirement)) - return locked_requirement_possibility_set(requirement, activated) - end - - group_possibilities(search_for(requirement)) - end - - # @param [Object] requirement the proposed requirement - # @param [Object] activated - # @return [Array] possibility set containing only the locked requirement, if any - def locked_requirement_possibility_set(requirement, activated = self.activated) - all_possibilities = search_for(requirement) - locked_requirement = locked_requirement_named(name_for(requirement)) - - # Longwinded way to build a possibilities array with either the locked - # requirement or nothing in it. Required, since the API for - # locked_requirement isn't guaranteed. - locked_possibilities = all_possibilities.select do |possibility| - requirement_satisfied_by?(locked_requirement, activated, possibility) - end - - group_possibilities(locked_possibilities) - end - - # Build an array of PossibilitySets, with each element representing a group of - # dependency versions that all have the same sub-dependency version constraints - # and are contiguous. - # @param [Array] possibilities an array of possibilities - # @return [Array] an array of possibility sets - def group_possibilities(possibilities) - possibility_sets = [] - current_possibility_set = nil - - possibilities.reverse_each do |possibility| - dependencies = dependencies_for(possibility) - if current_possibility_set && dependencies_equal?(current_possibility_set.dependencies, dependencies) - current_possibility_set.possibilities.unshift(possibility) - else - possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) - current_possibility_set = possibility_sets.first - end - end - - possibility_sets - end - - # Pushes a new {DependencyState}. - # If the {#specification_provider} says to - # {SpecificationProvider#allow_missing?} that particular requirement, and - # there are no possibilities for that requirement, then `state` is not - # pushed, and the vertex in {#activated} is removed, and we continue - # resolving the remaining requirements. - # @param [DependencyState] state - # @return [void] - def handle_missing_or_push_dependency_state(state) - if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) - state.activated.detach_vertex_named(state.name) - push_state_for_requirements(state.requirements.dup, false, state.activated) - else - states.push(state).tap { activated.tag(state) } - end - end - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb deleted file mode 100644 index d43121f8ca..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative 'dependency_graph' - -module Gem::Resolver::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} - # - # - class Resolver - require_relative 'resolution' - - # @return [SpecificationProvider] the specification provider used - # in the resolution process - attr_reader :specification_provider - - # @return [UI] the UI module used to communicate back to the user - # during the resolution process - attr_reader :resolver_ui - - # Initializes a new resolver. - # @param [SpecificationProvider] specification_provider - # see {#specification_provider} - # @param [UI] resolver_ui - # see {#resolver_ui} - def initialize(specification_provider, resolver_ui) - @specification_provider = specification_provider - @resolver_ui = resolver_ui - end - - # Resolves the requested dependencies into a {DependencyGraph}, - # locking to the base dependency graph (if specified) - # @param [Array] requested an array of 'requested' dependencies that the - # {#specification_provider} can understand - # @param [DependencyGraph,nil] base the base dependency graph to which - # dependencies should be 'locked' - def resolve(requested, base = DependencyGraph.new) - Resolution.new(specification_provider, - resolver_ui, - requested, - base). - resolve - end - end -end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb deleted file mode 100644 index 6e7c715fce..0000000000 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -module Gem::Resolver::Molinillo - # A state that a {Resolution} can be in - # @attr [String] name the name of the current requirement - # @attr [Array] requirements currently unsatisfied requirements - # @attr [DependencyGraph] activated the graph of activated dependencies - # @attr [Object] requirement the current requirement - # @attr [Object] possibilities the possibilities to satisfy the current requirement - # @attr [Integer] depth the depth of the resolution - # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name - # @attr [Array] unused_unwind_options unwinds for previous conflicts that weren't explored - ResolutionState = Struct.new( - :name, - :requirements, - :activated, - :requirement, - :possibilities, - :depth, - :conflicts, - :unused_unwind_options - ) - - class ResolutionState - # Returns an empty resolution state - # @return [ResolutionState] an empty state - def self.empty - new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) - end - end - - # A state that encapsulates a set of {#requirements} with an {Array} of - # possibilities - class DependencyState < ResolutionState - # Removes a possibility from `self` - # @return [PossibilityState] a state with a single possibility, - # the possibility that was removed from `self` - def pop_possibility_state - PossibilityState.new( - name, - requirements.dup, - activated, - requirement, - [possibilities.pop], - depth + 1, - conflicts.dup, - unused_unwind_options.dup - ).tap do |state| - state.activated.tag(state) - end - end - end - - # A state that encapsulates a single possibility to fulfill the given - # {#requirement} - class PossibilityState < ResolutionState - end -end diff --git a/lib/rubygems/resolver/spec_specification.rb b/lib/rubygems/resolver/spec_specification.rb index 79a34d8063..00ef9fdba0 100644 --- a/lib/rubygems/resolver/spec_specification.rb +++ b/lib/rubygems/resolver/spec_specification.rb @@ -66,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/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index 53d49ec432..7c95a9d4f5 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -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 @@ -140,7 +140,7 @@ class Gem::S3URISigner end def ec2_metadata_credentials_json - require_relative "net/http" + require_relative "vendored_net_http" require_relative "request" require_relative "request/connection_pools" require "json" @@ -152,7 +152,7 @@ class Gem::S3URISigner end def ec2_metadata_request(url) - uri = URI(url) + uri = Gem::URI(url) @request_pool ||= create_request_pool(uri) request = Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, @request_pool) response = request.fetch diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb index dba3cfb16d..6a02a48230 100644 --- a/lib/rubygems/safe_yaml.rb +++ b/lib/rubygems/safe_yaml.rb @@ -25,8 +25,17 @@ module Gem runtime ].freeze + @aliases_enabled = true + def self.aliases_enabled=(value) # :nodoc: + @aliases_enabled = !!value + end + + def self.aliases_enabled? # :nodoc: + @aliases_enabled + end + def self.safe_load(input) - ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: true) + ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled) end def self.load(input) diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index 1e8e57b5a9..69ba87b07f 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -323,7 +323,7 @@ require_relative "openssl" # == Original author # # Paul Duncan -# http://pablotron.org/ +# https://pablotron.org/ module Gem::Security ## diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index a0d03312b9..bda63c6844 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -221,14 +221,14 @@ 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_relative "../openssl" normalized = if @repository.match?(%r{^\w+://(\w+@)?}) - uri = URI(@repository).normalize.to_s.sub %r{/$},"" + uri = Gem::URI(@repository).normalize.to_s.sub %r{/$},"" uri.sub(/\A(\w+)/) { $1.downcase } else @repository diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb index 9e8a9e16ef..33db64fbc1 100644 --- a/lib/rubygems/source_list.rb +++ b/lib/rubygems/source_list.rb @@ -44,7 +44,7 @@ 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) diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 169002d7c7..61ea3fcfdc 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -341,7 +341,7 @@ class Gem::Specification < Gem::BasicSpecification # 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 http://choosealicense.com/. + # GitHub also provides a license picker at https://choosealicense.com/. # # You can also use a custom license file along with your gemspec and specify # a LicenseRef-, where idstring is the name of the file containing @@ -1003,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 @@ -1022,8 +1020,6 @@ 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 @@ -2079,7 +2075,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| @@ -2101,6 +2098,9 @@ 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 diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index 6655825287..516c26f53c 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -7,7 +7,7 @@ class Gem::SpecificationPolicy VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc: - SPECIAL_CHARACTERS = /\A[#{Regexp.escape('.-_')}]+/ # :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} # :nodoc: @@ -103,6 +103,8 @@ class Gem::SpecificationPolicy validate_dependencies + validate_required_ruby_version + validate_extensions validate_removed_attributes @@ -227,6 +229,12 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: 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. # @@ -427,13 +435,13 @@ or set it to nil if you don't want to specify a license. # Make sure a homepage is valid HTTP/HTTPS URI if homepage && !homepage.empty? - require "uri" + 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 @@ -497,10 +505,10 @@ You have specified rust based extension, but Cargo.lock is not part of the gem f 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 diff --git a/lib/rubygems/timeout.rb b/lib/rubygems/timeout.rb deleted file mode 100644 index 33c54a2aa4..0000000000 --- a/lib/rubygems/timeout.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require_relative "timeout/lib/timeout" diff --git a/lib/rubygems/timeout/.document b/lib/rubygems/timeout/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/timeout/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/timeout/lib/timeout.rb b/lib/rubygems/timeout/lib/timeout.rb deleted file mode 100644 index 24d877b994..0000000000 --- a/lib/rubygems/timeout/lib/timeout.rb +++ /dev/null @@ -1,199 +0,0 @@ -# frozen_string_literal: true -# Timeout long-running blocks -# -# == Synopsis -# -# require 'rubygems/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 include - # Gem::Timeout 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/tsort.rb b/lib/rubygems/tsort.rb deleted file mode 100644 index 60ebe22e81..0000000000 --- a/lib/rubygems/tsort.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require_relative "tsort/lib/tsort" diff --git a/lib/rubygems/tsort/.document b/lib/rubygems/tsort/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/tsort/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/tsort/lib/tsort.rb b/lib/rubygems/tsort/lib/tsort.rb deleted file mode 100644 index 05f3683d38..0000000000 --- a/lib/rubygems/tsort/lib/tsort.rb +++ /dev/null @@ -1,455 +0,0 @@ -# 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/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/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", -# SIAM Journal on Computing, 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. - # obj.tsort_each is similar to obj.tsort.each, 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. - # obj.each_strongly_connected_component is similar to - # obj.strongly_connected_components.each, 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/uri.rb b/lib/rubygems/uri.rb index 4b5d035aa0..a44aaceba5 100644 --- a/lib/rubygems/uri.rb +++ b/lib/rubygems/uri.rb @@ -16,9 +16,9 @@ class Gem::Uri # Parses uri, raising if it's invalid def self.parse!(uri) - require "uri" + require_relative "vendor/uri/lib/uri" - raise URI::InvalidURIError unless uri + raise Gem::URI::InvalidURIError unless uri return uri unless uri.is_a?(String) @@ -28,9 +28,9 @@ class Gem::Uri # 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)) + Gem::URI.parse(uri) + rescue Gem::URI::InvalidURIError + Gem::URI.parse(Gem::URI::DEFAULT_PARSER.escape(uri)) end end @@ -39,7 +39,7 @@ class Gem::Uri def self.parse(uri) parse!(uri) - rescue URI::InvalidURIError + rescue Gem::URI::InvalidURIError uri end diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 1815f6af6f..51f9c2029f 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -105,7 +105,7 @@ module Gem::Util 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) diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb index 47dbc17304..f3c7201639 100644 --- a/lib/rubygems/util/licenses.rb +++ b/lib/rubygems/util/licenses.rb @@ -28,6 +28,7 @@ class Gem::Licenses AGPL-3.0-or-later AMDPLPA AML + AML-glslang AMPAS ANTLR-PD ANTLR-PD-fallback @@ -42,6 +43,7 @@ class Gem::Licenses Abstyles AdaCore-doc Adobe-2006 + Adobe-Display-PostScript Adobe-Glyph Adobe-Utopia Afmparse @@ -57,6 +59,7 @@ class Gem::Licenses Artistic-2.0 BSD-1-Clause BSD-2-Clause + BSD-2-Clause-Darwin BSD-2-Clause-Patent BSD-2-Clause-Views BSD-3-Clause @@ -71,6 +74,7 @@ class Gem::Licenses 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 @@ -82,7 +86,9 @@ class Gem::Licenses BSD-Inferno-Nettverk BSD-Protection BSD-Source-Code + BSD-Source-beginning-file BSD-Systemics + BSD-Systemics-W3Works BSL-1.0 BUSL-1.1 Baekmuk @@ -96,6 +102,7 @@ class Gem::Licenses BlueOak-1.0.0 Boehm-GC Borceux + Brian-Gladman-2-Clause Brian-Gladman-3-Clause C-UDA-1.0 CAL-1.0 @@ -107,6 +114,7 @@ class Gem::Licenses 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 @@ -172,6 +180,7 @@ class Gem::Licenses CERN-OHL-W-2.0 CFITSIO CMU-Mach + CMU-Mach-nodoc CNRI-Jython CNRI-Python CNRI-Python-GPL-Compatible @@ -181,6 +190,7 @@ class Gem::Licenses CPOL-1.02 CUA-OPL-1.0 Caldera + Caldera-no-preamble ClArtistic Clips Community-Spec-1.0 @@ -191,10 +201,12 @@ class Gem::Licenses 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 @@ -215,6 +227,7 @@ class Gem::Licenses FBM FDK-AAC FSFAP + FSFAP-no-warranty-disclaimer FSFUL FSFULLR FSFULLRWD @@ -225,6 +238,7 @@ class Gem::Licenses FreeBSD-DOC FreeImage Furuseth + GCR-docs GD GFDL-1.1-invariants-only GFDL-1.1-invariants-or-later @@ -260,6 +274,10 @@ class Gem::Licenses HP-1989 HPND HPND-DEC + HPND-Fenneberg-Livingston + HPND-INRIA-IMAG + HPND-Kevlin-Henney + HPND-MIT-disclaimer HPND-Markus-Kuhn HPND-Pbmplus HPND-UC @@ -267,6 +285,7 @@ class Gem::Licenses 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 @@ -281,6 +300,7 @@ class Gem::Licenses IPA IPL-1.0 ISC + ISC-Veillard ImageMagick Imlib2 Info-ZIP @@ -306,6 +326,7 @@ class Gem::Licenses LGPL-3.0-or-later LGPLLR LOOP + LPD-document LPL-1.0 LPL-1.02 LPPL-1.0 @@ -350,6 +371,8 @@ class Gem::Licenses MS-PL MS-RL MTLL + Mackerras-3-Clause + Mackerras-3-Clause-acknowledgment MakeIndex Martin-Birgmeier McPhee-slideshow @@ -434,6 +457,8 @@ class Gem::Licenses OSL-3.0 OpenPBS-2.3 OpenSSL + OpenSSL-standalone + OpenVision PADL PDDL-1.0 PHP-3.0 @@ -441,6 +466,7 @@ class Gem::Licenses PSF-2.0 Parity-6.0.0 Parity-7.0.0 + Pixar Plexus PolyForm-Noncommercial-1.0.0 PolyForm-Small-Business-1.0.0 @@ -459,6 +485,7 @@ class Gem::Licenses Rdisc Ruby SAX-PD + SAX-PD-2.0 SCEA SGI-B-1.0 SGI-B-1.1 @@ -476,6 +503,7 @@ class Gem::Licenses SPL-1.0 SSH-OpenSSH SSH-short + SSLeay-standalone SSPL-1.0 SWL Saxpath @@ -489,11 +517,13 @@ class Gem::Licenses Spencer-94 Spencer-99 SugarCRM-1.1.3 + Sun-PPP SunPro Symlinks TAPR-OHL-1.0 TCL TCP-wrappers + TGPPL-1.0 TMate TORQUE-1.1 TOSL @@ -506,8 +536,10 @@ class Gem::Licenses TermReadKey UCAR UCL-1.0 + UMich-Merit UPL-1.0 URT-RLE + Unicode-3.0 Unicode-DFS-2015 Unicode-DFS-2016 Unicode-TOU @@ -542,6 +574,7 @@ class Gem::Licenses Zimbra-1.3 Zimbra-1.4 Zlib + bcrypt-Solar-Designer blessing bzip2-1.0.6 check-cvs @@ -557,6 +590,8 @@ class Gem::Licenses fwlw gSOAP-1.3b gnuplot + gtkbook + hdparm iMatix libpng-2.0 libselinux-1.0 @@ -564,6 +599,7 @@ class Gem::Licenses libutil-David-Nugent lsof magaz + mailprio metamail mpi-permissive mpich2 @@ -572,12 +608,15 @@ class Gem::Licenses psfrag psutils python-ldap + radvd snprintf + softSurfer ssh-keyscan swrule ulem w3m xinetd + xkeyboard-config-Zinoviev xlock xpp zlib-acknowledgement @@ -626,6 +665,7 @@ class Gem::Licenses 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 @@ -638,6 +678,7 @@ class Gem::Licenses 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 @@ -645,6 +686,7 @@ class Gem::Licenses GPL-CC-1.0 GStreamer-exception-2005 GStreamer-exception-2008 + Gmsh-exception KiCad-libraries-exception LGPL-3.0-linking-exception LLGPL @@ -671,6 +713,7 @@ class Gem::Licenses WxWindows-exception-3.1 cryptsetup-OpenSSL-exception eCos-exception-2.0 + fmt-exception freertos-exception-2.0 gnu-javamail-exception i2p-gpl-java-exception 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/vendor/molinillo/lib/molinillo.rb b/lib/rubygems/vendor/molinillo/lib/molinillo.rb new file mode 100644 index 0000000000..dd5600c9e3 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative 'molinillo/gem_metadata' +require_relative 'molinillo/errors' +require_relative 'molinillo/resolver' +require_relative 'molinillo/modules/ui' +require_relative 'molinillo/modules/specification_provider' + +# 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/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb new file mode 100644 index 0000000000..8417721537 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module Gem::Molinillo + module Delegates + # Delegates all {Gem::Molinillo::SpecificationProvider} methods to a + # `#specification_provider` property. + module SpecificationProvider + # (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::Molinillo::SpecificationProvider#dependencies_for) + def dependencies_for(specification) + with_no_such_dependency_error_handling do + specification_provider.dependencies_for(specification) + end + end + + # (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::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::Molinillo::SpecificationProvider#name_for) + def name_for(dependency) + with_no_such_dependency_error_handling do + specification_provider.name_for(dependency) + end + end + + # (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::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::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::Molinillo::SpecificationProvider#allow_missing?) + def allow_missing?(dependency) + with_no_such_dependency_error_handling do + specification_provider.allow_missing?(dependency) + end + end + + private + + # Ensures any raised {NoSuchDependencyError} has its + # {NoSuchDependencyError#required_by} set. + # @yield + def with_no_such_dependency_error_handling + yield + rescue NoSuchDependencyError => error + if state + vertex = activated.vertex_named(name_for(error.dependency)) + error.required_by += vertex.incoming_edges.map { |e| e.origin.name } + error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? + end + raise + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb new file mode 100644 index 0000000000..2dbbc589dc --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require_relative '../../../../vendored_tsort' + +require_relative 'dependency_graph/log' +require_relative 'dependency_graph/vertex' + +module Gem::Molinillo + # A directed acyclic graph that is tuned to hold named dependencies + class DependencyGraph + include Enumerable + + # Enumerates through the vertices of the graph. + # @return [Array] The graph's vertices. + def each + return vertices.values.each unless block_given? + vertices.values.each { |v| yield v } + end + + include Gem::TSort + + # @!visibility private + alias tsort_each_node each + + # @!visibility private + def tsort_each_child(vertex, &block) + vertex.successors.each(&block) + end + + # Topologically sorts the given vertices. + # @param [Enumerable] vertices the vertices to be sorted, which must + # all belong to the same graph. + # @return [Array] The sorted vertices. + def self.tsort(vertices) + Gem::TSort.tsort( + lambda { |b| vertices.each(&b) }, + lambda { |v, &b| (v.successors & vertices).each(&b) } + ) + end + + # A directed edge of a {DependencyGraph} + # @attr [Vertex] origin The origin of the directed edge + # @attr [Vertex] destination The destination of the directed edge + # @attr [Object] requirement The requirement the directed edge represents + Edge = Struct.new(:origin, :destination, :requirement) + + # @return [{String => Vertex}] the vertices of the dependency graph, keyed + # by {Vertex#name} + attr_reader :vertices + + # @return [Log] the op log for this graph + attr_reader :log + + # Initializes an empty dependency graph + def initialize + @vertices = {} + @log = Log.new + end + + # Tags the current state of the dependency as the given tag + # @param [Object] tag an opaque tag for the current state of the graph + # @return [Void] + def tag(tag) + log.tag(self, tag) + end + + # Rewinds the graph to the state tagged as `tag` + # @param [Object] tag the tag to rewind to + # @return [Void] + def rewind_to(tag) + log.rewind_to(self, tag) + end + + # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} + # are properly copied. + # @param [DependencyGraph] other the graph to copy. + def initialize_copy(other) + super + @vertices = {} + @log = other.log.dup + traverse = lambda do |new_v, old_v| + return if new_v.outgoing_edges.size == old_v.outgoing_edges.size + old_v.outgoing_edges.each do |edge| + destination = add_vertex(edge.destination.name, edge.destination.payload) + add_edge_no_circular(new_v, destination, edge.requirement) + traverse.call(destination, edge.destination) + end + end + other.vertices.each do |name, vertex| + new_vertex = add_vertex(name, vertex.payload, vertex.root?) + new_vertex.explicit_requirements.replace(vertex.explicit_requirements) + traverse.call(new_vertex, vertex) + end + end + + # @return [String] a string suitable for debugging + def inspect + "#{self.class}:#{vertices.values.inspect}" + end + + # @param [Hash] options options for dot output. + # @return [String] Returns a dot format representation of the graph + def to_dot(options = {}) + edge_label = options.delete(:edge_label) + raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? + + dot_vertices = [] + dot_edges = [] + vertices.each do |n, v| + dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" + v.outgoing_edges.each do |e| + label = edge_label ? edge_label.call(e) : e.requirement + dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" + end + end + + dot_vertices.uniq! + dot_vertices.sort! + dot_edges.uniq! + dot_edges.sort! + + dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') + dot.join("\n") + end + + # @param [DependencyGraph] other + # @return [Boolean] whether the two dependency graphs are equal, determined + # by a recursive traversal of each {#root_vertices} and its + # {Vertex#successors} + def ==(other) + return false unless other + return true if equal?(other) + vertices.each do |name, vertex| + other_vertex = other.vertex_named(name) + return false unless other_vertex + return false unless vertex.payload == other_vertex.payload + return false unless other_vertex.successors.to_set == vertex.successors.to_set + end + end + + # @param [String] name + # @param [Object] payload + # @param [Array] parent_names + # @param [Object] requirement the requirement that is requiring the child + # @return [void] + def add_child_vertex(name, payload, parent_names, requirement) + root = !parent_names.delete(nil) { true } + vertex = add_vertex(name, payload, root) + vertex.explicit_requirements << requirement if root + parent_names.each do |parent_name| + parent_vertex = vertex_named(parent_name) + add_edge(parent_vertex, vertex, requirement) + end + vertex + end + + # Adds a vertex with the given name, or updates the existing one. + # @param [String] name + # @param [Object] payload + # @return [Vertex] the vertex that was added to `self` + def add_vertex(name, payload, root = false) + log.add_vertex(self, name, payload, root) + end + + # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively + # removing any non-root vertices that were orphaned in the process + # @param [String] name + # @return [Array] the vertices which have been detached + def detach_vertex_named(name) + log.detach_vertex_named(self, name) + end + + # @param [String] name + # @return [Vertex,nil] the vertex with the given name + def vertex_named(name) + vertices[name] + end + + # @param [String] name + # @return [Vertex,nil] the root vertex with the given name + def root_vertex_named(name) + vertex = vertex_named(name) + vertex if vertex && vertex.root? + end + + # Adds a new {Edge} to the dependency graph + # @param [Vertex] origin + # @param [Vertex] destination + # @param [Object] requirement the requirement that this edge represents + # @return [Edge] the added edge + def add_edge(origin, destination, requirement) + if destination.path_to?(origin) + raise CircularDependencyError.new(path(destination, origin)) + end + add_edge_no_circular(origin, destination, requirement) + end + + # Deletes an {Edge} from the dependency graph + # @param [Edge] edge + # @return [Void] + def delete_edge(edge) + log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) + end + + # Sets the payload of the vertex with the given name + # @param [String] name the name of the vertex + # @param [Object] payload the payload + # @return [Void] + def set_payload(name, payload) + log.set_payload(self, name, payload) + end + + private + + # Adds a new {Edge} to the dependency graph without checking for + # circularity. + # @param (see #add_edge) + # @return (see #add_edge) + def add_edge_no_circular(origin, destination, requirement) + log.add_edge_no_circular(self, origin.name, destination.name, requirement) + end + + # Returns the path between two vertices + # @raise [ArgumentError] if there is no path between the vertices + # @param [Vertex] from + # @param [Vertex] to + # @return [Array] the shortest path from `from` to `to` + def path(from, to) + distances = Hash.new(vertices.size + 1) + distances[from.name] = 0 + predecessors = {} + each do |vertex| + vertex.successors.each do |successor| + if distances[successor.name] > distances[vertex.name] + 1 + distances[successor.name] = distances[vertex.name] + 1 + predecessors[successor] = vertex + end + end + end + + path = [to] + while before = predecessors[to] + path << before + to = before + break if to == from + end + + unless path.last.equal?(from) + raise ArgumentError, "There is no path from #{from.name} to #{to.name}" + end + + path.reverse + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rb new file mode 100644 index 0000000000..8707ec451d --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Gem::Molinillo + class DependencyGraph + # An action that modifies a {DependencyGraph} that is reversible. + # @abstract + class Action + # rubocop:disable Lint/UnusedMethodArgument + + # @return [Symbol] The name of the action. + def self.action_name + raise 'Abstract' + end + + # Performs the action on the given graph. + # @param [DependencyGraph] graph the graph to perform the action on. + # @return [Void] + def up(graph) + raise 'Abstract' + end + + # Reverses the action on the given graph. + # @param [DependencyGraph] graph the graph to reverse the action on. + # @return [Void] + def down(graph) + raise 'Abstract' + end + + # @return [Action,Nil] The previous action + attr_accessor :previous + + # @return [Action,Nil] The next action + attr_accessor :next + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb new file mode 100644 index 0000000000..aa9815c5ae --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require_relative 'action' +module Gem::Molinillo + class DependencyGraph + # @!visibility private + # (see DependencyGraph#add_edge_no_circular) + class AddEdgeNoCircular < Action + # @!group Action + + # (see Action.action_name) + def self.action_name + :add_vertex + end + + # (see Action#up) + def up(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges << edge + edge.destination.incoming_edges << edge + edge + end + + # (see Action#down) + def down(graph) + edge = make_edge(graph) + delete_first(edge.origin.outgoing_edges, edge) + delete_first(edge.destination.incoming_edges, edge) + end + + # @!group AddEdgeNoCircular + + # @return [String] the name of the origin of the edge + attr_reader :origin + + # @return [String] the name of the destination of the edge + attr_reader :destination + + # @return [Object] the requirement that the edge represents + attr_reader :requirement + + # @param [DependencyGraph] graph the graph to find vertices from + # @return [Edge] The edge this action adds + def make_edge(graph) + Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement) + end + + # Initialize an action to add an edge to a dependency graph + # @param [String] origin the name of the origin of the edge + # @param [String] destination the name of the destination of the edge + # @param [Object] requirement the requirement that the edge represents + def initialize(origin, destination, requirement) + @origin = origin + @destination = destination + @requirement = requirement + end + + private + + def delete_first(array, item) + return unless index = array.index(item) + array.delete_at(index) + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb new file mode 100644 index 0000000000..9c7066a669 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require_relative 'action' +module Gem::Molinillo + class DependencyGraph + # @!visibility private + # (see DependencyGraph#add_vertex) + class AddVertex < Action # :nodoc: + # @!group Action + + # (see Action.action_name) + def self.action_name + :add_vertex + end + + # (see Action#up) + def up(graph) + if existing = graph.vertices[name] + @existing_payload = existing.payload + @existing_root = existing.root + end + vertex = existing || Vertex.new(name, payload) + graph.vertices[vertex.name] = vertex + vertex.payload ||= payload + vertex.root ||= root + vertex + end + + # (see Action#down) + def down(graph) + if defined?(@existing_payload) + vertex = graph.vertices[name] + vertex.payload = @existing_payload + vertex.root = @existing_root + else + graph.vertices.delete(name) + end + end + + # @!group AddVertex + + # @return [String] the name of the vertex + attr_reader :name + + # @return [Object] the payload for the vertex + attr_reader :payload + + # @return [Boolean] whether the vertex is root or not + attr_reader :root + + # Initialize an action to add a vertex to a dependency graph + # @param [String] name the name of the vertex + # @param [Object] payload the payload for the vertex + # @param [Boolean] root whether the vertex is root or not + def initialize(name, payload, root) + @name = name + @payload = payload + @root = root + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb new file mode 100644 index 0000000000..1e62c0a0b6 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require_relative 'action' +module Gem::Molinillo + class DependencyGraph + # @!visibility private + # (see DependencyGraph#delete_edge) + class DeleteEdge < Action + # @!group Action + + # (see Action.action_name) + def self.action_name + :delete_edge + end + + # (see Action#up) + def up(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges.delete(edge) + edge.destination.incoming_edges.delete(edge) + end + + # (see Action#down) + def down(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges << edge + edge.destination.incoming_edges << edge + edge + end + + # @!group DeleteEdge + + # @return [String] the name of the origin of the edge + attr_reader :origin_name + + # @return [String] the name of the destination of the edge + attr_reader :destination_name + + # @return [Object] the requirement that the edge represents + attr_reader :requirement + + # @param [DependencyGraph] graph the graph to find vertices from + # @return [Edge] The edge this action adds + def make_edge(graph) + Edge.new( + graph.vertex_named(origin_name), + graph.vertex_named(destination_name), + requirement + ) + end + + # Initialize an action to add an edge to a dependency graph + # @param [String] origin_name the name of the origin of the edge + # @param [String] destination_name the name of the destination of the edge + # @param [Object] requirement the requirement that the edge represents + def initialize(origin_name, destination_name, requirement) + @origin_name = origin_name + @destination_name = destination_name + @requirement = requirement + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb new file mode 100644 index 0000000000..6132f969b9 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require_relative 'action' +module Gem::Molinillo + class DependencyGraph + # @!visibility private + # @see DependencyGraph#detach_vertex_named + class DetachVertexNamed < Action + # @!group Action + + # (see Action#name) + def self.action_name + :add_vertex + end + + # (see Action#up) + def up(graph) + return [] unless @vertex = graph.vertices.delete(name) + + removed_vertices = [@vertex] + @vertex.outgoing_edges.each do |e| + v = e.destination + v.incoming_edges.delete(e) + if !v.root? && v.incoming_edges.empty? + removed_vertices.concat graph.detach_vertex_named(v.name) + end + end + + @vertex.incoming_edges.each do |e| + v = e.origin + v.outgoing_edges.delete(e) + end + + removed_vertices + end + + # (see Action#down) + def down(graph) + return unless @vertex + graph.vertices[@vertex.name] = @vertex + @vertex.outgoing_edges.each do |e| + e.destination.incoming_edges << e + end + @vertex.incoming_edges.each do |e| + e.origin.outgoing_edges << e + end + end + + # @!group DetachVertexNamed + + # @return [String] the name of the vertex to detach + attr_reader :name + + # Initialize an action to detach a vertex from a dependency graph + # @param [String] name the name of the vertex to detach + def initialize(name) + @name = name + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rb new file mode 100644 index 0000000000..6954c4b1f8 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require_relative 'add_edge_no_circular' +require_relative 'add_vertex' +require_relative 'delete_edge' +require_relative 'detach_vertex_named' +require_relative 'set_payload' +require_relative 'tag' + +module Gem::Molinillo + class DependencyGraph + # A log for dependency graph actions + class Log + # Initializes an empty log + def initialize + @current_action = @first_action = nil + end + + # @!macro [new] action + # {include:DependencyGraph#$0} + # @param [Graph] graph the graph to perform the action on + # @param (see DependencyGraph#$0) + # @return (see DependencyGraph#$0) + + # @macro action + def tag(graph, tag) + push_action(graph, Tag.new(tag)) + end + + # @macro action + def add_vertex(graph, name, payload, root) + push_action(graph, AddVertex.new(name, payload, root)) + end + + # @macro action + def detach_vertex_named(graph, name) + push_action(graph, DetachVertexNamed.new(name)) + end + + # @macro action + def add_edge_no_circular(graph, origin, destination, requirement) + push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) + end + + # {include:DependencyGraph#delete_edge} + # @param [Graph] graph the graph to perform the action on + # @param [String] origin_name + # @param [String] destination_name + # @param [Object] requirement + # @return (see DependencyGraph#delete_edge) + def delete_edge(graph, origin_name, destination_name, requirement) + push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) + end + + # @macro action + def set_payload(graph, name, payload) + push_action(graph, SetPayload.new(name, payload)) + end + + # Pops the most recent action from the log and undoes the action + # @param [DependencyGraph] graph + # @return [Action] the action that was popped off the log + def pop!(graph) + return unless action = @current_action + unless @current_action = action.previous + @first_action = nil + end + action.down(graph) + action + end + + extend Enumerable + + # @!visibility private + # Enumerates each action in the log + # @yield [Action] + def each + return enum_for unless block_given? + action = @first_action + loop do + break unless action + yield action + action = action.next + end + self + end + + # @!visibility private + # Enumerates each action in the log in reverse order + # @yield [Action] + def reverse_each + return enum_for(:reverse_each) unless block_given? + action = @current_action + loop do + break unless action + yield action + action = action.previous + end + self + end + + # @macro action + def rewind_to(graph, tag) + loop do + action = pop!(graph) + raise "No tag #{tag.inspect} found" unless action + break if action.class.action_name == :tag && action.tag == tag + end + end + + private + + # Adds the given action to the log, running the action + # @param [DependencyGraph] graph + # @param [Action] action + # @return The value returned by `action.up` + def push_action(graph, action) + action.previous = @current_action + @current_action.next = action if @current_action + @current_action = action + @first_action ||= action + action.up(graph) + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb new file mode 100644 index 0000000000..9bcaaae0f9 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative 'action' +module Gem::Molinillo + class DependencyGraph + # @!visibility private + # @see DependencyGraph#set_payload + class SetPayload < Action # :nodoc: + # @!group Action + + # (see Action.action_name) + def self.action_name + :set_payload + end + + # (see Action#up) + def up(graph) + vertex = graph.vertex_named(name) + @old_payload = vertex.payload + vertex.payload = payload + end + + # (see Action#down) + def down(graph) + graph.vertex_named(name).payload = @old_payload + end + + # @!group SetPayload + + # @return [String] the name of the vertex + attr_reader :name + + # @return [Object] the payload for the vertex + attr_reader :payload + + # Initialize an action to add set the payload for a vertex in a dependency + # graph + # @param [String] name the name of the vertex + # @param [Object] payload the payload for the vertex + def initialize(name, payload) + @name = name + @payload = payload + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb new file mode 100644 index 0000000000..62f243a2af --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative 'action' +module Gem::Molinillo + class DependencyGraph + # @!visibility private + # @see DependencyGraph#tag + class Tag < Action + # @!group Action + + # (see Action.action_name) + def self.action_name + :tag + end + + # (see Action#up) + def up(graph) + end + + # (see Action#down) + def down(graph) + end + + # @!group Tag + + # @return [Object] An opaque tag + attr_reader :tag + + # Initialize an action to tag a state of a dependency graph + # @param [Object] tag an opaque tag + def initialize(tag) + @tag = tag + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb new file mode 100644 index 0000000000..074de369be --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +module Gem::Molinillo + class DependencyGraph + # A vertex in a {DependencyGraph} that encapsulates a {#name} and a + # {#payload} + class Vertex + # @return [String] the name of the vertex + attr_accessor :name + + # @return [Object] the payload the vertex holds + attr_accessor :payload + + # @return [Array] the explicit requirements that required + # this vertex + attr_reader :explicit_requirements + + # @return [Boolean] whether the vertex is considered a root vertex + attr_accessor :root + alias root? root + + # Initializes a vertex with the given name and payload. + # @param [String] name see {#name} + # @param [Object] payload see {#payload} + def initialize(name, payload) + @name = name.frozen? ? name : name.dup.freeze + @payload = payload + @explicit_requirements = [] + @outgoing_edges = [] + @incoming_edges = [] + end + + # @return [Array] all of the requirements that required + # this vertex + def requirements + (incoming_edges.map(&:requirement) + explicit_requirements).uniq + end + + # @return [Array] the edges of {#graph} that have `self` as their + # {Edge#origin} + attr_accessor :outgoing_edges + + # @return [Array] the edges of {#graph} that have `self` as their + # {Edge#destination} + attr_accessor :incoming_edges + + # @return [Array] the vertices of {#graph} that have an edge with + # `self` as their {Edge#destination} + def predecessors + incoming_edges.map(&:origin) + end + + # @return [Set] the vertices of {#graph} where `self` is a + # {#descendent?} + def recursive_predecessors + _recursive_predecessors + end + + # @param [Set] vertices the set to add the predecessors to + # @return [Set] the vertices of {#graph} where `self` is a + # {#descendent?} + def _recursive_predecessors(vertices = new_vertex_set) + incoming_edges.each do |edge| + vertex = edge.origin + next unless vertices.add?(vertex) + vertex._recursive_predecessors(vertices) + end + + vertices + end + protected :_recursive_predecessors + + # @return [Array] the vertices of {#graph} that have an edge with + # `self` as their {Edge#origin} + def successors + outgoing_edges.map(&:destination) + end + + # @return [Set] the vertices of {#graph} where `self` is an + # {#ancestor?} + def recursive_successors + _recursive_successors + end + + # @param [Set] vertices the set to add the successors to + # @return [Set] the vertices of {#graph} where `self` is an + # {#ancestor?} + def _recursive_successors(vertices = new_vertex_set) + outgoing_edges.each do |edge| + vertex = edge.destination + next unless vertices.add?(vertex) + vertex._recursive_successors(vertices) + end + + vertices + end + protected :_recursive_successors + + # @return [String] a string suitable for debugging + def inspect + "#{self.class}:#{name}(#{payload.inspect})" + end + + # @return [Boolean] whether the two vertices are equal, determined + # by a recursive traversal of each {Vertex#successors} + def ==(other) + return true if equal?(other) + shallow_eql?(other) && + successors.to_set == other.successors.to_set + end + + # @param [Vertex] other the other vertex to compare to + # @return [Boolean] whether the two vertices are equal, determined + # solely by {#name} and {#payload} equality + def shallow_eql?(other) + return true if equal?(other) + other && + name == other.name && + payload == other.payload + end + + alias eql? == + + # @return [Fixnum] a hash for the vertex based upon its {#name} + def hash + name.hash + end + + # Is there a path from `self` to `other` following edges in the + # dependency graph? + # @return whether there is a path following edges within this {#graph} + def path_to?(other) + _path_to?(other) + end + + alias descendent? path_to? + + # @param [Vertex] other the vertex to check if there's a path to + # @param [Set] visited the vertices of {#graph} that have been visited + # @return [Boolean] whether there is a path to `other` from `self` + def _path_to?(other, visited = new_vertex_set) + return false unless visited.add?(self) + return true if equal?(other) + successors.any? { |v| v._path_to?(other, visited) } + end + protected :_path_to? + + # Is there a path from `other` to `self` following edges in the + # dependency graph? + # @return whether there is a path following edges within this {#graph} + def ancestor?(other) + other.path_to?(self) + end + + alias is_reachable_from? ancestor? + + def new_vertex_set + require 'set' + Set.new + end + private :new_vertex_set + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/errors.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/errors.rb new file mode 100644 index 0000000000..07ea5fdf37 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/errors.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +module Gem::Molinillo + # An error that occurred during the resolution process + class ResolverError < StandardError; end + + # An error caused by searching for a dependency that is completely unknown, + # i.e. has no versions available whatsoever. + class NoSuchDependencyError < ResolverError + # @return [Object] the dependency that could not be found + attr_accessor :dependency + + # @return [Array] the specifications that depended upon {#dependency} + attr_accessor :required_by + + # Initializes a new error with the given missing dependency. + # @param [Object] dependency @see {#dependency} + # @param [Array] required_by @see {#required_by} + def initialize(dependency, required_by = []) + @dependency = dependency + @required_by = required_by.uniq + super() + end + + # The error message for the missing dependency, including the specifications + # that had this dependency. + def message + sources = required_by.map { |r| "`#{r}`" }.join(' and ') + message = "Unable to find a specification for `#{dependency}`" + message += " depended upon by #{sources}" unless sources.empty? + message + end + end + + # An error caused by attempting to fulfil a dependency that was circular + # + # @note This exception will be thrown if and only if a {Vertex} is added to a + # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an + # existing {DependencyGraph::Vertex} + class CircularDependencyError < ResolverError + # [Set] the dependencies responsible for causing the error + attr_reader :dependencies + + # Initializes a new error with the given circular vertices. + # @param [Array] vertices the vertices in the dependency + # that caused the error + def initialize(vertices) + super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" + @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set + end + end + + # An error caused by conflicts in version + class VersionConflict < ResolverError + # @return [{String => Resolution::Conflict}] the conflicts that caused + # resolution to fail + attr_reader :conflicts + + # @return [SpecificationProvider] the specification provider used during + # resolution + attr_reader :specification_provider + + # Initializes a new error with the given version conflicts. + # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} + # @param [SpecificationProvider] specification_provider see {#specification_provider} + def initialize(conflicts, specification_provider) + pairs = [] + conflicts.values.flat_map(&:requirements).each do |conflicting| + conflicting.each do |source, conflict_requirements| + conflict_requirements.each do |c| + pairs << [c, source] + end + end + end + + super "Unable to satisfy the following requirements:\n\n" \ + "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" + + @conflicts = conflicts + @specification_provider = specification_provider + end + + require_relative 'delegates/specification_provider' + include Delegates::SpecificationProvider + + # @return [String] An error message that includes requirement trees, + # which is much more detailed & customizable than the default message + # @param [Hash] opts the options to create a message with. + # @option opts [String] :solver_name The user-facing name of the solver + # @option opts [String] :possibility_type The generic name of a possibility + # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees + # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements + # @option opts [Proc] :additional_message_for_conflict A proc that appends additional + # messages for each conflict + # @option opts [Proc] :version_for_spec A proc that returns the version number for a + # possibility + def message_with_trees(opts = {}) + solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } + possibility_type = opts.delete(:possibility_type) { 'possibility named' } + reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } + printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } + additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } + version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } + incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do + proc do |name, _conflict| + %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) + end + end + + 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 << %(\n) + depth += 1 + end + t + end.join("\n") + + additional_message_for_conflict.call(o, name, conflict) + + o + end + end + + conflicts.sort.reduce(''.dup) do |o, (name, conflict)| + o << full_message_for_conflict.call(name, conflict) + end.strip + end + 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/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rb new file mode 100644 index 0000000000..85860902fc --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +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::Molinillo must to implement, + # using knowledge of their own model classes. + module SpecificationProvider + # Search for the specifications that match the given dependency. + # The specifications in the returned array will be considered in reverse + # order, so the latest version ought to be last. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `dependency` parameter. + # + # @param [Object] dependency + # @return [Array] the specifications that satisfy the given + # `dependency`. + def search_for(dependency) + [] + end + + # Returns the dependencies of `specification`. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `specification` parameter. + # + # @param [Object] specification + # @return [Array] the dependencies that are required by the given + # `specification`. + def dependencies_for(specification) + [] + end + + # Determines whether the given `requirement` is satisfied by the given + # `spec`, in the context of the current `activated` dependency graph. + # + # @param [Object] requirement + # @param [DependencyGraph] activated the current dependency graph in the + # resolution process. + # @param [Object] spec + # @return [Boolean] whether `requirement` is satisfied by `spec` in the + # context of the current `activated` dependency graph. + def requirement_satisfied_by?(requirement, activated, spec) + true + end + + # Determines whether two arrays of dependencies are equal, and thus can be + # grouped. + # + # @param [Array] dependencies + # @param [Array] other_dependencies + # @return [Boolean] whether `dependencies` and `other_dependencies` should + # be considered equal. + def dependencies_equal?(dependencies, other_dependencies) + dependencies == other_dependencies + end + + # Returns the name for the given `dependency`. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `dependency` parameter. + # + # @param [Object] dependency + # @return [String] the name for the given `dependency`. + def name_for(dependency) + dependency.to_s + end + + # @return [String] the name of the source of explicit dependencies, i.e. + # those passed to {Resolver#resolve} directly. + def name_for_explicit_dependency_source + 'user-specified dependency' + end + + # @return [String] the name of the source of 'locked' dependencies, i.e. + # those passed to {Resolver#resolve} directly as the `base` + def name_for_locking_dependency_source + 'Lockfile' + end + + # Sort dependencies so that the ones that are easiest to resolve are first. + # Easiest to resolve is (usually) defined by: + # 1) Is this dependency already activated? + # 2) How relaxed are the requirements? + # 3) Are there any conflicts for this dependency? + # 4) How many possibilities are there to satisfy this dependency? + # + # @param [Array] dependencies + # @param [DependencyGraph] activated the current dependency graph in the + # resolution process. + # @param [{String => Array}] conflicts + # @return [Array] a sorted copy of `dependencies`. + def sort_dependencies(dependencies, activated, conflicts) + dependencies.sort_by do |dependency| + name = name_for(dependency) + [ + activated.vertex_named(name).payload ? 0 : 1, + conflicts[name] ? 0 : 1, + ] + end + end + + # Returns whether this dependency, which has no possible matching + # specifications, can safely be ignored. + # + # @param [Object] dependency + # @return [Boolean] whether this dependency can safely be skipped. + def allow_missing?(dependency) + false + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/modules/ui.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/ui.rb new file mode 100644 index 0000000000..464722902e --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/modules/ui.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +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. + # + # @return [IO] + def output + STDOUT + end + + # Called roughly every {#progress_rate}, this method should convey progress + # to the user. + # + # @return [void] + def indicate_progress + output.print '.' unless debug? + end + + # How often progress should be conveyed to the user via + # {#indicate_progress}, in seconds. A third of a second, by default. + # + # @return [Float] + def progress_rate + 0.33 + end + + # Called before resolution begins. + # + # @return [void] + def before_resolution + output.print 'Resolving dependencies...' + end + + # Called after resolution ends (either successfully or with an error). + # By default, prints a newline. + # + # @return [void] + def after_resolution + output.puts + end + + # Conveys debug information to the user. + # + # @param [Integer] depth the current depth of the resolution process. + # @return [void] + def debug(depth = 0) + if debug? + debug_info = yield + debug_info = debug_info.inspect unless debug_info.is_a?(String) + debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } + output.puts debug_info + end + end + + # Whether or not debug messages should be printed. + # By default, whether or not the `MOLINILLO_DEBUG` environment variable is + # set. + # + # @return [Boolean] + def debug? + return @debug_mode if defined?(@debug_mode) + @debug_mode = ENV['MOLINILLO_DEBUG'] + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/resolution.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/resolution.rb new file mode 100644 index 0000000000..5cfc6e3a0d --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/resolution.rb @@ -0,0 +1,839 @@ +# frozen_string_literal: true + +module Gem::Molinillo + class Resolver + # A specific resolution from a given {Resolver} + class Resolution + # A conflict that the resolution process encountered + # @attr [Object] requirement the requirement that immediately led to the conflict + # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict + # @attr [Object, nil] existing the existing spec that was in conflict with + # the {#possibility} + # @attr [Object] possibility_set the set of specs that was unable to be + # activated due to a conflict. + # @attr [Object] locked_requirement the relevant locking requirement. + # @attr [Array>] requirement_trees the different requirement + # trees that led to every requirement for the conflicting name. + # @attr [{String=>Object}] activated_by_name the already-activated specs. + # @attr [Object] underlying_error an error that has occurred during resolution, and + # will be raised at the end of it if no resolution is found. + Conflict = Struct.new( + :requirement, + :requirements, + :existing, + :possibility_set, + :locked_requirement, + :requirement_trees, + :activated_by_name, + :underlying_error + ) + + class Conflict + # @return [Object] a spec that was unable to be activated due to a conflict + def possibility + possibility_set && possibility_set.latest_version + end + end + + # A collection of possibility states that share the same dependencies + # @attr [Array] dependencies the dependencies for this set of possibilities + # @attr [Array] possibilities the possibilities + PossibilitySet = Struct.new(:dependencies, :possibilities) + + class PossibilitySet + # String representation of the possibility set, for debugging + def to_s + "[#{possibilities.join(', ')}]" + end + + # @return [Object] most up-to-date dependency in the possibility set + def latest_version + possibilities.last + end + end + + # Details of the state to unwind to when a conflict occurs, and the cause of the unwind + # @attr [Integer] state_index the index of the state to unwind to + # @attr [Object] state_requirement the requirement of the state we're unwinding to + # @attr [Array] requirement_tree for the requirement we're relaxing + # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict + # @attr [Array] requirement_trees for the conflict + # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind + UnwindDetails = Struct.new( + :state_index, + :state_requirement, + :requirement_tree, + :conflicting_requirements, + :requirement_trees, + :requirements_unwound_to_instead + ) + + class UnwindDetails + include Comparable + + # We compare UnwindDetails when choosing which state to unwind to. If + # two options have the same state_index we prefer the one most + # removed from a requirement that caused the conflict. Both options + # would unwind to the same state, but a `grandparent` option will + # filter out fewer of its possibilities after doing so - where a state + # is both a `parent` and a `grandparent` to requirements that have + # caused a conflict this is the correct behaviour. + # @param [UnwindDetail] other UnwindDetail to be compared + # @return [Integer] integer specifying ordering + def <=>(other) + if state_index > other.state_index + 1 + elsif state_index == other.state_index + reversed_requirement_tree_index <=> other.reversed_requirement_tree_index + else + -1 + end + end + + # @return [Integer] index of state requirement in reversed requirement tree + # (the conflicting requirement itself will be at position 0) + def reversed_requirement_tree_index + @reversed_requirement_tree_index ||= + if state_requirement + requirement_tree.reverse.index(state_requirement) + else + 999_999 + end + end + + # @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 + # any of the other conflicting requirements (or we would have + # circularity) + def unwinding_to_primary_requirement? + requirement_tree.last == state_requirement + end + + # @return [Array] array of sub-dependencies to avoid when choosing a + # new possibility for the state we've unwound to. Only relevant for + # non-primary unwinds + def sub_dependencies_to_avoid + @requirements_to_avoid ||= + requirement_trees.map do |tree| + index = tree.index(state_requirement) + tree[index + 1] if index + end.compact + end + + # @return [Array] array of all the requirements that led to the need for + # this unwind + def all_requirements + @all_requirements ||= requirement_trees.flatten(1) + end + end + + # @return [SpecificationProvider] the provider that knows about + # dependencies, requirements, specifications, versions, etc. + attr_reader :specification_provider + + # @return [UI] the UI that knows how to communicate feedback about the + # resolution process back to the user + attr_reader :resolver_ui + + # @return [DependencyGraph] the base dependency graph to which + # dependencies should be 'locked' + attr_reader :base + + # @return [Array] the dependencies that were explicitly required + attr_reader :original_requested + + # Initializes a new resolution. + # @param [SpecificationProvider] specification_provider + # see {#specification_provider} + # @param [UI] resolver_ui see {#resolver_ui} + # @param [Array] requested see {#original_requested} + # @param [DependencyGraph] base see {#base} + def initialize(specification_provider, resolver_ui, requested, base) + @specification_provider = specification_provider + @resolver_ui = resolver_ui + @original_requested = requested + @base = base + @states = [] + @iteration_counter = 0 + @parents_of = Hash.new { |h, k| h[k] = [] } + end + + # Resolves the {#original_requested} dependencies into a full dependency + # graph + # @raise [ResolverError] if successful resolution is impossible + # @return [DependencyGraph] the dependency graph of successfully resolved + # dependencies + def resolve + start_resolution + + while state + break if !state.requirement && state.requirements.empty? + indicate_progress + if state.respond_to?(:pop_possibility_state) # DependencyState + debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } + state.pop_possibility_state.tap do |s| + if s + states.push(s) + activated.tag(s) + end + end + end + process_topmost_state + end + + resolve_activated_specs + ensure + end_resolution + end + + # @return [Integer] the number of resolver iterations in between calls to + # {#resolver_ui}'s {UI#indicate_progress} method + attr_accessor :iteration_rate + private :iteration_rate + + # @return [Time] the time at which resolution began + attr_accessor :started_at + private :started_at + + # @return [Array] the stack of states for the resolution + attr_accessor :states + private :states + + private + + # Sets up the resolution process + # @return [void] + def start_resolution + @started_at = Time.now + + push_initial_state + + debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } + resolver_ui.before_resolution + end + + def resolve_activated_specs + activated.vertices.each do |_, vertex| + next unless vertex.payload + + latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| + vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } + end + + activated.set_payload(vertex.name, latest_version) + end + activated.freeze + end + + # Ends the resolution process + # @return [void] + def end_resolution + resolver_ui.after_resolution + debug do + "Finished resolution (#{@iteration_counter} steps) " \ + "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" + end + debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state + debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state + end + + require_relative 'state' + require_relative 'modules/specification_provider' + + require_relative 'delegates/resolution_state' + require_relative 'delegates/specification_provider' + + include Gem::Molinillo::Delegates::ResolutionState + include Gem::Molinillo::Delegates::SpecificationProvider + + # Processes the topmost available {RequirementState} on the stack + # @return [void] + def process_topmost_state + if possibility + attempt_to_activate + else + create_conflict + unwind_for_conflict + end + rescue CircularDependencyError => underlying_error + create_conflict(underlying_error) + unwind_for_conflict + end + + # @return [Object] the current possibility that the resolution is trying + # to activate + def possibility + possibilities.last + end + + # @return [RequirementState] the current state the resolution is + # operating upon + def state + states.last + end + + # Creates and pushes the initial state for the resolution, based upon the + # {#requested} dependencies + # @return [void] + def push_initial_state + graph = DependencyGraph.new.tap do |dg| + original_requested.each do |requested| + vertex = dg.add_vertex(name_for(requested), nil, true) + vertex.explicit_requirements << requested + end + dg.tag(:initial_state) + end + + push_state_for_requirements(original_requested, true, graph) + end + + # Unwinds the states stack because a conflict has been encountered + # @return [void] + def unwind_for_conflict + details_for_unwind = build_details_for_unwind + unwind_options = unused_unwind_options + debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } + conflicts.tap do |c| + sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) + raise_error_unless_state(c) + activated.rewind_to(sliced_states.first || :initial_state) if sliced_states + state.conflicts = c + state.unused_unwind_options = unwind_options + filter_possibilities_after_unwind(details_for_unwind) + index = states.size - 1 + @parents_of.each { |_, a| a.reject! { |i| i >= index } } + state.unused_unwind_options.reject! { |uw| uw.state_index >= index } + end + end + + # Raises a VersionConflict error, or any underlying error, if there is no + # current state + # @return [void] + def raise_error_unless_state(conflicts) + return if state + + error = conflicts.values.map(&:underlying_error).compact.first + raise error || VersionConflict.new(conflicts, specification_provider) + end + + # @return [UnwindDetails] Details of the nearest index to which we could unwind + def build_details_for_unwind + # Get the possible unwinds for the current conflict + current_conflict = conflicts[name] + binding_requirements = binding_requirements_for_conflict(current_conflict) + unwind_details = unwind_options_for_requirements(binding_requirements) + + last_detail_for_current_unwind = unwind_details.sort.last + current_detail = last_detail_for_current_unwind + + # Look for past conflicts that could be unwound to affect the + # requirement tree for the current conflict + all_reqs = last_detail_for_current_unwind.all_requirements + all_reqs_size = all_reqs.size + relevant_unused_unwinds = unused_unwind_options.select do |alternative| + diff_reqs = all_reqs - alternative.requirements_unwound_to_instead + next if diff_reqs.size == all_reqs_size + # Find the highest index unwind whilst looping through + current_detail = alternative if alternative > current_detail + alternative + end + + # Add the current unwind options to the `unused_unwind_options` array. + # The "used" option will be filtered out during `unwind_for_conflict`. + state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } + + # Update the requirements_unwound_to_instead on any relevant unused unwinds + relevant_unused_unwinds.each do |d| + (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! + end + unwind_details.each do |d| + (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! + end + + current_detail + end + + # @param [Array] binding_requirements array of requirements that combine to create a conflict + # @return [Array] array of UnwindDetails that have a chance + # of resolving the passed requirements + def unwind_options_for_requirements(binding_requirements) + unwind_details = [] + + trees = [] + binding_requirements.reverse_each do |r| + partial_tree = [r] + trees << partial_tree + unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) + + # If this requirement has alternative possibilities, check if any would + # satisfy the other requirements that created this conflict + requirement_state = find_state_for(r) + if conflict_fixing_possibilities?(requirement_state, binding_requirements) + unwind_details << UnwindDetails.new( + states.index(requirement_state), + r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + + # Next, look at the parent of this requirement, and check if the requirement + # could have been avoided if an alternative PossibilitySet had been chosen + parent_r = parent_of(r) + next if parent_r.nil? + partial_tree.unshift(parent_r) + requirement_state = find_state_for(parent_r) + if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } + unwind_details << UnwindDetails.new( + states.index(requirement_state), + parent_r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + + # Finally, look at the grandparent and up of this requirement, looking + # for any possibilities that wouldn't create their parent requirement + grandparent_r = parent_of(parent_r) + until grandparent_r.nil? + partial_tree.unshift(grandparent_r) + requirement_state = find_state_for(grandparent_r) + if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } + unwind_details << UnwindDetails.new( + states.index(requirement_state), + grandparent_r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + parent_r = grandparent_r + grandparent_r = parent_of(parent_r) + end + end + + unwind_details + end + + # @param [DependencyState] state + # @param [Array] binding_requirements array of requirements + # @return [Boolean] whether or not the given state has any possibilities + # that could satisfy the given requirements + def conflict_fixing_possibilities?(state, binding_requirements) + return false unless state + + state.possibilities.any? do |possibility_set| + possibility_set.possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, binding_requirements) + end + end + end + + # Filter's a state's possibilities to remove any that would not fix the + # conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just + # unwound from + # @return [void] + def filter_possibilities_after_unwind(unwind_details) + return unless state && !state.possibilities.empty? + + if unwind_details.unwinding_to_primary_requirement? + filter_possibilities_for_primary_unwind(unwind_details) + else + filter_possibilities_for_parent_unwind(unwind_details) + end + end + + # Filter's a state's possibilities to remove any that would not satisfy + # the requirements in the conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just unwound from + # @return [void] + def filter_possibilities_for_primary_unwind(unwind_details) + unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } + unwinds_to_state << unwind_details + unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) + + state.possibilities.reject! do |possibility_set| + possibility_set.possibilities.none? do |poss| + unwind_requirement_sets.any? do |requirements| + possibility_satisfies_requirements?(poss, requirements) + end + end + end + end + + # @param [Object] possibility a single possibility + # @param [Array] requirements an array of requirements + # @return [Boolean] whether the possibility satisfies all of the + # given requirements + def possibility_satisfies_requirements?(possibility, requirements) + name = name_for(possibility) + + activated.tag(:swap) + activated.set_payload(name, possibility) if activated.vertex_named(name) + satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } + activated.rewind_to(:swap) + + satisfied + end + + # Filter's a state's possibilities to remove any that would (eventually) + # create a requirement in the conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just unwound from + # @return [void] + def filter_possibilities_for_parent_unwind(unwind_details) + unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } + unwinds_to_state << unwind_details + + primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq + parent_unwinds = unwinds_to_state.uniq - primary_unwinds + + allowed_possibility_sets = primary_unwinds.flat_map do |unwind| + states[unwind.state_index].possibilities.select do |possibility_set| + possibility_set.possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) + end + end + end + + requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) + + state.possibilities.reject! do |possibility_set| + !allowed_possibility_sets.include?(possibility_set) && + (requirements_to_avoid - possibility_set.dependencies).empty? + end + end + + # @param [Conflict] conflict + # @return [Array] minimal array of requirements that would cause the passed + # conflict to occur. + def binding_requirements_for_conflict(conflict) + return [conflict.requirement] if conflict.possibility.nil? + + possible_binding_requirements = conflict.requirements.values.flatten(1).uniq + + # When there's a `CircularDependency` error the conflicting requirement + # (the one causing the circular) won't be `conflict.requirement` + # (which won't be for the right state, because we won't have created it, + # because it's circular). + # We need to make sure we have that requirement in the conflict's list, + # otherwise we won't be able to unwind properly, so we just return all + # the requirements for the conflict. + return possible_binding_requirements if conflict.underlying_error + + possibilities = search_for(conflict.requirement) + + # If all the requirements together don't filter out all possibilities, + # then the only two requirements we need to consider are the initial one + # (where the dependency's version was first chosen) and the last + if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) + return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact + end + + # Loop through the possible binding requirements, removing each one + # that doesn't bind. Use a `reverse_each` as we want the earliest set of + # binding requirements, and don't use `reject!` as we wish to refine the + # array *on each iteration*. + binding_requirements = possible_binding_requirements.dup + possible_binding_requirements.reverse_each do |req| + next if req == conflict.requirement + unless binding_requirement_in_set?(req, binding_requirements, possibilities) + binding_requirements -= [req] + end + end + + binding_requirements + end + + # @param [Object] requirement we wish to check + # @param [Array] possible_binding_requirements array of requirements + # @param [Array] possibilities array of possibilities the requirements will be used to filter + # @return [Boolean] whether or not the given requirement is required to filter + # out all elements of the array of possibilities. + def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) + possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) + end + end + + # @param [Object] requirement + # @return [Object] the requirement that led to `requirement` being added + # to the list of requirements. + def parent_of(requirement) + return unless requirement + return unless index = @parents_of[requirement].last + return unless parent_state = @states[index] + parent_state.requirement + end + + # @param [String] name + # @return [Object] the requirement that led to a version of a possibility + # with the given name being activated. + def requirement_for_existing_name(name) + return nil unless vertex = activated.vertex_named(name) + return nil unless vertex.payload + states.find { |s| s.name == name }.requirement + end + + # @param [Object] requirement + # @return [ResolutionState] the state whose `requirement` is the given + # `requirement`. + def find_state_for(requirement) + return nil unless requirement + states.find { |i| requirement == i.requirement } + end + + # @param [Object] underlying_error + # @return [Conflict] a {Conflict} that reflects the failure to activate + # the {#possibility} in conjunction with the current {#state} + def create_conflict(underlying_error = nil) + vertex = activated.vertex_named(name) + locked_requirement = locked_requirement_named(name) + + requirements = {} + unless vertex.explicit_requirements.empty? + requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements + end + requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement + vertex.incoming_edges.each do |edge| + (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) + end + + activated_by_name = {} + activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } + conflicts[name] = Conflict.new( + requirement, + requirements, + vertex.payload && vertex.payload.latest_version, + possibility, + locked_requirement, + requirement_trees, + activated_by_name, + underlying_error + ) + end + + # @return [Array>] The different requirement + # trees that led to every requirement for the current spec. + def requirement_trees + vertex = activated.vertex_named(name) + vertex.requirements.map { |r| requirement_tree_for(r) } + end + + # @param [Object] requirement + # @return [Array] the list of requirements that led to + # `requirement` being required. + def requirement_tree_for(requirement) + tree = [] + while requirement + tree.unshift(requirement) + requirement = parent_of(requirement) + end + tree + end + + # Indicates progress roughly once every second + # @return [void] + def indicate_progress + @iteration_counter += 1 + @progress_rate ||= resolver_ui.progress_rate + if iteration_rate.nil? + if Time.now - started_at >= @progress_rate + self.iteration_rate = @iteration_counter + end + end + + if iteration_rate && (@iteration_counter % iteration_rate) == 0 + resolver_ui.indicate_progress + end + end + + # Calls the {#resolver_ui}'s {UI#debug} method + # @param [Integer] depth the depth of the {#states} stack + # @param [Proc] block a block that yields a {#to_s} + # @return [void] + def debug(depth = 0, &block) + resolver_ui.debug(depth, &block) + end + + # Attempts to activate the current {#possibility} + # @return [void] + def attempt_to_activate + debug(depth) { 'Attempting to activate ' + possibility.to_s } + existing_vertex = activated.vertex_named(name) + if existing_vertex.payload + debug(depth) { "Found existing spec (#{existing_vertex.payload})" } + attempt_to_filter_existing_spec(existing_vertex) + else + latest = possibility.latest_version + possibility.possibilities.select! do |possibility| + requirement_satisfied_by?(requirement, activated, possibility) + end + if possibility.latest_version.nil? + # ensure there's a possibility for better error messages + possibility.possibilities << latest if latest + create_conflict + unwind_for_conflict + else + activate_new_spec + end + end + end + + # Attempts to update the existing vertex's `PossibilitySet` with a filtered version + # @return [void] + def attempt_to_filter_existing_spec(vertex) + filtered_set = filtered_possibility_set(vertex) + if !filtered_set.possibilities.empty? + activated.set_payload(name, filtered_set) + new_requirements = requirements.dup + push_state_for_requirements(new_requirements, false) + else + create_conflict + debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } + unwind_for_conflict + end + end + + # Generates a filtered version of the existing vertex's `PossibilitySet` using the + # current state's `requirement` + # @param [Object] vertex existing vertex + # @return [PossibilitySet] filtered possibility set + def filtered_possibility_set(vertex) + PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) + end + + # @param [String] requirement_name the spec name to search for + # @return [Object] the locked spec named `requirement_name`, if one + # is found on {#base} + def locked_requirement_named(requirement_name) + vertex = base.vertex_named(requirement_name) + vertex && vertex.payload + end + + # Add the current {#possibility} to the dependency graph of the current + # {#state} + # @return [void] + def activate_new_spec + conflicts.delete(name) + debug(depth) { "Activated #{name} at #{possibility}" } + activated.set_payload(name, possibility) + require_nested_dependencies_for(possibility) + end + + # Requires the dependencies that the recently activated spec has + # @param [Object] possibility_set the PossibilitySet that has just been + # activated + # @return [void] + def require_nested_dependencies_for(possibility_set) + nested_dependencies = dependencies_for(possibility_set.latest_version) + debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } + nested_dependencies.each do |d| + activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) + parent_index = states.size - 1 + parents = @parents_of[d] + parents << parent_index if parents.empty? + end + + push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) + end + + # Pushes a new {DependencyState} that encapsulates both existing and new + # requirements + # @param [Array] new_requirements + # @param [Boolean] requires_sort + # @param [Object] new_activated + # @return [void] + def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) + new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort + new_requirement = nil + loop do + new_requirement = new_requirements.shift + break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } + end + new_name = new_requirement ? name_for(new_requirement) : ''.freeze + possibilities = possibilities_for_requirement(new_requirement) + handle_missing_or_push_dependency_state DependencyState.new( + new_name, new_requirements, new_activated, + new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup + ) + end + + # Checks a proposed requirement with any existing locked requirement + # before generating an array of possibilities for it. + # @param [Object] requirement the proposed requirement + # @param [Object] activated + # @return [Array] possibilities + def possibilities_for_requirement(requirement, activated = self.activated) + return [] unless requirement + if locked_requirement_named(name_for(requirement)) + return locked_requirement_possibility_set(requirement, activated) + end + + group_possibilities(search_for(requirement)) + end + + # @param [Object] requirement the proposed requirement + # @param [Object] activated + # @return [Array] possibility set containing only the locked requirement, if any + def locked_requirement_possibility_set(requirement, activated = self.activated) + all_possibilities = search_for(requirement) + locked_requirement = locked_requirement_named(name_for(requirement)) + + # Longwinded way to build a possibilities array with either the locked + # requirement or nothing in it. Required, since the API for + # locked_requirement isn't guaranteed. + locked_possibilities = all_possibilities.select do |possibility| + requirement_satisfied_by?(locked_requirement, activated, possibility) + end + + group_possibilities(locked_possibilities) + end + + # Build an array of PossibilitySets, with each element representing a group of + # dependency versions that all have the same sub-dependency version constraints + # and are contiguous. + # @param [Array] possibilities an array of possibilities + # @return [Array] an array of possibility sets + def group_possibilities(possibilities) + possibility_sets = [] + current_possibility_set = nil + + possibilities.reverse_each do |possibility| + dependencies = dependencies_for(possibility) + if current_possibility_set && dependencies_equal?(current_possibility_set.dependencies, dependencies) + current_possibility_set.possibilities.unshift(possibility) + else + possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) + current_possibility_set = possibility_sets.first + end + end + + possibility_sets + end + + # Pushes a new {DependencyState}. + # If the {#specification_provider} says to + # {SpecificationProvider#allow_missing?} that particular requirement, and + # there are no possibilities for that requirement, then `state` is not + # pushed, and the vertex in {#activated} is removed, and we continue + # resolving the remaining requirements. + # @param [DependencyState] state + # @return [void] + def handle_missing_or_push_dependency_state(state) + if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) + state.activated.detach_vertex_named(state.name) + push_state_for_requirements(state.requirements.dup, false, state.activated) + else + states.push(state).tap { activated.tag(state) } + end + end + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/resolver.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/resolver.rb new file mode 100644 index 0000000000..86229c3fa1 --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/resolver.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative 'dependency_graph' + +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} + # + # + class Resolver + require_relative 'resolution' + + # @return [SpecificationProvider] the specification provider used + # in the resolution process + attr_reader :specification_provider + + # @return [UI] the UI module used to communicate back to the user + # during the resolution process + attr_reader :resolver_ui + + # Initializes a new resolver. + # @param [SpecificationProvider] specification_provider + # see {#specification_provider} + # @param [UI] resolver_ui + # see {#resolver_ui} + def initialize(specification_provider, resolver_ui) + @specification_provider = specification_provider + @resolver_ui = resolver_ui + end + + # Resolves the requested dependencies into a {DependencyGraph}, + # locking to the base dependency graph (if specified) + # @param [Array] requested an array of 'requested' dependencies that the + # {#specification_provider} can understand + # @param [DependencyGraph,nil] base the base dependency graph to which + # dependencies should be 'locked' + def resolve(requested, base = DependencyGraph.new) + Resolution.new(specification_provider, + resolver_ui, + requested, + base). + resolve + end + end +end diff --git a/lib/rubygems/vendor/molinillo/lib/molinillo/state.rb b/lib/rubygems/vendor/molinillo/lib/molinillo/state.rb new file mode 100644 index 0000000000..c48ec6af9c --- /dev/null +++ b/lib/rubygems/vendor/molinillo/lib/molinillo/state.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Gem::Molinillo + # A state that a {Resolution} can be in + # @attr [String] name the name of the current requirement + # @attr [Array] requirements currently unsatisfied requirements + # @attr [DependencyGraph] activated the graph of activated dependencies + # @attr [Object] requirement the current requirement + # @attr [Object] possibilities the possibilities to satisfy the current requirement + # @attr [Integer] depth the depth of the resolution + # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name + # @attr [Array] unused_unwind_options unwinds for previous conflicts that weren't explored + ResolutionState = Struct.new( + :name, + :requirements, + :activated, + :requirement, + :possibilities, + :depth, + :conflicts, + :unused_unwind_options + ) + + class ResolutionState + # Returns an empty resolution state + # @return [ResolutionState] an empty state + def self.empty + new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) + end + end + + # A state that encapsulates a set of {#requirements} with an {Array} of + # possibilities + class DependencyState < ResolutionState + # Removes a possibility from `self` + # @return [PossibilityState] a state with a single possibility, + # the possibility that was removed from `self` + def pop_possibility_state + PossibilityState.new( + name, + requirements.dup, + activated, + requirement, + [possibilities.pop], + depth + 1, + conflicts.dup, + unused_unwind_options.dup + ).tap do |state| + state.activated.tag(state) + end + end + end + + # A state that encapsulates a single possibility to fulfill the given + # {#requirement} + class PossibilityState < ResolutionState + end +end 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..25c870a591 --- /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 . +# HTTPS support added by GOTOU Yuuzou . +# +# 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}[https://docs.ruby-lang.org/en/master/OpenURI.html]. + # - 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}[https://docs.ruby-lang.org/en/master/Gem/URI/Generic.html] 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 'https' and 'http': + # + # 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::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::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 'http_proxy'. + # + # === 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') + # # => # + # 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 'ENV['http_proxy']' + # + # When environment variable 'http_proxy' + # 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 'http' or 'https': + # + # 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 'Accept-Encoding' + # 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 'Content-Range'. + # + # 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]: + # + # - 'deflate', 'gzip', or 'x-gzip': + # decompresses the body and deletes the header. + # - 'none' or 'identity': + # 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 Content-Length headers. + # - {:ignore_eof=}[rdoc-ref:Gem::Net::HTTP#ignore_eof=]: + # Sets whether to ignore end-of-file when reading a response body + # with Content-Length 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) # => # + # 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) # => # + # 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 http.start 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 http.start 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 opts[:use_ssl] 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 ENV['http_proxy'] 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) + # # => # + # 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) + # # => # + # 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 + # # => "#" + # + 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}[https://docs.ruby-lang.org/en/master/Encoding.html]. + # + # Examples: + # + # http = Gem::Net::HTTP.new(hostname) + # http.response_body_encoding = 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 + # 'ENV['http_proxy']'; + # 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') # => # + # 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 = < 60 + # http.post(_uri.path, data, headers) + # # => # + # 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 Content-Length 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=}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-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}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-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=}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D]. + attr_accessor :ssl_version + + # Sets or returns the minimum SSL version. + # See {OpenSSL::SSL::SSLContext#min_version=}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D]. + attr_accessor :min_version + + # Sets or returns the maximum SSL version. + # See {OpenSSL::SSL::SSLContext#max_version=}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-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=}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#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) + # # => # + # http.start + # # => # + # 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 + # # => # + # 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 # => # + # + # 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('/') # => # + # + # 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') # => # + # 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 # => # + # + # 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) # => # + # + # 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 # => # + # + # 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) # => # + # + 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) # => # + # + 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') # => # + # + # 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 # => # + # + # Output: + # + # # + # + 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') # => # + # + 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') + # # => # + # + # 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 # => # + # + # 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') + # # => # + # + 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') + # # => # + # http.send_request('POST', '/todos', 'xyzzy') + # # => # + # + 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) + # # => # + # + # req = Gem::Net::HTTP::Post.new('/todos') + # http.request(req, 'xyzzy') + # # => # + # + # 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 # => # + # + # Output: + # + # # + # + 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::Net::HTTP::Get.new('example.com').uri # => nil + # + attr_reader :uri + + # Returns +false+ if the request's header 'Accept-Encoding' + # 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) # => # + # 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 # => "#" + # + 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) # => # + # req.body_stream # => nil + # require 'stringio' + # req.body_stream = StringIO.new('xyzzy') # => # + # req.body_stream # => # + # + attr_reader :body_stream + + # Sets the body stream for the request: + # + # req = Gem::Net::HTTP::Post.new(uri) # => # + # req.body_stream # => nil + # require 'stringio' + # req.body_stream = StringIO.new('xyzzy') # => # + # req.body_stream # => # + # + 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 'Accept' is treated as if it were +# 'Accept'.downcase; i.e., 'accept'. +# - A symbol: Key :Accept is treated as if it were +# :Accept.to_s.downcase; i.e., 'accept'. +# +# 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 +# self[key.downcase.to_s].join(', ')): +# +# - #[]: 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 'Authorization'. +# - #content_length=: Sets the integer length for field 'Content-Length. +# - #content_type=: Sets the string value for field 'Content-Type'. +# - #proxy_basic_auth: Sets the string authorization header for 'Proxy-Authorization'. +# - #set_range: Sets the value for field 'Range'. +# +# === 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 'Content-Length'. +# - #content_range: Returns the Range value of field 'Content-Range'. +# - #content_type: Returns the string value of field 'Content-Type'. +# - #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 'Content-Type'. +# - #sub_type: Returns second part of the string value of field 'Content-Type'. +# - #range: Returns an array of Range objects of field 'Range', or +nil+. +# - #range_length: Returns the integer length of the range given in field 'Content-Range'. +# - #type_params: Returns the string parameters for 'Content-Type'. +# +# === Queries +# +# - #chunked?: Returns whether field 'Transfer-Encoding' is set to 'chunked'. +# - #connection_close?: Returns whether field 'Connection' is set to 'close'. +# - #connection_keep_alive?: Returns whether field 'Connection' is set to 'keep-alive'. +# - #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 'Range', + # 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 'Range'; + # 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 'Content-Length' 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 'Content-Length' 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) # => # + # 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 # => # + # + 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 'Transfer-Encoding' + # exists and has value 'chunked', + # +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 + # 'Content-Range', 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 + # 'Content-Range', 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 'Content-Type', + # 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 'Content-Type', + # 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 'Content-Type', + # 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 'Content-Type', + # 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 'Content-Type'; + # 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 'Content-Type' + # to 'application/x-www-form-urlencoded'. + # + # 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 '&': + # + # 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 params.map 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) + # + # Argument +params+ As an Array + # + # 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 + # 'multipart/form-data': + # + # - 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]]) + # + # Argument +params+ As a Hash + # + # 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 + # 'multipart/form-data'). + # + # 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 'Content-Type', and must be one of: + # + # - 'application/x-www-form-urlencoded' (the default). + # - 'multipart/form-data'; + # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578]. + # + # Optional argument +formopt+ is a hash of options + # (applicable only when argument +enctype+ + # is 'multipart/form-data') + # 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 'Authorization' 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 'Proxy-Authorization' 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) # => # +# req = Gem::Net::HTTP::Get.new(uri.hostname) # => # +# +# And with any of the subclasses: +# +# req = Gem::Net::HTTP::Head.new(uri) # => # +# req = Gem::Net::HTTP::Post.new(uri) # => # +# req = Gem::Net::HTTP::Put.new(uri) # => # +# # ... +# +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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) # => # +# 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::HTTP.get_response(hostname, '/nosuch') +# # => # +# +# 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 # => # +# +# \Class \Gem::Net::HTTPResponse includes module Gem::Net::HTTPHeader, +# which provides access to response header values via (among others): +# +# - \Hash-like method []. +# - Specific reader methods, such as +content_type+. +# +# Examples: +# +# res = Gem::Net::HTTP.get_response(uri) # => # +# 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 # => # + # end + # + # http.request(req) do |res| + # res.body_encoding = "UTF-8" + # p res.body.encoding # => # + # 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 dest#<< method (it could be String or IO, or any + # other object responding to <<). + # + # 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' + 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 Switching Protocol responses (status code 101). + # + # The Switching Protocol 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 Early Hints responses (status code 103). + # + # The Early Hints 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 Non-Authoritative Information responses (status code 203). + # + # The Non-Authoritative Information 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 No Content responses (status code 204). + # + # The No Content 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 Reset Content responses (status code 205). + # + # The Reset Content 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 Partial Content responses (status code 206). + # + # The Partial Content 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 Multi-Status (WebDAV) responses (status code 207). + # + # The Multi-Status (WebDAV) 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 Already Reported (WebDAV) responses (status code 208). + # + # The Already Reported (WebDAV) 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 IM Used responses (status code 226). + # + # The IM Used 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 Multiple Choices responses (status code 300). + # + # The Multiple Choices 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 Moved Permanently responses (status code 301). + # + # The Moved Permanently 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 Found responses (status code 302). + # + # The Found 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 See Other 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 Not Modified 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 Use Proxy 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 Temporary Redirect 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 Permanent Redirect 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 Bad Request 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 Unauthorized 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 Payment Required 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 Forbidden 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 Not Found 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 Method Not Allowed 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 Not Acceptable 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 Proxy Authentication Required 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 Request Gem::Timeout 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 Conflict 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 Gone 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 Length Required 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 Precondition Failed 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 Payload Too Large 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 Gem::URI Too Long 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 Unsupported Media Type 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 Range Not Satisfiable 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 Expectation Failed 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 Misdirected Request 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 Unprocessable Entity 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 Locked (WebDAV) 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 Failed Dependency (WebDAV) 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 Upgrade Required 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 Precondition Required 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 Too Many Requests 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 Request Header Fields Too Large 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 Unavailable For Legal Reasons 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 Internal Server Error 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 Not Implemented 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 Bad Gateway 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 Service Unavailable 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 Gateway Gem::Timeout 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 HTTP Version Not Supported 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 Variant Also Negotiates 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 Insufficient Storage (WebDAV) 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 Loop Detected (WebDAV) 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 Not Extended 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 Network Authentication Required 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 + 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 +# +# 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 `
': 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 +# # +# $ ruby optparse-test.rb --user 2 +# # +# $ 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 + # []= 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 + # []= 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 []= 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..1209d5167a --- /dev/null +++ b/lib/rubygems/vendor/resolv/lib/resolv.rb @@ -0,0 +1,3387 @@ +# 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.3.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, hostname, *aliases = line.split(/\s+/) + next unless addr + @addr2name[addr] = [] unless @addr2name.include? addr + @addr2name[addr] << hostname + @addr2name[addr].concat(aliases) + @name2addr[hostname] = [] unless @name2addr.include? hostname + @name2addr[hostname] << addr + aliases.each {|n| + @name2addr[n] = [] unless @name2addr.include? n + @name2addr[n] << 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(?\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 + + ClassInsensitiveTypes = [ # :nodoc: + NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY + ] + + ## + # 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 paramters 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 include + # Gem::Timeout 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", +# SIAM Journal on Computing, 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. + # obj.tsort_each is similar to obj.tsort.each, 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. + # obj.each_strongly_connected_component is similar to + # obj.strongly_connected_components.each, 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") +# #=> # +# +# 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") +# #=> # +# +# == 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 +# Documentation:: +# Akira Yamada +# Dmitry V. Sabanin +# Vincent Batts +# License:: +# Copyright (c) 2001 akira yamada +# 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 +# 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 Gem::URI.scheme_list[scheme.upcase]. + # - 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.for('foo', *values, default: Gem::URI::HTTP) + # # => # + # + 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.parse('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.join('http://example.com', 'foo') + # # => # + # + # Gem::URI.join('http://example.com', '/foo', '/bar') + # # => # + # + # Gem::URI.join('http://example.com', '/foo', 'bar') + # # => # + # + # Gem::URI.join('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 '*', '.', '-', and '_'. + # - Character in ranges 'a'..'z', 'A'..'Z', + # and '0'..'9'. + # + # Example: + # + # Gem::URI.encode_www_form_component('*.-_azAZ09') + # # => "*.-_azAZ09" + # + # - Converts: + # + # - Character ' ' to character '+'. + # - Any other character to "percent notation"; + # the percent notation for character c is '%%%X' % c.ord. + # + # 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 ' ' as '%20'). + 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 '*', '.', '-', and '_'. + # - Character in ranges 'a'..'z', 'A'..'Z', + # and '0'..'9'. + # + # Example: + # + # Gem::URI.decode_www_form_component('*.-_azAZ09') + # # => "*.-_azAZ09" + # + # - Converts: + # + # - Character '+' to character ' '. + # - 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 '+'). + 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 ' ' (space) + # is encoded as '%20' (instead of '+'). + 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 '+' 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 Content-Type is + # 'application/x-www-form-urlencoded'. + # + # The returned string consists of the elements of +enum+, + # each converted to one or more URL-encoded strings, + # and all joined with character '&'. + # + # 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 ele[0]: + # + # 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 res['Content-Type'] is 'application/x-www-form-urlencoded'. + # + # 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') + # # => # + # # Returns the given Gem::URI. + # Gem::URI(uri) + # # => # + # + 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 [host, path]. + # + # 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 +# 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 [userinfo, host, port, path, typecode]. + # + # 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") + # #=> # + # uri.typecode = "i" + # uri + # #=> # + # + 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: + # + # ftp://ftp.example.com/pub/ruby + # + # 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: + # + # ftp://ftp.example.com/%2Fpub/ruby + # + # 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 +# 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 [user, password] + # 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') + # #=> # + # + 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') + # #=> # + # + 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 + # #=> # + # + # 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") + # #=> [#, #] + # + 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 +# 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 . + # + 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 [userinfo, host, port, path, query, fragment]. + # + # 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 +# 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 +# Akira Yamada +# 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:///[?[?[?[?]]]] + #++ + 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 [host, port, dn, attributes, scope, filter, extensions]. + # + # 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 +# 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(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\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 [to, headers]. + # + # 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 + # "subject=subscribe&cc=address", or as an Array of Arrays + # like [['subject', 'subscribe'], ['cc', 'address']]. + # + # 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 +# 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.parse(u.to_s) #=> raises Gem::URI::InvalidURIError + # + # s = "http://example.com/ABCD" + # u1 = p.parse(s) #=> # + # u2 = Gem::URI.parse(s) #=> # + # 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") + # #=> # + # + 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[ + (?\[(?: + (? + (?:\h{1,4}:){6} + (?\h{1,4}:\h{1,4} + | (?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d) + \.\g\.\g\.\g) + ) + | ::(?:\h{1,4}:){5}\g + | \h{1,4}?::(?:\h{1,4}:){4}\g + | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g + | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g + | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g + | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g + | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4} + | (?:(?:\h{1,4}:){,6}\h{1,4})?:: + ) + | (?v\h++\.[!$&-.0-9:;=A-Z_a-z~]++) + )\]) + | \g + | (?(?:%\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}){0} + (? + (?#{SCHEME}): + (?// + (? + (?:(?#{USERINFO.source})@)? + (?#{HOST.source.delete(" \n")}) + (?::(?\d*+))? + ) + (?(?:/\g*+)?) + | (?/((?!/)\g++)?) + | (?(?!/)\g++) + | (?) + ) + (?:\?(?[^\#]*+))? + (?:\#(?#{FRAGMENT}))? + )\z]x + + RFC3986_relative_ref = %r[\A + (?#{SEG}){0} + (? + (?// + (? + (?:(?#{USERINFO.source})@)? + (?#{HOST.source.delete(" \n")}(?\d*+))? + ) + (?(?:/\g*+)?) + | (?/\g*+) + | (?#{SEG_NC}++(?:/\g*+)?) + | (?) + ) + (?:\?(?[^#]*+))? + (?:\#(?#{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 +# 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 . + # + 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 [userinfo, host, port, path, query]. + # + # 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 +# 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/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 947fda0911..128becc1ce 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -58,6 +58,8 @@ module Gem 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? @@ -72,6 +74,8 @@ module Gem 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) @@ -80,6 +84,14 @@ module Gem 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) -- cgit v1.2.3