diff --git a/CHANGELOG.md b/CHANGELOG.md index b1adf3e2e892..61232a9df3b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 4.0.21 / 2026-09-16 + +### Enhancements: + +* Stop vendoring resolv for two regexps. Pull request [#9877](https://github.com/ruby/rubygems/pull/9877) by Hiroshi SHIBATA +* Normalize absolute symlink targets during gem extraction. Pull request [#9860](https://github.com/ruby/rubygems/pull/9860) by Hiroshi SHIBATA +* Installs bundler 4.0.21 as a default gem. + ## 4.0.20 / 2026-09-02 ### Enhancements: diff --git a/Manifest.txt b/Manifest.txt index d92bdbcbf9d2..5304cd96eb12 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -578,8 +578,6 @@ lib/rubygems/vendor/optparse/lib/optparse/shellwords.rb lib/rubygems/vendor/optparse/lib/optparse/time.rb lib/rubygems/vendor/optparse/lib/optparse/uri.rb lib/rubygems/vendor/optparse/lib/optparse/version.rb -lib/rubygems/vendor/resolv/COPYING -lib/rubygems/vendor/resolv/lib/resolv.rb lib/rubygems/vendor/securerandom/COPYING lib/rubygems/vendor/securerandom/lib/securerandom.rb lib/rubygems/vendor/timeout/COPYING diff --git a/bundler/CHANGELOG.md b/bundler/CHANGELOG.md index bc86b803f881..18bfb4de170d 100644 --- a/bundler/CHANGELOG.md +++ b/bundler/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 4.0.21 / 2026-09-16 + +### Enhancements: + +* Don't update bundler to a prerelease unless asked for one. Pull request [#9869](https://github.com/ruby/rubygems/pull/9869) by Hiroshi SHIBATA +* Reject Bundler redirects that downgrade https to http. Pull request [#9859](https://github.com/ruby/rubygems/pull/9859) by Hiroshi SHIBATA + +### Bug fixes: + +* Support `safe.bareRepository=explicit` in git sources. Pull request [#9876](https://github.com/ruby/rubygems/pull/9876) by Hiroshi SHIBATA +* Stop resolving locally for empty CHECKSUMS entries. Pull request [#9862](https://github.com/ruby/rubygems/pull/9862) by Hiroshi SHIBATA +* Expand the git source gemspec path before the chdir. Pull request [#9845](https://github.com/ruby/rubygems/pull/9845) by Hiroshi SHIBATA + ## 4.0.20 / 2026-09-02 ### Enhancements: diff --git a/bundler/lib/bundler/cli.rb b/bundler/lib/bundler/cli.rb index 28b65e467baf..336ca72192b0 100644 --- a/bundler/lib/bundler/cli.rb +++ b/bundler/lib/bundler/cli.rb @@ -317,7 +317,7 @@ def install method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)" method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock" - method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" + method_option "bundler", type: :string, lazy_default: ">= #{Bundler::VERSION}", banner: "Update the locked version of bundler" method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version" method_option "minor", type: :boolean, banner: "Prefer updating only to next minor version" method_option "major", type: :boolean, banner: "Prefer updating to next major version (default)" @@ -642,7 +642,7 @@ def inject(*) 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 "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" + method_option "bundler", type: :string, lazy_default: ">= #{Bundler::VERSION}", banner: "Update the locked version of bundler" method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable." def lock require_relative "cli/lock" diff --git a/bundler/lib/bundler/cli/update.rb b/bundler/lib/bundler/cli/update.rb index 1444668bd487..23c198964c1d 100644 --- a/bundler/lib/bundler/cli/update.rb +++ b/bundler/lib/bundler/cli/update.rb @@ -13,7 +13,7 @@ def run update_bundler = options[:bundler] - Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler + Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler, pre: options[:pre]) if update_bundler Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] diff --git a/bundler/lib/bundler/definition.rb b/bundler/lib/bundler/definition.rb index 8b4fb4b7a694..65b3d53d2a22 100644 --- a/bundler/lib/bundler/definition.rb +++ b/bundler/lib/bundler/definition.rb @@ -192,7 +192,7 @@ def setup_domain!(options = {}) sources.cached! - if options[:add_checksums] || (!options[:local] && install_needed?) + if options[:add_checksums] || (!options[:local] && (install_needed? || @locked_spec_with_empty_checksums)) sources.remote! true else @@ -639,7 +639,7 @@ def something_changed? @missing_lockfile_dep || @unlocking_bundler || @locked_spec_with_missing_checksums || - @locked_spec_with_empty_checksums || + empty_checksums_actionable? || @locked_spec_with_missing_deps || @locked_spec_with_invalid_deps end @@ -648,6 +648,16 @@ def resolve_needed? unlocking? || something_changed? end + # Only a remote fetch can fill an empty CHECKSUMS entry, so it justifies a + # resolution only when one is coming. Resolving locally for it would repeat + # on every `Bundler.setup` without changing the lockfile. Frozen mode still + # has to refuse the entry. + def empty_checksums_actionable? + return false unless @locked_spec_with_empty_checksums + + Bundler.frozen_bundle? || !sources.local_mode? + end + def should_add_extra_platforms? !lockfile_exists? && Bundler::MatchPlatform.generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform] end diff --git a/bundler/lib/bundler/fetcher/downloader.rb b/bundler/lib/bundler/fetcher/downloader.rb index 179eed83401f..d9cb819d55ef 100644 --- a/bundler/lib/bundler/fetcher/downloader.rb +++ b/bundler/lib/bundler/fetcher/downloader.rb @@ -46,6 +46,11 @@ def fetch(uri, headers = {}, counter = 0) response when Gem::Net::HTTPRedirection new_uri = Gem::URI.parse(response["location"]) + # Following a downgrade would put the credentials this request carries + # on a plaintext connection, so refuse it like Gem::RemoteFetcher does. + if https?(uri) && !https?(new_uri) + raise HTTPError, "Redirecting to a non-https URI is not allowed: #{URICredentialsFilter.credential_filtered_uri(new_uri)}" + end if new_uri.host == uri.host new_uri.user = uri.user new_uri.password = uri.password @@ -105,6 +110,10 @@ def request(uri, headers) private + def https?(uri) + uri.scheme&.casecmp("https")&.zero? + end + def validate_uri_scheme!(uri) return if /\Ahttps?\z/.match?(uri.scheme) raise InvalidOption, diff --git a/bundler/lib/bundler/man/bundle-lock.1 b/bundler/lib/bundler/man/bundle-lock.1 index fedb10d66b81..130b50fe0675 100644 --- a/bundler/lib/bundler/man/bundle-lock.1 +++ b/bundler/lib/bundler/man/bundle-lock.1 @@ -13,7 +13,7 @@ Lock the gems specified in Gemfile\. Ignores the existing lockfile\. Resolve then updates lockfile\. Taking a list of gems or updating all gems if no list is given\. .TP \fB\-\-bundler[=BUNDLER]\fR -Update the locked version of bundler to the given version or the latest version if no version is given\. +Update the locked version of bundler\. BUNDLER can be a version such as \fB4\.0\.20\fR, or a requirement such as \fB"> 0\.a"\fR\. With no argument, update to the latest released version, which never selects a prerelease\. .TP \fB\-\-local\fR Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems' cache or in \fBvendor/cache\fR\. Note that if a appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\. diff --git a/bundler/lib/bundler/man/bundle-lock.1.ronn b/bundler/lib/bundler/man/bundle-lock.1.ronn index 683a429b0e1e..96c37a66b7e4 100644 --- a/bundler/lib/bundler/man/bundle-lock.1.ronn +++ b/bundler/lib/bundler/man/bundle-lock.1.ronn @@ -33,8 +33,9 @@ Lock the gems specified in Gemfile. of gems or updating all gems if no list is given. * `--bundler[=BUNDLER]`: - Update the locked version of bundler to the given version or the latest - version if no version is given. + Update the locked version of bundler. BUNDLER can be a version such as + `4.0.20`, or a requirement such as `"> 0.a"`. With no argument, update to the + latest released version, which never selects a prerelease. * `--local`: Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the diff --git a/bundler/lib/bundler/man/bundle-update.1 b/bundler/lib/bundler/man/bundle-update.1 index f59616f3233f..1e9081b94af1 100644 --- a/bundler/lib/bundler/man/bundle-update.1 +++ b/bundler/lib/bundler/man/bundle-update.1 @@ -27,7 +27,7 @@ Do not attempt to fetch gems remotely and use the gem cache instead\. Update the locked version of Ruby to the current version of Ruby\. .TP \fB\-\-bundler[=BUNDLER]\fR -Update the locked version of bundler to the invoked bundler version\. +Update the locked version of bundler\. BUNDLER can be a version such as \fB4\.0\.20\fR, or a requirement such as \fB"> 0\.a"\fR\. With no argument, update to the latest released version, which never selects a prerelease\. .TP \fB\-\-force\fR, \fB\-\-redownload\fR Force reinstalling every gem, even if already installed\. diff --git a/bundler/lib/bundler/man/bundle-update.1.ronn b/bundler/lib/bundler/man/bundle-update.1.ronn index 72fbf054d157..659ec2da7a06 100644 --- a/bundler/lib/bundler/man/bundle-update.1.ronn +++ b/bundler/lib/bundler/man/bundle-update.1.ronn @@ -53,7 +53,9 @@ gem. Update the locked version of Ruby to the current version of Ruby. * `--bundler[=BUNDLER]`: - Update the locked version of bundler to the invoked bundler version. + Update the locked version of bundler. BUNDLER can be a version such as + `4.0.20`, or a requirement such as `"> 0.a"`. With no argument, update to the + latest released version, which never selects a prerelease. * `--force`, `--redownload`: Force reinstalling every gem, even if already installed. diff --git a/bundler/lib/bundler/self_manager.rb b/bundler/lib/bundler/self_manager.rb index 4e6156ffa2f5..aa948841ba5a 100644 --- a/bundler/lib/bundler/self_manager.rb +++ b/bundler/lib/bundler/self_manager.rb @@ -30,8 +30,8 @@ def install_locked_bundler_and_restart_with_it_if_needed install_and_restart_with(restart_version) end - def update_bundler_and_restart_with_it_if_needed(target) - spec = resolve_update_version_from(target) + def update_bundler_and_restart_with_it_if_needed(target, pre: false) + spec = resolve_update_version_from(target, pre: pre) return unless spec version = spec.version @@ -108,9 +108,9 @@ def autoswitching_applies? lockfile_version end - def resolve_update_version_from(target) + def resolve_update_version_from(target, pre: false) requirement = Gem::Requirement.new(target) - update_candidate = find_latest_matching_spec(requirement) + update_candidate = find_latest_matching_spec(requirement, pre: pre) if update_candidate.nil? raise InvalidOption, "The `bundle update --bundler` target version (#{target}) does not exist" @@ -137,18 +137,24 @@ def remote_specs end end - def find_latest_matching_spec(requirement) + def find_latest_matching_spec(requirement, pre: false) Bundler.configure - local_result = find_latest_matching_spec_from_collection(local_specs, requirement) + # A bare `bundle update --bundler` must stay on releases, like `gem update + # --system`, so only `--pre` or a prerelease requirement opts into one. + allow_prerelease = pre || requirement.prerelease? + + local_result = find_latest_matching_spec_from_collection(local_specs, requirement, allow_prerelease) return local_result if local_result && requirement.specific? - remote_result = find_latest_matching_spec_from_collection(remote_specs, requirement) + remote_result = find_latest_matching_spec_from_collection(remote_specs, requirement, allow_prerelease) return remote_result if local_result.nil? - [local_result, remote_result].max + [local_result, remote_result].compact.max end - def find_latest_matching_spec_from_collection(specs, requirement) + def find_latest_matching_spec_from_collection(specs, requirement, allow_prerelease) + specs = specs.reject {|spec| spec.version.prerelease? } unless allow_prerelease + specs.sort.reverse_each.find {|spec| requirement.satisfied_by?(spec.version) } end diff --git a/bundler/lib/bundler/source/git.rb b/bundler/lib/bundler/source/git.rb index e57e1164a018..d47eccc7ce7f 100644 --- a/bundler/lib/bundler/source/git.rb +++ b/bundler/lib/bundler/source/git.rb @@ -423,10 +423,12 @@ def fetch def validate_spec(_spec); end def load_gemspec(file) - dirname = Pathname.new(file).dirname - SharedHelpers.chdir(dirname.to_s) do - stub = Gem::StubSpecification.gemspec_stub(file, install_path.parent, install_path.parent) - stub.full_gem_path = dirname.expand_path(root).to_s + # Expand the path before the chdir below, since resolving it inside the + # block would base it on the gemspec directory instead of `root`. + gemspec_path = Pathname.new(file).expand_path(root) + SharedHelpers.chdir(gemspec_path.dirname.to_s) do + stub = Gem::StubSpecification.gemspec_stub(gemspec_path.to_s, install_path.parent, install_path.parent) + stub.full_gem_path = gemspec_path.dirname.to_s StubSpecification.from_stub(stub) end end diff --git a/bundler/lib/bundler/source/git/git_proxy.rb b/bundler/lib/bundler/source/git/git_proxy.rb index 42f1e9dd71ff..c22f5919eff1 100644 --- a/bundler/lib/bundler/source/git/git_proxy.rb +++ b/bundler/lib/bundler/source/git/git_proxy.rb @@ -507,7 +507,15 @@ def capture3_args_for(cmd, dir) return ["git", *opts, *cmd] unless dir - ["git", "-C", dir.to_s, *opts, *cmd] + # With safe.bareRepository=explicit, git refuses to discover a bare + # repository from -C, so the cache clone is named with --git-dir. + # Working trees, like a local override, still go through -C. + location = bare_repo?(dir) ? "--git-dir" : "-C" + ["git", location, dir.to_s, *opts, *cmd] + end + + def bare_repo?(dir) + File.exist?(File.join(dir, "objects")) && File.exist?(File.join(dir, "HEAD")) end def extra_clone_args diff --git a/bundler/lib/bundler/version.rb b/bundler/lib/bundler/version.rb index c2fcaaaa302c..791b1001322a 100644 --- a/bundler/lib/bundler/version.rb +++ b/bundler/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.20".freeze + VERSION = "4.0.21".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/bundler/spec/bundler/fetcher/downloader_spec.rb b/bundler/spec/bundler/fetcher/downloader_spec.rb index edf426328a4d..8cca6a56a2a7 100644 --- a/bundler/spec/bundler/fetcher/downloader_spec.rb +++ b/bundler/spec/bundler/fetcher/downloader_spec.rb @@ -57,6 +57,19 @@ subject.fetch(uri, options, counter) end end + + context "when the redirect uri downgrades https to http" do + let(:uri) { Gem::URI("https://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + + before { http_response["location"] = "http://www.uri-to-fetch.com/api/v2/endpoint" } + + it "should raise a Bundler::HTTPError instead of following the redirect" do + expect(subject).to receive(:fetch).with(uri, options, 0).and_call_original + expect(subject).not_to receive(:fetch).with(Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 1) + expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, + "Redirecting to a non-https URI is not allowed: http://www.uri-to-fetch.com/api/v2/endpoint") + end + end end context "when the request response is a Gem::Net::HTTPSuccess" do diff --git a/bundler/spec/bundler/source/git_spec.rb b/bundler/spec/bundler/source/git_spec.rb index 14e91c6bdce2..ce4cef5e90b9 100644 --- a/bundler/spec/bundler/source/git_spec.rb +++ b/bundler/spec/bundler/source/git_spec.rb @@ -120,4 +120,30 @@ end end end + + describe "#load_gemspec" do + let(:options) do + { "uri" => uri, "revision" => "123abc" } + end + + before do + allow(Bundler).to receive(:root).and_return(tmp) + allow(subject).to receive(:install_path).and_return(tmp("install/bar-123abc")) + + create_file(tmp("bar/bar.gemspec"), <<~GEMSPEC) + Gem::Specification.new do |s| + s.name = "bar" + s.version = "1.0" + end + GEMSPEC + end + + it "resolves a relative path against the root, not the gemspec directory" do + spec = Dir.chdir(tmp) { subject.send(:load_gemspec, "bar/bar.gemspec") } + + expect(spec.name).to eq("bar") + expect(spec.loaded_from).to eq(tmp("bar/bar.gemspec").to_s) + expect(spec.full_gem_path).to eq(tmp("bar").to_s) + end + end end diff --git a/bundler/spec/commands/lock_spec.rb b/bundler/spec/commands/lock_spec.rb index 15f9d0122f47..eb6d67b92844 100644 --- a/bundler/spec/commands/lock_spec.rb +++ b/bundler/spec/commands/lock_spec.rb @@ -885,6 +885,26 @@ expect(lockfile).to end_with("BUNDLED WITH\n 99\n") end + it "does not update the bundler version in the lockfile to a prerelease version, unless the target version allows prereleases" do + build_repo4 do + build_gem "bundler", "55" + build_gem "bundler", "56.0.0.beta1" + end + + system_gems "bundler-55", gem_repo: gem_repo4 + + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "https://gem.repo4" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') + + bundle "lock --update --bundler --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(lockfile).to end_with("BUNDLED WITH\n 55\n") + + bundle "lock --update --bundler '> 0.a' --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(lockfile).to end_with("BUNDLED WITH\n 56.0.0.beta1\n") + end + it "supports adding new platforms when there's no previous lockfile" do gemfile_with_rails_weakling_and_foo_from_repo4 diff --git a/bundler/spec/commands/update_spec.rb b/bundler/spec/commands/update_spec.rb index 60a0594f62ce..58fee0d6995b 100644 --- a/bundler/spec/commands/update_spec.rb +++ b/bundler/spec/commands/update_spec.rb @@ -1546,7 +1546,7 @@ bundle :update, bundler: true, verbose: true expect(out).to include("Updating bundler to 999.0.0") - expect(out).to include("Running `bundle update --bundler \"> 0.a\" --verbose` with bundler 999.0.0") + expect(out).to include("Running `bundle update --bundler \">= 999.0.0\" --verbose` with bundler 999.0.0") expect(out).not_to include("Installing Bundler 2.99.9 and restarting using that version.") expect(lockfile).to eq <<~L @@ -1652,6 +1652,180 @@ expect(the_bundle).to include_gems "myrack 1.0" end + it "does not update the bundler version in the lockfile to a prerelease version", :ruby_repo do + pristine_system_gems "bundler-9.9.9" + + build_repo4 do + build_gem "myrack", "1.0" + + build_bundler "9.9.9" + build_bundler "999.0.0.beta1" + end + + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "9.9.9") + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + bundle :update, bundler: true, verbose: true + + expect(out).to include("Using bundler 9.9.9") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + 9.9.9 + L + end + + it "updates the bundler version in the lockfile to a prerelease version when the target version allows prereleases", :ruby_repo do + bundle_config "path.system true" + + pristine_system_gems "bundler-9.0.0" + + build_repo4 do + build_gem "myrack", "1.0" + + build_bundler "999.0.0.beta1" + end + + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "999.0.0.beta1") + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + bundle "update --bundler '> 0.a' --verbose" + + expect(out).to include("Updating bundler to 999.0.0.beta1") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + 999.0.0.beta1 + L + end + + it "goes back to a released version given explicitly when the lockfile is locked to a prerelease", :ruby_repo do + bundle_config "path.system true" + + pristine_system_gems "bundler-9.0.0.beta1" + + build_repo4 do + build_gem "myrack", "1.0" + + build_bundler "9.0.0" + end + + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "9.0.0") + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + # Auto switching puts the beta back in charge on every command, so an + # explicit target is the only way out of a lockfile that names one. + expect(lockfile).to match(/BUNDLED WITH\n\s+9\.0\.0\.beta1\n/) + + bundle "update --bundler 9.0.0 --verbose" + + expect(out).to include("Updating bundler to 9.0.0") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + 9.0.0 + L + + bundle "--version" + expect(out).to include("9.0.0") + end + + it "updates the bundler version in the lockfile to a prerelease version when --pre is given", :ruby_repo do + bundle_config "path.system true" + + pristine_system_gems "bundler-9.0.0" + + build_repo4 do + build_gem "myrack", "1.0" + + build_bundler "999.0.0.beta1" + end + + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "999.0.0.beta1") + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + bundle :update, bundler: true, pre: true, verbose: true + + expect(out).to include("Updating bundler to 999.0.0.beta1") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + 999.0.0.beta1 + L + end + it "errors if the explicit target version does not exist" do pristine_system_gems "bundler-9.9.9" diff --git a/bundler/spec/install/gemfile/git_spec.rb b/bundler/spec/install/gemfile/git_spec.rb index 24e697b76fa4..8030e92588f9 100644 --- a/bundler/spec/install/gemfile/git_spec.rb +++ b/bundler/spec/install/gemfile/git_spec.rb @@ -31,6 +31,17 @@ expect(out).to eq("WIN") end + it "installs and updates gems when git only accepts explicit bare repositories" do + git "config --global safe.bareRepository explicit" + + install_base_gemfile + expect(the_bundle).to include_gems("foo 1.0") + + update_git "foo", "1.1", path: lib_path("foo-1.0") + bundle "update foo" + expect(the_bundle).to include_gems("foo 1.1") + end + it "points the installed copy's origin at the real remote, not the local cache" do install_base_gemfile diff --git a/bundler/spec/lock/lockfile_spec.rb b/bundler/spec/lock/lockfile_spec.rb index 4cd371767d3f..c42628cbdc64 100644 --- a/bundler/spec/lock/lockfile_spec.rb +++ b/bundler/spec/lock/lockfile_spec.rb @@ -1679,6 +1679,36 @@ expect(the_bundle).not_to include_gems "myrack 0.9.1" end + it "fills empty CHECKSUMS entries when not frozen, even if every gem is already installed" do + system_gems "myrack-0.9.1", gem_repo: gem_repo2 + + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + CHECKSUMS + myrack (0.9.1) + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + + expect(lockfile).to include(" #{checksum_to_lock(gem_repo2, "myrack", "0.9.1")}\n") + end + it "automatically fixes the lockfile when it's missing deps, they conflict with other locked deps, but conflicts are fixable" do build_repo4 do build_gem "other_dep", "0.9" diff --git a/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock b/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock index 487676890e07..a70a2ae93537 100644 --- a/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock +++ b/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/bundler/spec/realworld/fixtures/warbler/Gemfile.lock b/bundler/spec/realworld/fixtures/warbler/Gemfile.lock index 56d86f765e60..3f304aa8563a 100644 --- a/bundler/spec/realworld/fixtures/warbler/Gemfile.lock +++ b/bundler/spec/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/bundler/spec/runtime/env_helpers_spec.rb b/bundler/spec/runtime/env_helpers_spec.rb index c4ebdd1fd289..38f501e7cde6 100644 --- a/bundler/spec/runtime/env_helpers_spec.rb +++ b/bundler/spec/runtime/env_helpers_spec.rb @@ -21,7 +21,7 @@ def run_bundler_script(env, script) create_file("source.rb", <<-RUBY) print Bundler.original_env["PATH"] RUBY - path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo" + path = default_system_path + "#{File::PATH_SEPARATOR}/foo" with_path_as(path) do bundle_exec_ruby(bundled_app("source.rb").to_s) expect(stdboth).to eq(path) @@ -49,7 +49,7 @@ def run_bundler_script(env, script) end exec(Gem.ruby, __FILE__, (count - 1).to_s) RUBY - path = `getconf PATH`.strip + File::PATH_SEPARATOR + File.dirname(Gem.ruby) + path = default_system_path + File::PATH_SEPARATOR + File.dirname(Gem.ruby) with_path_as(path) do build_bundler_context bundle_exec_ruby("#{bundled_app("exe.rb")} 2") diff --git a/bundler/spec/runtime/setup_spec.rb b/bundler/spec/runtime/setup_spec.rb index b2857e3b445c..8f8f1e01c4ff 100644 --- a/bundler/spec/runtime/setup_spec.rb +++ b/bundler/spec/runtime/setup_spec.rb @@ -259,6 +259,24 @@ def clean_load_path(lp) expect(bundled_app_lock).to exist end + it "does not resolve again for the empty CHECKSUMS entries it locked itself" do + system_gems "myrack-1.0.0" + + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + ruby "require 'bundler'; Bundler.setup" + expect(out).to include("Resolving dependencies...") + lockfile = File.read(bundled_app_lock) + expect(lockfile).to match(/^CHECKSUMS\n(?: .+\n)* myrack \(1\.0\.0\)\n/) + + ruby "require 'bundler'; Bundler.setup" + expect(out).not_to include("Resolving dependencies...") + expect(File.read(bundled_app_lock)).to eq(lockfile) + end + describe "$BUNDLE_GEMFILE" do context "user provides an absolute path" do it "uses BUNDLE_GEMFILE to locate the gemfile if present" do diff --git a/bundler/spec/support/helpers.rb b/bundler/spec/support/helpers.rb index 09452fb56629..dd80d5d4c176 100644 --- a/bundler/spec/support/helpers.rb +++ b/bundler/spec/support/helpers.rb @@ -422,6 +422,12 @@ def with_gem_path_as(path) end end + # Windows has no `getconf`, and its current PATH holds the directories the + # Ruby under test finds its DLLs in, so keep it as is there. + def default_system_path + Gem.win_platform? ? ENV["PATH"] : `getconf PATH`.strip + end + def with_path_as(path) without_env_side_effects do ENV["PATH"] = path.to_s diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 6f2825913730..e6db0d71d05e 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.20" + VERSION = "4.0.21" end require_relative "rubygems/defaults" diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 75a3a6aa327d..7040fbe0530e 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -429,7 +429,7 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: if entry.symlink? link_target = entry.header.linkname - real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination)) + real_destination = File.expand_path(link_target, File.dirname(destination)) raise Gem::Package::SymlinkError.new(full_name, real_destination, destination_dir) unless normalize_path(real_destination).start_with? normalize_path(destination_dir + "/") diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb index b5b93befffd4..e1ccc0561004 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http.rb @@ -22,7 +22,6 @@ require_relative '../../../net-protocol/lib/net/protocol' require_relative '../../../uri/lib/uri' -require_relative '../../../resolv/lib/resolv' autoload :OpenSSL, 'openssl' module Gem::Net #:nodoc: @@ -1531,6 +1530,29 @@ def use_ssl=(flag) SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: + # Resolv::IPv4::Regex and Resolv::IPv6::Regex, copied from resolv 0.7.1 rather + # than required, so that net/http does not load a DNS resolver just to keep an + # IP address literal out of the Server Name Indication. + # https://github.com/ruby/resolv/blob/v0.7.1/lib/resolv.rb + ipv4_octet = /0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?/ + hex16 = /[0-9A-Fa-f]{1,4}/ + hex16_group = /(?:#{hex16}(?::#{hex16})*)?/ + dotted_quad = /\d+\.\d+\.\d+\.\d+/ + zone_id = /%[-0-9A-Za-z._~]+/ + + IPV4_ADDRESS = /\A(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\z/ # :nodoc: + + IPV6_ADDRESS = /\A(?: + (?:#{hex16}:){7}#{hex16} # a:b:c:d:e:f:g:h + | #{hex16_group}::#{hex16_group} # a::b + | (?:#{hex16}:){6}#{dotted_quad} # a:b:c:d:e:f:w.x.y.z + | #{hex16_group}::(?:#{hex16}:)*#{dotted_quad} # a::b:w.x.y.z + | [Ff][Ee]80(?::#{hex16}){7}#{zone_id} # fe80:b:c:d:e:f:g:h%em1 + | [Ff][Ee]80:(?:#{hex16_group}::#{hex16_group}|:#{hex16_group})?:#{hex16}#{zone_id} # fe80::b%em1 + )\z/x # :nodoc: + + private_constant :IPV4_ADDRESS, :IPV6_ADDRESS + # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file @@ -1714,7 +1736,7 @@ def connect # Server Name Indication (SNI) RFC 3546/6066 case @address - when Gem::Resolv::IPv4::Regex, Gem::Resolv::IPv6::Regex + when IPV4_ADDRESS, IPV6_ADDRESS # don't set SNI, as IP addresses in SNI is not valid # per RFC 6066, section 3. diff --git a/lib/rubygems/vendor/resolv/COPYING b/lib/rubygems/vendor/resolv/COPYING deleted file mode 100644 index 48e5a96de7c8..000000000000 --- a/lib/rubygems/vendor/resolv/COPYING +++ /dev/null @@ -1,56 +0,0 @@ -Ruby is copyrighted free software by Yukihiro Matsumoto . -You can redistribute it and/or modify it under either the terms of the -2-clause BSDL (see the file BSDL), or the conditions below: - -1. You may make and give away verbatim copies of the source form of the - software without restriction, provided that you duplicate all of the - original copyright notices and associated disclaimers. - -2. You may modify your copy of the software in any way, provided that - you do at least ONE of the following: - - a. place your modifications in the Public Domain or otherwise - make them Freely Available, such as by posting said - modifications to Usenet or an equivalent medium, or by allowing - the author to include your modifications in the software. - - b. use the modified software only within your corporation or - organization. - - c. give non-standard binaries non-standard names, with - instructions on where to get the original software distribution. - - d. make other distribution arrangements with the author. - -3. You may distribute the software in object code or binary form, - provided that you do at least ONE of the following: - - a. distribute the binaries and library files of the software, - together with instructions (in the manual page or equivalent) - on where to get the original distribution. - - b. accompany the distribution with the machine-readable source of - the software. - - c. give non-standard binaries non-standard names, with - instructions on where to get the original software distribution. - - d. make other distribution arrangements with the author. - -4. You may modify and include the part of the software into any other - software (possibly commercial). But some files in the distribution - are not written by the author, so that they are not under these terms. - - For the list of those files and their copying conditions, see the - file LEGAL. - -5. The scripts and library files supplied as input to or produced as - output from the software do not automatically fall under the - copyright of the software, but belong to whomever generated them, - and may be sold commercially, and may be aggregated with this - software. - -6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE. diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb deleted file mode 100644 index 4f48e0642bf4..000000000000 --- a/lib/rubygems/vendor/resolv/lib/resolv.rb +++ /dev/null @@ -1,3499 +0,0 @@ -# frozen_string_literal: true - -require 'socket' -require_relative '../../../vendored_timeout' -require 'io/wait' -require_relative '../../../vendored_securerandom' -require 'rbconfig' - -# 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 - - # The version string - VERSION = "0.7.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+. - # - # If +resolvers+ is not given, a hash, or +nil+, uses a Hosts resolver and - # and a DNS resolver. If +resolvers+ is a hash, uses the hash as - # configuration for the DNS resolver. - - def initialize(resolvers=(arg_not_set = true; nil), use_ipv6: (keyword_not_set = true; nil)) - if !keyword_not_set && !arg_not_set - warn "Support for separate use_ipv6 keyword is deprecated, as it is ignored if an argument is provided. Do not provide a positional argument if using the use_ipv6 keyword argument.", uplevel: 1 - end - - @resolvers = case resolvers - when Hash, nil - [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(resolvers || {}))] - else - resolvers - end - 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|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ - begin - require 'win32/resolv' unless defined?(Win32::Resolv) - hosts = Win32::Resolv.get_hosts_path || IO::NULL - rescue LoadError - end - end - # The default file name for host names - DefaultFileName = hosts || '/etc/hosts' - - ## - # Creates a new Gem::Resolv::Hosts, using +filename+ for its data source. - - def initialize(filename = DefaultFileName) - @filename = filename - @mutex = Thread::Mutex.new - @initialized = nil - end - - def lazy_initialize # :nodoc: - @mutex.synchronize { - unless @initialized - @name2addr = {} - @addr2name = {} - File.open(@filename, 'rb') {|f| - f.each {|line| - line.sub!(/#.*/, '') - addr, *hostnames = line.split(/\s+/) - next unless addr - (@addr2name[addr] ||= []).concat(hostnames) - hostnames.each {|hostname| (@name2addr[hostname] ||= []) << addr} - } - } - @name2addr.each {|name, arr| arr.reverse!} - @initialized = true - end - } - self - end - - ## - # Gets the IP address of +name+ from the hosts file. - - def getaddress(name) - each_address(name) {|address| return address} - raise ResolvError.new("#{@filename} has no name: #{name}") - end - - ## - # Gets all IP addresses for +name+ from the hosts file. - - def getaddresses(name) - ret = [] - each_address(name) {|address| ret << address} - return ret - end - - ## - # Iterates over all IP addresses for +name+ retrieved from the hosts file. - - def each_address(name, &proc) - lazy_initialize - @name2addr[name]&.each(&proc) - end - - ## - # Gets the hostname of +address+ from the hosts file. - - def getname(address) - each_name(address) {|name| return name} - raise ResolvError.new("#{@filename} has no address: #{address}") - end - - ## - # Gets all hostnames for +address+ from the hosts file. - - def getnames(address) - ret = [] - each_name(address) {|name| ret << name} - return ret - end - - ## - # Iterates over all hostnames for +address+ retrieved from the hosts file. - - def each_name(address, &proc) - lazy_initialize - @addr2name[address]&.each(&proc) - end - end - - ## - # Gem::Resolv::DNS is a DNS stub resolver. - # - # Information taken from the following places: - # - # * STD0013 - # * RFC 1035 - # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters - # * etc. - - class DNS - - ## - # Default DNS Port - - Port = 53 - - ## - # Default DNS UDP packet size - - UDPSize = 512 - - ## - # Creates a new DNS resolver. See Gem::Resolv::DNS.new for argument details. - # - # Yields the created DNS resolver to the block, if given, otherwise - # returns it. - - def self.open(*args) - dns = new(*args) - return dns unless block_given? - begin - yield dns - ensure - dns.close - end - end - - ## - # Creates a new DNS resolver. - # - # +config_info+ can be: - # - # nil:: Uses /etc/resolv.conf. - # String:: Path to a file using /etc/resolv.conf's format. - # Hash:: Must contain :nameserver, :search and :ndots keys. - # :nameserver_port can be used to specify port number of nameserver address. - # :raise_timeout_errors can be used to raise timeout errors - # as exceptions instead of treating the same as an NXDOMAIN response. - # - # The value of :nameserver should be an address string or - # an array of address strings. - # - :nameserver => '8.8.8.8' - # - :nameserver => ['8.8.8.8', '8.8.4.4'] - # - # The value of :nameserver_port should be an array of - # pair of nameserver address and port number. - # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]] - # - # Example: - # - # Gem::Resolv::DNS.new(:nameserver => ['210.251.121.21'], - # :search => ['ruby-lang.org'], - # :ndots => 1) - - def initialize(config_info=nil) - @mutex = Thread::Mutex.new - @config = Config.new(config_info) - @initialized = nil - end - - # Sets the resolver timeouts. This may be a single positive number - # or an array of positive numbers representing timeouts in seconds. - # If an array is specified, a DNS request will retry and wait for - # each successive interval in the array until a successful response - # is received. Specifying +nil+ reverts to the default timeouts: - # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ] - # - # Example: - # - # dns.timeouts = 3 - # - def timeouts=(values) - @config.timeouts = values - end - - def lazy_initialize # :nodoc: - @mutex.synchronize { - unless @initialized - @config.lazy_initialize - @initialized = true - end - } - self - end - - ## - # Closes the DNS resolver. - - def close - @mutex.synchronize { - if @initialized - @initialized = false - end - } - end - - ## - # Gets the IP address of +name+ from the DNS resolver. - # - # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved address will - # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 - - def getaddress(name) - each_address(name) {|address| return address} - raise ResolvError.new("DNS result has no information for #{name}") - end - - ## - # Gets all IP addresses for +name+ from the DNS resolver. - # - # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will - # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 - - def getaddresses(name) - ret = [] - each_address(name) {|address| ret << address} - return ret - end - - ## - # Iterates over all IP addresses for +name+ retrieved from the DNS - # resolver. - # - # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will - # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 - - def each_address(name) - if use_ipv6? - each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address} - end - each_resource(name, Resource::IN::A) {|resource| yield resource.address} - end - - def use_ipv6? # :nodoc: - @config.lazy_initialize unless @config.instance_variable_get(:@initialized) - - 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 - - # :stopdoc: - - def fetch_resource(name, typeclass) - lazy_initialize - truncated = {} - requesters = {} - udp_requester = begin - make_udp_requester - rescue Errno::EACCES - # fall back to TCP - end - senders = {} - - begin - @config.resolv(name) do |candidate, tout, nameserver, port| - msg = Message.new - msg.rd = 1 - msg.add_question(candidate, typeclass) - - requester = requesters.fetch([nameserver, port]) do - if !truncated[candidate] && udp_requester - udp_requester - else - requesters[[nameserver, port]] = make_tcp_requester(nameserver, port) - end - end - - unless sender = senders[[candidate, requester, nameserver, port]] - sender = requester.sender(msg, candidate, nameserver, port) - next if !sender - senders[[candidate, requester, 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 - # Retry via TCP: - truncated[candidate] = true - 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 - end - ensure - udp_requester&.close - requesters.each_value { |requester| 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) - rescue Errno::ECONNREFUSED - # Treat a refused TCP connection attempt to a nameserver like a timeout, - # as Gem::Resolv::DNS::Config#resolv considers ResolvTimeout exceptions as a - # hint to try the next nameserver: - raise ResolvTimeout - 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 - - def self.random(arg) # :nodoc: - begin - Gem::SecureRandom.random_number(arg) - rescue NotImplementedError - 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 - - case RUBY_PLATFORM - when *[ - # https://www.rfc-editor.org/rfc/rfc6056.txt - # Appendix A. Survey of the Algorithms in Use by Some Popular Implementations - /freebsd/, /linux/, /netbsd/, /openbsd/, /solaris/, - /darwin/, # the same as FreeBSD - ] then - def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: - udpsock.bind(bind_host, 0) - end - else - # Sequential port assignment - def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: - # Ephemeral port number range recommended by RFC 6056 - 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.each(&:freeze)) - when 'domain' - next if args.empty? - search = [args[0].freeze] - when 'search' - next if args.empty? - search = args.each(&:freeze) - when 'options' - args.each {|arg| - case arg - when /\Andots:(\d+)\z/ - ndots = $1.to_i - end - } - end - } - } - return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze - end - - def Config.default_config_hash(filename="/etc/resolv.conf") - if File.exist? filename - Config.parse_resolv_conf(filename) - elsif defined?(Win32::Resolv) - search, nameserver = Win32::Resolv.get_resolv_info - config_hash = {} - config_hash[:nameserver] = nameserver if nameserver - config_hash[:search] = [search].flatten if search - config_hash - else - {} - end - 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 = [] - size = -1 - 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 - l = self.get_label - d << l - size += 1 + l.string.bytesize - raise DecodeError.new("name label data exceed 255 octets") if size > 255 - 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 = Module.new do - module_function - - def []=(type_class_value, klass) - type_value, class_value = type_class_value - Resource.const_set(:"Type#{type_value}_Class#{class_value}", klass) - end - end - - 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: - cache = :"Type#{type_value}_Class#{class_value}" - - return (const_defined?(cache) && const_get(cache)) || - Generic.create(type_value, class_value) - end - - ## - # A generic resource abstract class. - - class Generic < Resource - - ## - # Creates a new generic resource. - - def initialize(data) - @data = data - end - - ## - # Data for this generic resource. - - attr_reader :data - - def encode_rdata(msg) # :nodoc: - msg.put_bytes(data) - end - - def self.decode_rdata(msg) # :nodoc: - return self.new(msg.get_bytes) - end - - def self.create(type_value, class_value) # :nodoc: - c = Class.new(Generic) - c.const_set(:TypeValue, type_value) - c.const_set(:ClassValue, class_value) - Generic.const_set("Type#{type_value}_Class#{class_value}", c) - ClassHash[[type_value, class_value]] = c - return c - end - end - - ## - # Domain Name resource abstract class. - - class DomainName < Resource - - ## - # Creates a new DomainName from +name+. - - def initialize(name) - @name = name - end - - ## - # The name of this DomainName. - - attr_reader :name - - def encode_rdata(msg) # :nodoc: - msg.put_name(@name) - end - - def self.decode_rdata(msg) # :nodoc: - return self.new(msg.get_name) - end - end - - # Standard (class generic) RRs - - ClassValue = nil # :nodoc: - - ## - # An authoritative name server. - - class NS < DomainName - TypeValue = 2 # :nodoc: - end - - ## - # The canonical name for an alias. - - class CNAME < DomainName - TypeValue = 5 # :nodoc: - end - - ## - # Start Of Authority resource. - - class SOA < Resource - - TypeValue = 6 # :nodoc: - - ## - # Creates a new SOA record. See the attr documentation for the - # details of each argument. - - def initialize(mname, rname, serial, refresh, retry_, expire, minimum) - @mname = mname - @rname = rname - @serial = serial - @refresh = refresh - @retry = retry_ - @expire = expire - @minimum = minimum - end - - ## - # Name of the host where the master zone file for this zone resides. - - attr_reader :mname - - ## - # The person responsible for this domain name. - - attr_reader :rname - - ## - # The version number of the zone file. - - attr_reader :serial - - ## - # How often, in seconds, a secondary name server is to check for - # updates from the primary name server. - - attr_reader :refresh - - ## - # How often, in seconds, a secondary name server is to retry after a - # failure to check for a refresh. - - attr_reader :retry - - ## - # Time in seconds that a secondary name server is to use the data - # before refreshing from the primary name server. - - attr_reader :expire - - ## - # The minimum number of seconds to be used for TTL values in RRs. - - attr_reader :minimum - - def encode_rdata(msg) # :nodoc: - msg.put_name(@mname) - msg.put_name(@rname) - msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum) - end - - def self.decode_rdata(msg) # :nodoc: - mname = msg.get_name - rname = msg.get_name - serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN') - return self.new( - mname, rname, serial, refresh, retry_, expire, minimum) - end - end - - ## - # A Pointer to another DNS name. - - class PTR < DomainName - TypeValue = 12 # :nodoc: - end - - ## - # Host Information resource. - - class HINFO < Resource - - TypeValue = 13 # :nodoc: - - ## - # Creates a new HINFO running +os+ on +cpu+. - - def initialize(cpu, os) - @cpu = cpu - @os = os - end - - ## - # CPU architecture for this resource. - - attr_reader :cpu - - ## - # Operating system for this resource. - - attr_reader :os - - def encode_rdata(msg) # :nodoc: - msg.put_string(@cpu) - msg.put_string(@os) - end - - def self.decode_rdata(msg) # :nodoc: - cpu = msg.get_string - os = msg.get_string - return self.new(cpu, os) - end - end - - ## - # Mailing list or mailbox information. - - class MINFO < Resource - - TypeValue = 14 # :nodoc: - - def initialize(rmailbx, emailbx) - @rmailbx = rmailbx - @emailbx = emailbx - end - - ## - # Domain name responsible for this mail list or mailbox. - - attr_reader :rmailbx - - ## - # Mailbox to use for error messages related to the mail list or mailbox. - - attr_reader :emailbx - - def encode_rdata(msg) # :nodoc: - msg.put_name(@rmailbx) - msg.put_name(@emailbx) - end - - def self.decode_rdata(msg) # :nodoc: - rmailbx = msg.get_string - emailbx = msg.get_string - return self.new(rmailbx, emailbx) - end - end - - ## - # Mail Exchanger resource. - - class MX < Resource - - TypeValue= 15 # :nodoc: - - ## - # Creates a new MX record with +preference+, accepting mail at - # +exchange+. - - def initialize(preference, exchange) - @preference = preference - @exchange = exchange - end - - ## - # The preference for this MX. - - attr_reader :preference - - ## - # The host of this MX. - - attr_reader :exchange - - def encode_rdata(msg) # :nodoc: - msg.put_pack('n', @preference) - msg.put_name(@exchange) - end - - def self.decode_rdata(msg) # :nodoc: - preference, = msg.get_unpack('n') - exchange = msg.get_name - return self.new(preference, exchange) - end - end - - ## - # Unstructured text resource. - - class TXT < Resource - - TypeValue = 16 # :nodoc: - - def initialize(first_string, *rest_strings) - @strings = [first_string, *rest_strings] - end - - ## - # Returns an Array of Strings for this TXT record. - - attr_reader :strings - - ## - # Returns the concatenated string from +strings+. - - def data - @strings.join("") - end - - def encode_rdata(msg) # :nodoc: - msg.put_string_list(@strings) - end - - def self.decode_rdata(msg) # :nodoc: - strings = msg.get_string_list - return self.new(*strings) - end - end - - ## - # Location resource - - class LOC < Resource - - TypeValue = 29 # :nodoc: - - def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude) - @version = version - @ssize = Gem::Resolv::LOC::Size.create(ssize) - @hprecision = Gem::Resolv::LOC::Size.create(hprecision) - @vprecision = Gem::Resolv::LOC::Size.create(vprecision) - @latitude = Gem::Resolv::LOC::Coord.create(latitude) - @longitude = Gem::Resolv::LOC::Coord.create(longitude) - @altitude = Gem::Resolv::LOC::Alt.create(altitude) - end - - ## - # Returns the version value for this LOC record which should always be 00 - - attr_reader :version - - ## - # The spherical size of this LOC - # in meters using scientific notation as 2 integers of XeY - - attr_reader :ssize - - ## - # The horizontal precision using ssize type values - # in meters using scientific notation as 2 integers of XeY - # for precision use value/2 e.g. 2m = +/-1m - - attr_reader :hprecision - - ## - # The vertical precision using ssize type values - # in meters using scientific notation as 2 integers of XeY - # for precision use value/2 e.g. 2m = +/-1m - - attr_reader :vprecision - - ## - # The latitude for this LOC where 2**31 is the equator - # in thousandths of an arc second as an unsigned 32bit integer - - attr_reader :latitude - - ## - # The longitude for this LOC where 2**31 is the prime meridian - # in thousandths of an arc second as an unsigned 32bit integer - - attr_reader :longitude - - ## - # The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid - # in centimeters as an unsigned 32bit integer - - attr_reader :altitude - - def encode_rdata(msg) # :nodoc: - msg.put_bytes(@version) - msg.put_bytes(@ssize.scalar) - msg.put_bytes(@hprecision.scalar) - msg.put_bytes(@vprecision.scalar) - msg.put_bytes(@latitude.coordinates) - msg.put_bytes(@longitude.coordinates) - msg.put_bytes(@altitude.altitude) - end - - def self.decode_rdata(msg) # :nodoc: - version = msg.get_bytes(1) - ssize = msg.get_bytes(1) - hprecision = msg.get_bytes(1) - vprecision = msg.get_bytes(1) - latitude = msg.get_bytes(4) - longitude = msg.get_bytes(4) - altitude = msg.get_bytes(4) - return self.new( - version, - Gem::Resolv::LOC::Size.new(ssize), - Gem::Resolv::LOC::Size.new(hprecision), - Gem::Resolv::LOC::Size.new(vprecision), - Gem::Resolv::LOC::Coord.new(latitude,"lat"), - Gem::Resolv::LOC::Coord.new(longitude,"lon"), - Gem::Resolv::LOC::Alt.new(altitude) - ) - end - end - - ## - # A Query type requesting any RR. - - class ANY < Query - TypeValue = 255 # :nodoc: - end - - ## - # CAA resource record defined in RFC 8659 - # - # These records identify certificate authority allowed to issue - # certificates for the given domain. - - class CAA < Resource - TypeValue = 257 - - ## - # Creates a new CAA for +flags+, +tag+ and +value+. - - def initialize(flags, tag, value) - unless (0..255) === flags - raise ArgumentError.new('flags must be an Integer between 0 and 255') - end - unless (1..15) === tag.bytesize - raise ArgumentError.new('length of tag must be between 1 and 15') - end - - @flags = flags - @tag = tag - @value = value - end - - ## - # Flags for this property: - # - Bit 0 : 0 = not critical, 1 = critical - - attr_reader :flags - - ## - # Property tag ("issue", "issuewild", "iodef"...). - - attr_reader :tag - - ## - # Property value. - - attr_reader :value - - ## - # Whether the critical flag is set on this property. - - def critical? - flags & 0x80 != 0 - end - - def encode_rdata(msg) # :nodoc: - msg.put_pack('C', @flags) - msg.put_string(@tag) - msg.put_bytes(@value) - end - - def self.decode_rdata(msg) # :nodoc: - flags, = msg.get_unpack('C') - tag = msg.get_string - value = msg.get_bytes - self.new flags, tag, value - end - end - - ClassInsensitiveTypes = [ # :nodoc: - NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY, CAA - ] - - ## - # module IN contains ARPA Internet specific RRs. - - module IN - - ClassValue = 1 # :nodoc: - - ClassInsensitiveTypes.each {|s| - c = Class.new(s) - c.const_set(:TypeValue, s::TypeValue) - c.const_set(:ClassValue, ClassValue) - ClassHash[[s::TypeValue, ClassValue]] = c - self.const_set(s.name.sub(/.*::/, ''), c) - } - - ## - # IPv4 Address resource - - class A < Resource - TypeValue = 1 - ClassValue = IN::ClassValue - ClassHash[[TypeValue, ClassValue]] = self # :nodoc: - - ## - # Creates a new A for +address+. - - def initialize(address) - @address = IPv4.create(address) - end - - ## - # The Gem::Resolv::IPv4 address for this A. - - attr_reader :address - - def encode_rdata(msg) # :nodoc: - msg.put_bytes(@address.address) - end - - def self.decode_rdata(msg) # :nodoc: - return self.new(IPv4.new(msg.get_bytes(4))) - end - end - - ## - # Well Known Service resource. - - class WKS < Resource - TypeValue = 11 - ClassValue = IN::ClassValue - ClassHash[[TypeValue, ClassValue]] = self # :nodoc: - - def initialize(address, protocol, bitmap) - @address = IPv4.create(address) - @protocol = protocol - @bitmap = bitmap - end - - ## - # The host these services run on. - - attr_reader :address - - ## - # IP protocol number for these services. - - attr_reader :protocol - - ## - # A bit map of enabled services on this host. - # - # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP - # service (port 25). If this bit is set, then an SMTP server should - # be listening on TCP port 25; if zero, SMTP service is not - # supported. - - attr_reader :bitmap - - def encode_rdata(msg) # :nodoc: - msg.put_bytes(@address.address) - msg.put_pack("n", @protocol) - msg.put_bytes(@bitmap) - end - - def self.decode_rdata(msg) # :nodoc: - address = IPv4.new(msg.get_bytes(4)) - protocol, = msg.get_unpack("n") - bitmap = msg.get_bytes - return self.new(address, protocol, bitmap) - end - end - - ## - # An IPv6 address record. - - class AAAA < Resource - TypeValue = 28 - ClassValue = IN::ClassValue - ClassHash[[TypeValue, ClassValue]] = self # :nodoc: - - ## - # Creates a new AAAA for +address+. - - def initialize(address) - @address = IPv6.create(address) - end - - ## - # The Gem::Resolv::IPv6 address for this AAAA. - - attr_reader :address - - def encode_rdata(msg) # :nodoc: - msg.put_bytes(@address.address) - end - - def self.decode_rdata(msg) # :nodoc: - return self.new(IPv6.new(msg.get_bytes(16))) - end - end - - ## - # SRV resource record defined in RFC 2782 - # - # These records identify the hostname and port that a service is - # available at. - - class SRV < Resource - TypeValue = 33 - ClassValue = IN::ClassValue - ClassHash[[TypeValue, ClassValue]] = self # :nodoc: - - # Create a SRV resource record. - # - # See the documentation for #priority, #weight, #port and #target - # for +priority+, +weight+, +port and +target+ respectively. - - def initialize(priority, weight, port, target) - @priority = priority.to_int - @weight = weight.to_int - @port = port.to_int - @target = Name.create(target) - end - - # The priority of this target host. - # - # A client MUST attempt to contact the target host with the - # lowest-numbered priority it can reach; target hosts with the same - # priority SHOULD be tried in an order defined by the weight field. - # The range is 0-65535. Note that it is not widely implemented and - # should be set to zero. - - attr_reader :priority - - # A server selection mechanism. - # - # The weight field specifies a relative weight for entries with the - # same priority. Larger weights SHOULD be given a proportionately - # higher probability of being selected. The range of this number is - # 0-65535. Domain administrators SHOULD use Weight 0 when there - # isn't any server selection to do, to make the RR easier to read - # for humans (less noisy). Note that it is not widely implemented - # and should be set to zero. - - attr_reader :weight - - # The port on this target host of this service. - # - # The range is 0-65535. - - attr_reader :port - - # The domain name of the target host. - # - # A target of "." means that the service is decidedly not available - # at this domain. - - attr_reader :target - - def encode_rdata(msg) # :nodoc: - msg.put_pack("n", @priority) - msg.put_pack("n", @weight) - msg.put_pack("n", @port) - msg.put_name(@target, compress: false) - end - - def self.decode_rdata(msg) # :nodoc: - priority, = msg.get_unpack("n") - weight, = msg.get_unpack("n") - port, = msg.get_unpack("n") - target = msg.get_name - return self.new(priority, weight, port, target) - end - end - - ## - # Common implementation for SVCB-compatible resource records. - - class ServiceBinding - - ## - # Create a service binding resource record. - - def initialize(priority, target, params = []) - @priority = priority.to_int - @target = Name.create(target) - @params = SvcParams.new(params) - end - - ## - # The priority of this target host. - # - # The range is 0-65535. - # If set to 0, this RR is in AliasMode. Otherwise, it is in ServiceMode. - - attr_reader :priority - - ## - # The domain name of the target host. - - attr_reader :target - - ## - # The service parameters for the target host. - - attr_reader :params - - ## - # Whether this RR is in AliasMode. - - def alias_mode? - self.priority == 0 - end - - ## - # Whether this RR is in ServiceMode. - - def service_mode? - !alias_mode? - end - - def encode_rdata(msg) # :nodoc: - msg.put_pack("n", @priority) - msg.put_name(@target, compress: false) - @params.encode(msg) - end - - def self.decode_rdata(msg) # :nodoc: - priority, = msg.get_unpack("n") - target = msg.get_name - params = SvcParams.decode(msg) - return self.new(priority, target, params) - end - end - - ## - # SVCB resource record [RFC9460] - - class SVCB < ServiceBinding - TypeValue = 64 - ClassValue = IN::ClassValue - ClassHash[[TypeValue, ClassValue]] = self # :nodoc: - end - - ## - # HTTPS resource record [RFC9460] - - class HTTPS < ServiceBinding - TypeValue = 65 - ClassValue = IN::ClassValue - ClassHash[[TypeValue, ClassValue]] = self # :nodoc: - end - end - end - end - - ## - # A Gem::Resolv::DNS IPv4 address. - - class IPv4 - - Regex256 = /0 - |1(?:[0-9][0-9]?)? - |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? - |[3-9][0-9]?/x # :nodoc: - - ## - # Regular expression IPv4 addresses must match. - Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/ - - ## - # Creates a new IPv4 address from +arg+ which may be: - # - # IPv4:: returns +arg+. - # String:: +arg+ must match the IPv4::Regex constant - - 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 # :nodoc: - - ## - # A Gem::Resolv::LOC::Size - - class Size - - # Regular expression LOC size must match. - - 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 - - # Internal use; use self.create. - 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 - - # Regular expression LOC Coord must match. - - 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 - - # Internal use; use self.create. - 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 - - # Regular expression LOC Alt must match. - - 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 - - # Internal use; use self.create. - 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/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 1c3cf2e1784a..05b530f20a68 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -736,6 +736,36 @@ def test_extract_symlink_parent assert_path_not_exist File.join(destination_subdir, "lib/link") end + def test_extract_symlink_parent_absolute_path + package = Gem::Package.new @gem + + # Extract into a subdirectory of @destination; if this test fails it writes + # a file outside destination_subdir, but we want the file to remain inside + # @destination so it will be cleaned up. + destination_subdir = File.join @destination, "subdir" + FileUtils.mkdir_p destination_subdir + + pend "TMPDIR seems too long to add it as symlink into tar" if destination_subdir.size > 90 + + tgz_io = util_tar_gz do |tar| + tar.mkdir "lib", 0o755 + tar.add_symlink "lib/link", File.join(destination_subdir, ".."), 0o644 + tar.add_file "lib/link/outside.txt", 0o644 do |io| + io.write "hi" + end + end + + e = assert_raise(Gem::Package::SymlinkError) do + package.extract_tar_gz tgz_io, destination_subdir + end + + assert_equal("installing symlink 'lib/link' pointing to parent path #{@destination} of " \ + "#{destination_subdir} is not allowed", e.message) + + assert_path_not_exist File.join(@destination, "outside.txt") + assert_path_not_exist File.join(destination_subdir, "lib/link") + end + def test_extract_symlink_parent_doesnt_delete_user_dir package = Gem::Package.new @gem diff --git a/tool/automatiek/net-http-v0.7.0.patch b/tool/automatiek/net-http-v0.7.0.patch index e82c1ec132d5..7ef5cb203897 100644 --- a/tool/automatiek/net-http-v0.7.0.patch +++ b/tool/automatiek/net-http-v0.7.0.patch @@ -27,3 +27,54 @@ index cd5177fcbc..9110b108b8 100644 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', +diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb +index e5258175add..e1ccc056100 100644 +--- a/lib/rubygems/vendor/net-http/lib/net/http.rb ++++ b/lib/rubygems/vendor/net-http/lib/net/http.rb +@@ -22,7 +22,6 @@ + + require_relative '../../../net-protocol/lib/net/protocol' + require_relative '../../../uri/lib/uri' +-require 'resolv' + autoload :OpenSSL, 'openssl' + + module Gem::Net #:nodoc: +@@ -1531,6 +1530,29 @@ def use_ssl=(flag) + + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: + ++ # Resolv::IPv4::Regex and Resolv::IPv6::Regex, copied from resolv 0.7.1 rather ++ # than required, so that net/http does not load a DNS resolver just to keep an ++ # IP address literal out of the Server Name Indication. ++ # https://github.com/ruby/resolv/blob/v0.7.1/lib/resolv.rb ++ ipv4_octet = /0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?/ ++ hex16 = /[0-9A-Fa-f]{1,4}/ ++ hex16_group = /(?:#{hex16}(?::#{hex16})*)?/ ++ dotted_quad = /\d+\.\d+\.\d+\.\d+/ ++ zone_id = /%[-0-9A-Za-z._~]+/ ++ ++ IPV4_ADDRESS = /\A(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\z/ # :nodoc: ++ ++ IPV6_ADDRESS = /\A(?: ++ (?:#{hex16}:){7}#{hex16} # a:b:c:d:e:f:g:h ++ | #{hex16_group}::#{hex16_group} # a::b ++ | (?:#{hex16}:){6}#{dotted_quad} # a:b:c:d:e:f:w.x.y.z ++ | #{hex16_group}::(?:#{hex16}:)*#{dotted_quad} # a::b:w.x.y.z ++ | [Ff][Ee]80(?::#{hex16}){7}#{zone_id} # fe80:b:c:d:e:f:g:h%em1 ++ | [Ff][Ee]80:(?:#{hex16_group}::#{hex16_group}|:#{hex16_group})?:#{hex16}#{zone_id} # fe80::b%em1 ++ )\z/x # :nodoc: ++ ++ private_constant :IPV4_ADDRESS, :IPV6_ADDRESS ++ + # Sets or returns the path to a CA certification file in PEM format. + attr_accessor :ca_file + +@@ -1714,7 +1736,7 @@ def connect + + # Server Name Indication (SNI) RFC 3546/6066 + case @address +- when Resolv::IPv4::Regex, Resolv::IPv6::Regex ++ when IPV4_ADDRESS, IPV6_ADDRESS + # don't set SNI, as IP addresses in SNI is not valid + # per RFC 6066, section 3. + diff --git a/tool/automatiek/resolv-v0.7.0.patch b/tool/automatiek/resolv-v0.7.0.patch deleted file mode 100644 index 26dbda3d9599..000000000000 --- a/tool/automatiek/resolv-v0.7.0.patch +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb -index d72b4dba38..168df21f3e 100644 ---- a/lib/rubygems/vendor/resolv/lib/resolv.rb -+++ b/lib/rubygems/vendor/resolv/lib/resolv.rb -@@ -1,9 +1,9 @@ - # frozen_string_literal: true - - require 'socket' --require_relative '../../timeout/lib/timeout' -+require_relative '../../../vendored_timeout' - require 'io/wait' --require_relative '../../securerandom/lib/securerandom' -+require_relative '../../../vendored_securerandom' - require 'rbconfig' - - # 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 diff --git a/tool/automatiek/vendor.rb b/tool/automatiek/vendor.rb index 3cebc3e2ce30..444695b731ad 100755 --- a/tool/automatiek/vendor.rb +++ b/tool/automatiek/vendor.rb @@ -81,11 +81,10 @@ def require_target vendored_gems = [ # RubyGems VendoredGem.new(name: "molinillo", namespace: "Molinillo", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/molinillo", license_path: "LICENSE", extra_dependencies: %w[tsort/lib/rubygems/vendor/tsort], patch_name: "molinillo-master.patch"), - VendoredGem.new(name: "net-http", namespace: "Net", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/net-http", license_path: "COPYING", extra_dependencies: %w[net-protocol resolv timeout uri/lib/rubygems/vendor/uri], skip_dependencies: %w[uri], patch_name: "net-http-v0.7.0.patch"), + VendoredGem.new(name: "net-http", namespace: "Net", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/net-http", license_path: "COPYING", extra_dependencies: %w[net-protocol timeout uri/lib/rubygems/vendor/uri], skip_dependencies: %w[uri], patch_name: "net-http-v0.7.0.patch"), VendoredGem.new(name: "net-http-persistent", namespace: "Net::HTTP::Persistent", prefix: "Gem", vendor_lib: "bundler/lib/bundler/vendor/net-http-persistent", license_path: "README.rdoc", extra_dependencies: %w[net-http uri/lib/rubygems/vendor/uri], patch_name: "net-http-persistent-v4.0.6.patch"), VendoredGem.new(name: "net-protocol", namespace: "Net", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/net-protocol", license_path: "LICENSE.txt"), VendoredGem.new(name: "optparse", namespace: "OptionParser", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/optparse", license_path: "COPYING", extra_dependencies: %w[uri/lib/rubygems/vendor/uri], patch_name: "optparse-v0.8.0.patch"), - VendoredGem.new(name: "resolv", namespace: "Resolv", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/resolv", license_path: "COPYING", extra_dependencies: %w[securerandom/lib/rubygems/vendor/securerandom timeout], patch_name: "resolv-v0.7.0.patch"), VendoredGem.new(name: "securerandom", namespace: "SecureRandom", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/securerandom", license_path: "COPYING"), VendoredGem.new(name: "timeout", namespace: "Timeout", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/timeout", license_path: "COPYING", patch_name: "timeout-v0.4.3.patch"), VendoredGem.new(name: "tsort", namespace: "TSort", prefix: "Gem", vendor_lib: "lib/rubygems/vendor/tsort", license_path: "LICENSE.txt"), diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 81c2de36b22e..b477846eadde 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/tool/bundler/lint_gems.rb.lock b/tool/bundler/lint_gems.rb.lock index b81cd1004e5e..5696326acc02 100644 --- a/tool/bundler/lint_gems.rb.lock +++ b/tool/bundler/lint_gems.rb.lock @@ -119,4 +119,4 @@ CHECKSUMS wmi-lite (1.0.7) sha256=116ef5bb470dbe60f58c2db9047af3064c16245d6562c646bc0d90877e27ddda BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/tool/bundler/release_gems.rb.lock b/tool/bundler/release_gems.rb.lock index 8a0247dc4c49..2cdf2cd2658d 100644 --- a/tool/bundler/release_gems.rb.lock +++ b/tool/bundler/release_gems.rb.lock @@ -87,4 +87,4 @@ CHECKSUMS uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 4869b1ae34d1..46035a284a7d 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index e94fe4a90dc9..44c6fecdbf82 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index a3ba9240f3d5..f6a6e227c715 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -102,4 +102,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.20 + 4.0.21 diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index 8f2d0344494e..ebffc281f50b 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -9,7 +9,6 @@ gem "net-protocol", "0.2.2" gem "optparse", "0.8.0" gem "pub_grub", github: "jhawthorn/pub_grub", ref: "df6add45d1b4d122daff2f959c9bd1ca93d14261" -gem "resolv", "0.7.0" gem "securerandom", "0.4.1" gem "timeout", "0.4.4" gem "thor", "1.4.0" diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index 02da7edf0c4d..371f5404d7b3 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -24,7 +24,6 @@ GEM net-protocol (0.2.2) timeout optparse (0.8.0) - resolv (0.7.0) securerandom (0.4.1) thor (1.4.0) timeout (0.4.4) @@ -48,7 +47,6 @@ DEPENDENCIES net-protocol (= 0.2.2) optparse (= 0.8.0) pub_grub! - resolv (= 0.7.0) securerandom (= 0.4.1) thor (= 1.4.0) timeout (= 0.4.4) @@ -64,7 +62,6 @@ CHECKSUMS net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 optparse (0.8.0) sha256=ef6b7fbaf7ec331474f325bc08dd5622e6e1e651007a5341330ee4b08ce734f0 pub_grub (0.5.0) - resolv (0.7.0) sha256=e20ce2c00d1e1d1bd13aa98f52d9b5166509f80f50673084ae54dd26a0b083ad securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d timeout (0.4.4) sha256=f0f6f970104b82427cd990680f539b6bbb8b1e55efa913a55c6492935e4e0edb @@ -72,4 +69,4 @@ CHECKSUMS uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH - 4.0.20 + 4.0.21