Warning: file_get_contents(https://raw.githubusercontent.com/Den1xxx/Filemanager/master/languages/ru.json): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 88

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 215

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 216

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 217

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 218

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 219

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 220
PK!Udd rubygems.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require "rbconfig" module Gem VERSION = "3.5.22" end # Must be first since it unloads the prelude from 1.9.2 require_relative "rubygems/compatibility" require_relative "rubygems/defaults" require_relative "rubygems/deprecate" require_relative "rubygems/errors" ## # RubyGems is the Ruby standard for publishing and managing third party # libraries. # # For user documentation, see: # # * gem help and gem help [command] # * {RubyGems User Guide}[https://guides.rubygems.org/] # * {Frequently Asked Questions}[https://guides.rubygems.org/faqs] # # For gem developer documentation see: # # * {Creating Gems}[https://guides.rubygems.org/make-your-own-gem] # * Gem::Specification # * Gem::Version for version dependency notes # # Further RubyGems documentation can be found at: # # * {RubyGems Guides}[https://guides.rubygems.org] # * {RubyGems API}[https://www.rubydoc.info/github/rubygems/rubygems] (also available from # gem server) # # == RubyGems Plugins # # RubyGems will load plugins in the latest version of each installed gem or # $LOAD_PATH. Plugins must be named 'rubygems_plugin' (.rb, .so, etc) and # placed at the root of your gem's #require_path. Plugins are installed at a # special location and loaded on boot. # # For an example plugin, see the {Graph gem}[https://github.com/seattlerb/graph] # which adds a gem graph command. # # == RubyGems Defaults, Packaging # # RubyGems defaults are stored in lib/rubygems/defaults.rb. If you're packaging # RubyGems or implementing Ruby you can change RubyGems' defaults. # # For RubyGems packagers, provide lib/rubygems/defaults/operating_system.rb # and override any defaults from lib/rubygems/defaults.rb. # # For Ruby implementers, provide lib/rubygems/defaults/#{RUBY_ENGINE}.rb and # override any defaults from lib/rubygems/defaults.rb. # # If you need RubyGems to perform extra work on install or uninstall, your # defaults override file can set pre/post install and uninstall hooks. # See Gem::pre_install, Gem::pre_uninstall, Gem::post_install, # Gem::post_uninstall. # # == Bugs # # You can submit bugs to the # {RubyGems bug tracker}[https://github.com/rubygems/rubygems/issues] # on GitHub # # == Credits # # RubyGems is currently maintained by Eric Hodel. # # RubyGems was originally developed at RubyConf 2003 by: # # * Rich Kilmer -- rich(at)infoether.com # * Chad Fowler -- chad(at)chadfowler.com # * David Black -- dblack(at)wobblini.net # * Paul Brannan -- paul(at)atdesk.com # * Jim Weirich -- jim(at)weirichhouse.org # # Contributors: # # * Gavin Sinclair -- gsinclair(at)soyabean.com.au # * George Marrows -- george.marrows(at)ntlworld.com # * Dick Davies -- rasputnik(at)hellooperator.net # * Mauricio Fernandez -- batsman.geo(at)yahoo.com # * Simon Strandgaard -- neoneye(at)adslhome.dk # * Dave Glasser -- glasser(at)mit.edu # * Paul Duncan -- pabs(at)pablotron.org # * Ville Aine -- vaine(at)cs.helsinki.fi # * Eric Hodel -- drbrain(at)segment7.net # * Daniel Berger -- djberg96(at)gmail.com # * Phil Hagelberg -- technomancy(at)gmail.com # * Ryan Davis -- ryand-ruby(at)zenspider.com # * Evan Phoenix -- evan(at)fallingsnow.net # * Steve Klabnik -- steve(at)steveklabnik.com # # (If your name is missing, PLEASE let us know!) # # == License # # See {LICENSE.txt}[rdoc-ref:lib/rubygems/LICENSE.txt] for permissions. # # Thanks! # # -The RubyGems Team module Gem RUBYGEMS_DIR = __dir__ ## # An Array of Regexps that match windows Ruby platforms. WIN_PATTERNS = [ /bccwin/i, /cygwin/i, /djgpp/i, /mingw/i, /mswin/i, /wince/i, ].freeze GEM_DEP_FILES = %w[ gem.deps.rb gems.rb Gemfile Isolate ].freeze ## # Subdirectories in a gem repository REPOSITORY_SUBDIRECTORIES = %w[ build_info cache doc extensions gems plugins specifications ].freeze ## # Subdirectories in a gem repository for default gems REPOSITORY_DEFAULT_GEM_SUBDIRECTORIES = %w[ gems specifications/default ].freeze @@win_platform = nil @configuration = nil @gemdeps = nil @loaded_specs = {} LOADED_SPECS_MUTEX = Thread::Mutex.new @path_to_default_spec_map = {} @platforms = [] @ruby = nil @ruby_api_version = nil @sources = nil @post_build_hooks ||= [] @post_install_hooks ||= [] @post_uninstall_hooks ||= [] @pre_uninstall_hooks ||= [] @pre_install_hooks ||= [] @pre_reset_hooks ||= [] @post_reset_hooks ||= [] @default_source_date_epoch = nil @discover_gems_on_require = true ## # Try to activate a gem containing +path+. Returns true if # activation succeeded or wasn't needed because it was already # activated. Returns false if it can't find the path in a gem. def self.try_activate(path) # finds the _latest_ version... regardless of loaded specs and their deps # if another gem had a requirement that would mean we shouldn't # activate the latest version, then either it would already be activated # or if it was ambiguous (and thus unresolved) the code in our custom # require will try to activate the more specific version. spec = Gem::Specification.find_by_path path return false unless spec return true if spec.activated? begin spec.activate rescue Gem::LoadError => e # this could fail due to gem dep collisions, go lax spec_by_name = Gem::Specification.find_by_name(spec.name) if spec_by_name.nil? raise e else spec_by_name.activate end end true end def self.needs rs = Gem::RequestSet.new yield rs finish_resolve rs end def self.finish_resolve(request_set=Gem::RequestSet.new) request_set.import Gem::Specification.unresolved_deps.values request_set.import Gem.loaded_specs.values.map {|s| Gem::Dependency.new(s.name, s.version) } request_set.resolve_current.each do |s| s.full_spec.activate end end ## # Find the full path to the executable for gem +name+. If the +exec_name+ # is not given, an exception will be raised, otherwise the # specified executable's path is returned. +requirements+ allows # you to specify specific gem versions. def self.bin_path(name, exec_name = nil, *requirements) requirements = Gem::Requirement.default if requirements.empty? find_spec_for_exe(name, exec_name, requirements).bin_file exec_name end def self.find_spec_for_exe(name, exec_name, requirements) raise ArgumentError, "you must supply exec_name" unless exec_name dep = Gem::Dependency.new name, requirements loaded = Gem.loaded_specs[name] return loaded if loaded && dep.matches_spec?(loaded) specs = dep.matching_specs(true) specs = specs.find_all do |spec| spec.executables.include? exec_name end if exec_name unless spec = specs.first msg = "can't find gem #{dep} with executable #{exec_name}" raise Gem::GemNotFoundException, msg end spec end private_class_method :find_spec_for_exe ## # Find the full path to the executable for gem +name+. If the +exec_name+ # is not given, an exception will be raised, otherwise the # specified executable's path is returned. +requirements+ allows # you to specify specific gem versions. # # A side effect of this method is that it will activate the gem that # contains the executable. # # This method should *only* be used in bin stub files. def self.activate_bin_path(name, exec_name = nil, *requirements) # :nodoc: spec = find_spec_for_exe name, exec_name, requirements Gem::LOADED_SPECS_MUTEX.synchronize do spec.activate finish_resolve end spec.bin_file exec_name end ## # The mode needed to read a file as straight binary. def self.binary_mode "rb" end ## # The path where gem executables are to be installed. def self.bindir(install_dir=Gem.dir) return File.join install_dir, "bin" unless install_dir.to_s == Gem.default_dir.to_s Gem.default_bindir end ## # The path were rubygems plugins are to be installed. def self.plugindir(install_dir=Gem.dir) File.join install_dir, "plugins" end ## # Reset the +dir+ and +path+ values. The next time +dir+ or +path+ # is requested, the values will be calculated from scratch. This is # mainly used by the unit tests to provide test isolation. def self.clear_paths @paths = nil @user_home = nil Gem::Specification.reset Gem::Security.reset if defined?(Gem::Security) end ## # The standard configuration object for gems. def self.configuration @configuration ||= Gem::ConfigFile.new [] end ## # Use the given configuration object (which implements the ConfigFile # protocol) as the standard configuration object. def self.configuration=(config) @configuration = config end ## # A Zlib::Deflate.deflate wrapper def self.deflate(data) require "zlib" Zlib::Deflate.deflate data end # Retrieve the PathSupport object that RubyGems uses to # lookup files. def self.paths @paths ||= Gem::PathSupport.new(ENV) end # Initialize the filesystem paths to use from +env+. # +env+ is a hash-like object (typically ENV) that # is queried for 'GEM_HOME', 'GEM_PATH', and 'GEM_SPEC_CACHE' # Keys for the +env+ hash should be Strings, and values of the hash should # be Strings or +nil+. def self.paths=(env) clear_paths target = {} env.each_pair do |k,v| case k when "GEM_HOME", "GEM_PATH", "GEM_SPEC_CACHE" case v when nil, String target[k] = v when Array unless Gem::Deprecate.skip warn <<-EOWARN Array values in the parameter to `Gem.paths=` are deprecated. Please use a String or nil. An Array (#{env.inspect}) was passed in from #{caller[3]} EOWARN end target[k] = v.join File::PATH_SEPARATOR end else target[k] = v end end @paths = Gem::PathSupport.new ENV.to_hash.merge(target) Gem::Specification.dirs = @paths.path end ## # The path where gems are to be installed. def self.dir paths.home end def self.path paths.path end def self.spec_cache_dir paths.spec_cache_dir end ## # Quietly ensure the Gem directory +dir+ contains all the proper # subdirectories. If we can't create a directory due to a permission # problem, then we will silently continue. # # If +mode+ is given, missing directories are created with this mode. # # World-writable directories will never be created. def self.ensure_gem_subdirectories(dir = Gem.dir, mode = nil) ensure_subdirectories(dir, mode, REPOSITORY_SUBDIRECTORIES) end ## # Quietly ensure the Gem directory +dir+ contains all the proper # subdirectories for handling default gems. If we can't create a # directory due to a permission problem, then we will silently continue. # # If +mode+ is given, missing directories are created with this mode. # # World-writable directories will never be created. def self.ensure_default_gem_subdirectories(dir = Gem.dir, mode = nil) ensure_subdirectories(dir, mode, REPOSITORY_DEFAULT_GEM_SUBDIRECTORIES) end def self.ensure_subdirectories(dir, mode, subdirs) # :nodoc: old_umask = File.umask File.umask old_umask | 0o002 options = {} options[:mode] = mode if mode subdirs.each do |name| subdir = File.join dir, name next if File.exist? subdir require "fileutils" begin FileUtils.mkdir_p subdir, **options rescue SystemCallError end end ensure File.umask old_umask end ## # The extension API version of ruby. This includes the static vs non-static # distinction as extensions cannot be shared between the two. def self.extension_api_version # :nodoc: if RbConfig::CONFIG["ENABLE_SHARED"] == "no" "#{ruby_api_version}-static" else ruby_api_version end end ## # Returns a list of paths matching +glob+ that can be used by a gem to pick # up features from other gems. For example: # # Gem.find_files('rdoc/discover').each do |path| load path end # # if +check_load_path+ is true (the default), then find_files also searches # $LOAD_PATH for files as well as gems. # # Note that find_files will return all files even if they are from different # versions of the same gem. See also find_latest_files def self.find_files(glob, check_load_path=true) files = [] files = find_files_from_load_path glob if check_load_path gem_specifications = @gemdeps ? Gem.loaded_specs.values : Gem::Specification.stubs files.concat gem_specifications.map {|spec| spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}") }.flatten # $LOAD_PATH might contain duplicate entries or reference # the spec dirs directly, so we prune. files.uniq! if check_load_path files end def self.find_files_from_load_path(glob) # :nodoc: glob_with_suffixes = "#{glob}#{Gem.suffix_pattern}" $LOAD_PATH.map do |load_path| Gem::Util.glob_files_in_dir(glob_with_suffixes, load_path) end.flatten.select {|file| File.file? file } end ## # Returns a list of paths matching +glob+ from the latest gems that can be # used by a gem to pick up features from other gems. For example: # # Gem.find_latest_files('rdoc/discover').each do |path| load path end # # if +check_load_path+ is true (the default), then find_latest_files also # searches $LOAD_PATH for files as well as gems. # # Unlike find_files, find_latest_files will return only files from the # latest version of a gem. def self.find_latest_files(glob, check_load_path=true) files = [] files = find_files_from_load_path glob if check_load_path files.concat Gem::Specification.latest_specs(true).map {|spec| spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}") }.flatten # $LOAD_PATH might contain duplicate entries or reference # the spec dirs directly, so we prune. files.uniq! if check_load_path files end ## # Top level install helper method. Allows you to install gems interactively: # # % irb # >> Gem.install "minitest" # Fetching: minitest-5.14.0.gem (100%) # => [#] def self.install(name, version = Gem::Requirement.default, *options) require_relative "rubygems/dependency_installer" inst = Gem::DependencyInstaller.new(*options) inst.install name, version inst.installed_gems end ## # Get the default RubyGems API host. This is normally # https://rubygems.org. def self.host @host ||= Gem::DEFAULT_HOST end ## Set the default RubyGems API host. def self.host=(host) @host = host end ## # The index to insert activated gem paths into the $LOAD_PATH. The activated # gem's paths are inserted before site lib directory by default. def self.load_path_insert_index $LOAD_PATH.each_with_index do |path, i| return i if path.instance_variable_defined?(:@gem_prelude_index) end index = $LOAD_PATH.index RbConfig::CONFIG["sitelibdir"] index || 0 end ## # The number of paths in the +$LOAD_PATH+ from activated gems. Used to # prioritize +-I+ and ENV['RUBYLIB'] entries during +require+. def self.activated_gem_paths @activated_gem_paths ||= 0 end ## # Add a list of paths to the $LOAD_PATH at the proper place. def self.add_to_load_path(*paths) @activated_gem_paths = activated_gem_paths + paths.size # gem directories must come after -I and ENV['RUBYLIB'] $LOAD_PATH.insert(Gem.load_path_insert_index, *paths) end @yaml_loaded = false ## # Loads YAML, preferring Psych def self.load_yaml return if @yaml_loaded require "psych" require_relative "rubygems/psych_tree" require_relative "rubygems/safe_yaml" @yaml_loaded = true end @safe_marshal_loaded = false def self.load_safe_marshal return if @safe_marshal_loaded require_relative "rubygems/safe_marshal" @safe_marshal_loaded = true end ## # The file name and line number of the caller of the caller of this method. # # +depth+ is how many layers up the call stack it should go. # # e.g., # # def a; Gem.location_of_caller; end # a #=> ["x.rb", 2] # (it'll vary depending on file name and line number) # # def b; c; end # def c; Gem.location_of_caller(2); end # b #=> ["x.rb", 6] # (it'll vary depending on file name and line number) def self.location_of_caller(depth = 1) caller[depth] =~ /(.*?):(\d+).*?$/i file = $1 lineno = $2.to_i [file, lineno] end ## # The version of the Marshal format for your Ruby. def self.marshal_version "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" end ## # Set array of platforms this RubyGems supports (primarily for testing). def self.platforms=(platforms) @platforms = platforms end ## # Array of platforms this RubyGems supports. def self.platforms @platforms ||= [] if @platforms.empty? @platforms = [Gem::Platform::RUBY, Gem::Platform.local] end @platforms end ## # Adds a post-build hook that will be passed an Gem::Installer instance # when Gem::Installer#install is called. The hook is called after the gem # has been extracted and extensions have been built but before the # executables or gemspec has been written. If the hook returns +false+ then # the gem's files will be removed and the install will be aborted. def self.post_build(&hook) @post_build_hooks << hook end ## # Adds a post-install hook that will be passed an Gem::Installer instance # when Gem::Installer#install is called def self.post_install(&hook) @post_install_hooks << hook end ## # Adds a post-installs hook that will be passed a Gem::DependencyInstaller # and a list of installed specifications when # Gem::DependencyInstaller#install is complete def self.done_installing(&hook) @done_installing_hooks << hook end ## # Adds a hook that will get run after Gem::Specification.reset is # run. def self.post_reset(&hook) @post_reset_hooks << hook end ## # Adds a post-uninstall hook that will be passed a Gem::Uninstaller instance # and the spec that was uninstalled when Gem::Uninstaller#uninstall is # called def self.post_uninstall(&hook) @post_uninstall_hooks << hook end ## # Adds a pre-install hook that will be passed an Gem::Installer instance # when Gem::Installer#install is called. If the hook returns +false+ then # the install will be aborted. def self.pre_install(&hook) @pre_install_hooks << hook end ## # Adds a hook that will get run before Gem::Specification.reset is # run. def self.pre_reset(&hook) @pre_reset_hooks << hook end ## # Adds a pre-uninstall hook that will be passed an Gem::Uninstaller instance # and the spec that will be uninstalled when Gem::Uninstaller#uninstall is # called def self.pre_uninstall(&hook) @pre_uninstall_hooks << hook end ## # The directory prefix this RubyGems was installed at. If your # prefix is in a standard location (ie, rubygems is installed where # you'd expect it to be), then prefix returns nil. def self.prefix prefix = File.dirname RUBYGEMS_DIR if prefix != File.expand_path(RbConfig::CONFIG["sitelibdir"]) && prefix != File.expand_path(RbConfig::CONFIG["libdir"]) && File.basename(RUBYGEMS_DIR) == "lib" prefix end end ## # Refresh available gems from disk. def self.refresh Gem::Specification.reset end ## # Safely read a file in binary mode on all platforms. def self.read_binary(path) File.binread(path) end ## # Safely write a file in binary mode on all platforms. def self.write_binary(path, data) File.binwrite(path, data) rescue Errno::ENOSPC # If we ran out of space but the file exists, it's *guaranteed* to be corrupted. File.delete(path) if File.exist?(path) raise end ## # Open a file with given flags def self.open_file(path, flags, &block) File.open(path, flags, &block) end ## # Open a file with given flags, and protect access with a file lock def self.open_file_with_lock(path, &block) file_lock = "#{path}.lock" open_file_with_flock(file_lock, &block) ensure FileUtils.rm_f file_lock end ## # Open a file with given flags, and protect access with flock def self.open_file_with_flock(path, &block) # read-write mode is used rather than read-only in order to support NFS mode = IO::RDWR | IO::APPEND | IO::CREAT | IO::BINARY mode |= IO::SHARE_DELETE if IO.const_defined?(:SHARE_DELETE) File.open(path, mode) do |io| begin io.flock(File::LOCK_EX) rescue Errno::ENOSYS, Errno::ENOTSUP end yield io end end ## # The path to the running Ruby interpreter. def self.ruby if @ruby.nil? @ruby = RbConfig.ruby @ruby = "\"#{@ruby}\"" if /\s/.match?(@ruby) end @ruby end ## # Returns a String containing the API compatibility version of Ruby def self.ruby_api_version @ruby_api_version ||= RbConfig::CONFIG["ruby_version"].dup end def self.env_requirement(gem_name) @env_requirements_by_name ||= {} @env_requirements_by_name[gem_name] ||= begin req = ENV["GEM_REQUIREMENT_#{gem_name.upcase}"] || ">= 0" Gem::Requirement.create(req) end end post_reset { @env_requirements_by_name = {} } ## # Returns the latest release-version specification for the gem +name+. def self.latest_spec_for(name) dependency = Gem::Dependency.new name fetcher = Gem::SpecFetcher.fetcher spec_tuples, = fetcher.spec_for_dependency dependency spec, = spec_tuples.last spec end ## # Returns the latest release version of RubyGems. def self.latest_rubygems_version latest_version_for("rubygems-update") || raise("Can't find 'rubygems-update' in any repo. Check `gem source list`.") end ## # Returns the version of the latest release-version of gem +name+ def self.latest_version_for(name) latest_spec_for(name)&.version end ## # A Gem::Version for the currently running Ruby. def self.ruby_version return @ruby_version if defined? @ruby_version version = RUBY_VERSION.dup if RUBY_PATCHLEVEL == -1 if RUBY_ENGINE == "ruby" desc = RUBY_DESCRIPTION[/\Aruby #{Regexp.quote(RUBY_VERSION)}([^ ]+) /, 1] else desc = RUBY_DESCRIPTION[/\A#{RUBY_ENGINE} #{Regexp.quote(RUBY_ENGINE_VERSION)} \(#{RUBY_VERSION}([^ ]+)\) /, 1] end version << ".#{desc}" if desc end @ruby_version = Gem::Version.new version end ## # A Gem::Version for the currently running RubyGems def self.rubygems_version return @rubygems_version if defined? @rubygems_version @rubygems_version = Gem::Version.new Gem::VERSION end ## # Returns an Array of sources to fetch remote gems from. Uses # default_sources if the sources list is empty. def self.sources source_list = configuration.sources || default_sources @sources ||= Gem::SourceList.from(source_list) end ## # Need to be able to set the sources without calling # Gem.sources.replace since that would cause an infinite loop. # # DOC: This comment is not documentation about the method itself, it's # more of a code comment about the implementation. def self.sources=(new_sources) if !new_sources @sources = nil else @sources = Gem::SourceList.from(new_sources) end end ## # Glob pattern for require-able path suffixes. def self.suffix_pattern @suffix_pattern ||= "{#{suffixes.join(",")}}" end ## # Regexp for require-able path suffixes. def self.suffix_regexp @suffix_regexp ||= /#{Regexp.union(suffixes)}\z/ end ## # Glob pattern for require-able plugin suffixes. def self.plugin_suffix_pattern @plugin_suffix_pattern ||= "_plugin#{suffix_pattern}" end ## # Regexp for require-able plugin suffixes. def self.plugin_suffix_regexp @plugin_suffix_regexp ||= /_plugin#{suffix_regexp}\z/ end ## # Suffixes for require-able paths. def self.suffixes @suffixes ||= ["", ".rb", *%w[DLEXT DLEXT2].map do |key| val = RbConfig::CONFIG[key] next unless val && !val.empty? ".#{val}" end].compact.uniq end ## # Suffixes for dynamic library require-able paths. def self.dynamic_library_suffixes @dynamic_library_suffixes ||= suffixes - [".rb"] end ## # Prints the amount of time the supplied block takes to run using the debug # UI output. def self.time(msg, width = 0, display = Gem.configuration.verbose) now = Time.now value = yield elapsed = Time.now - now ui.say format("%2$*1$s: %3$3.3fs", -width, msg, elapsed) if display value end ## # Lazily loads DefaultUserInteraction and returns the default UI. def self.ui require_relative "rubygems/user_interaction" Gem::DefaultUserInteraction.ui end ## # Use the +home+ and +paths+ values for Gem.dir and Gem.path. Used mainly # by the unit tests to provide environment isolation. def self.use_paths(home, *paths) paths.flatten! paths.compact! hash = { "GEM_HOME" => home, "GEM_PATH" => paths.empty? ? home : paths.join(File::PATH_SEPARATOR) } hash.delete_if {|_, v| v.nil? } self.paths = hash end ## # Is this a windows platform? def self.win_platform? if @@win_platform.nil? ruby_platform = RbConfig::CONFIG["host_os"] @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil? end @@win_platform end ## # Is this a java platform? def self.java_platform? RUBY_PLATFORM == "java" end ## # Is this platform Solaris? def self.solaris_platform? RUBY_PLATFORM.include?("solaris") end ## # Is this platform FreeBSD def self.freebsd_platform? RbConfig::CONFIG["host_os"].to_s.include?("bsd") end ## # Load +plugins+ as Ruby files def self.load_plugin_files(plugins) # :nodoc: plugins.each do |plugin| # Skip older versions of the GemCutter plugin: Its commands are in # RubyGems proper now. next if /gemcutter-0\.[0-3]/.match?(plugin) begin load plugin rescue ScriptError, StandardError => e details = "#{plugin.inspect}: #{e.message} (#{e.class})" warn "Error loading RubyGems plugin #{details}" end end end ## # Find rubygems plugin files in the standard location and load them def self.load_plugins Gem.path.each do |gem_path| load_plugin_files Gem::Util.glob_files_in_dir("*#{Gem.plugin_suffix_pattern}", plugindir(gem_path)) end end ## # Find all 'rubygems_plugin' files in $LOAD_PATH and load them def self.load_env_plugins load_plugin_files find_files_from_load_path("rubygems_plugin") end ## # Looks for a gem dependency file at +path+ and activates the gems in the # file if found. If the file is not found an ArgumentError is raised. # # If +path+ is not given the RUBYGEMS_GEMDEPS environment variable is used, # but if no file is found no exception is raised. # # If '-' is given for +path+ RubyGems searches up from the current working # directory for gem dependency files (gem.deps.rb, Gemfile, Isolate) and # activates the gems in the first one found. # # You can run this automatically when rubygems starts. To enable, set # the RUBYGEMS_GEMDEPS environment variable to either the path # of your gem dependencies file or "-" to auto-discover in parent # directories. # # NOTE: Enabling automatic discovery on multiuser systems can lead to # execution of arbitrary code when used from directories outside your # control. def self.use_gemdeps(path = nil) raise_exception = path path ||= ENV["RUBYGEMS_GEMDEPS"] return unless path path = path.dup if path == "-" Gem::Util.traverse_parents Dir.pwd do |directory| dep_file = GEM_DEP_FILES.find {|f| File.file?(f) } next unless dep_file path = File.join directory, dep_file break end end unless File.file? path return unless raise_exception raise ArgumentError, "Unable to find gem dependencies file at #{path}" end ENV["BUNDLE_GEMFILE"] ||= File.expand_path(path) require_relative "rubygems/user_interaction" require "bundler" begin Gem::DefaultUserInteraction.use_ui(ui) do Bundler.ui.silence do @gemdeps = Bundler.setup end ensure Gem::DefaultUserInteraction.ui.close end rescue Bundler::BundlerError => e warn e.message warn "You may need to `bundle install` to install missing gems" warn "" end end ## # If the SOURCE_DATE_EPOCH environment variable is set, returns it's value. # Otherwise, returns the time that +Gem.source_date_epoch_string+ was # first called in the same format as SOURCE_DATE_EPOCH. # # NOTE(@duckinator): The implementation is a tad weird because we want to: # 1. Make builds reproducible by default, by having this function always # return the same result during a given run. # 2. Allow changing ENV['SOURCE_DATE_EPOCH'] at runtime, since multiple # tests that set this variable will be run in a single process. # # If you simplify this function and a lot of tests fail, that is likely # due to #2 above. # # Details on SOURCE_DATE_EPOCH: # https://reproducible-builds.org/specs/source-date-epoch/ def self.source_date_epoch_string # The value used if $SOURCE_DATE_EPOCH is not set. @default_source_date_epoch ||= Time.now.to_i.to_s specified_epoch = ENV["SOURCE_DATE_EPOCH"] # If it's empty or just whitespace, treat it like it wasn't set at all. specified_epoch = nil if !specified_epoch.nil? && specified_epoch.strip.empty? epoch = specified_epoch || @default_source_date_epoch epoch.strip end ## # Returns the value of Gem.source_date_epoch_string, as a Time object. # # This is used throughout RubyGems for enabling reproducible builds. def self.source_date_epoch Time.at(source_date_epoch_string.to_i).utc.freeze end # FIX: Almost everywhere else we use the `def self.` way of defining class # methods, and then we switch over to `class << self` here. Pick one or the # other. class << self ## # RubyGems distributors (like operating system package managers) can # disable RubyGems update by setting this to error message printed to # end-users on gem update --system instead of actual update. attr_accessor :disable_system_update_message ## # Whether RubyGems should enhance builtin `require` to automatically # check whether the path required is present in installed gems, and # automatically activate them and add them to `$LOAD_PATH`. attr_accessor :discover_gems_on_require ## # Hash of loaded Gem::Specification keyed by name attr_reader :loaded_specs ## # GemDependencyAPI object, which is set when .use_gemdeps is called. # This contains all the information from the Gemfile. attr_reader :gemdeps ## # Register a Gem::Specification for default gem. # # Two formats for the specification are supported: # # * MRI 2.0 style, where spec.files contains unprefixed require names. # The spec's filenames will be registered as-is. # * New style, where spec.files contains files prefixed with paths # from spec.require_paths. The prefixes are stripped before # registering the spec's filenames. Unprefixed files are omitted. # def register_default_spec(spec) extended_require_paths = spec.require_paths.map {|f| f + "/" } new_format = extended_require_paths.any? {|path| spec.files.any? {|f| f.start_with? path } } if new_format prefix_group = extended_require_paths.join("|") prefix_pattern = /^(#{prefix_group})/ end spec.files.each do |file| if new_format file = file.sub(prefix_pattern, "") next unless $~ end spec.activate if already_loaded?(file) @path_to_default_spec_map[file] = spec @path_to_default_spec_map[file.sub(suffix_regexp, "")] = spec end end ## # Find a Gem::Specification of default gem from +path+ def find_default_spec(path) @path_to_default_spec_map[path] end ## # Find an unresolved Gem::Specification of default gem from +path+ def find_unresolved_default_spec(path) default_spec = @path_to_default_spec_map[path] default_spec if default_spec && loaded_specs[default_spec.name] != default_spec end ## # Clear default gem related variables. It is for test def clear_default_specs @path_to_default_spec_map.clear end ## # The list of hooks to be run after Gem::Installer#install extracts files # and builds extensions attr_reader :post_build_hooks ## # The list of hooks to be run after Gem::Installer#install completes # installation attr_reader :post_install_hooks ## # The list of hooks to be run after Gem::DependencyInstaller installs a # set of gems attr_reader :done_installing_hooks ## # The list of hooks to be run after Gem::Specification.reset is run. attr_reader :post_reset_hooks ## # The list of hooks to be run after Gem::Uninstaller#uninstall completes # installation attr_reader :post_uninstall_hooks ## # The list of hooks to be run before Gem::Installer#install does any work attr_reader :pre_install_hooks ## # The list of hooks to be run before Gem::Specification.reset is run. attr_reader :pre_reset_hooks ## # The list of hooks to be run before Gem::Uninstaller#uninstall does any # work attr_reader :pre_uninstall_hooks private def already_loaded?(file) $LOADED_FEATURES.any? do |feature_path| feature_path.end_with?(file) && default_gem_load_paths.any? {|load_path_entry| feature_path == "#{load_path_entry}/#{file}" } end end def default_gem_load_paths @default_gem_load_paths ||= $LOAD_PATH[load_path_insert_index..-1].map do |lp| expanded = File.expand_path(lp) next expanded unless File.exist?(expanded) File.realpath(expanded) end end end ## # Location of Marshal quick gemspecs on remote repositories MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/".freeze autoload :ConfigFile, File.expand_path("rubygems/config_file", __dir__) autoload :CIDetector, File.expand_path("rubygems/ci_detector", __dir__) autoload :Dependency, File.expand_path("rubygems/dependency", __dir__) autoload :DependencyList, File.expand_path("rubygems/dependency_list", __dir__) autoload :Installer, File.expand_path("rubygems/installer", __dir__) autoload :Licenses, File.expand_path("rubygems/util/licenses", __dir__) autoload :NameTuple, File.expand_path("rubygems/name_tuple", __dir__) autoload :PathSupport, File.expand_path("rubygems/path_support", __dir__) autoload :RequestSet, File.expand_path("rubygems/request_set", __dir__) autoload :Requirement, File.expand_path("rubygems/requirement", __dir__) autoload :Resolver, File.expand_path("rubygems/resolver", __dir__) autoload :Source, File.expand_path("rubygems/source", __dir__) autoload :SourceList, File.expand_path("rubygems/source_list", __dir__) autoload :SpecFetcher, File.expand_path("rubygems/spec_fetcher", __dir__) autoload :SpecificationPolicy, File.expand_path("rubygems/specification_policy", __dir__) autoload :Util, File.expand_path("rubygems/util", __dir__) autoload :Version, File.expand_path("rubygems/version", __dir__) end require_relative "rubygems/exceptions" require_relative "rubygems/specification" # REFACTOR: This should be pulled out into some kind of hacks file. begin ## # Defaults the operating system (or packager) wants to provide for RubyGems. require "rubygems/defaults/operating_system" rescue LoadError # Ignored rescue StandardError => e path = e.backtrace_locations.reverse.find {|l| l.path.end_with?("rubygems/defaults/operating_system.rb") }.path msg = "#{e.message}\n" \ "Loading the #{path} file caused an error. " \ "This file is owned by your OS, not by rubygems upstream. " \ "Please find out which OS package this file belongs to and follow the guidelines from your OS to report " \ "the problem and ask for help." raise e.class, msg end begin ## # Defaults the Ruby implementation wants to provide for RubyGems require "rubygems/defaults/#{RUBY_ENGINE}" rescue LoadError end # TruffleRuby >= 24 defines REUSE_AS_BINARY_ON_TRUFFLERUBY in defaults/truffleruby. # However, TruffleRuby < 24 defines REUSE_AS_BINARY_ON_TRUFFLERUBY directly in its copy # of lib/rubygems/platform.rb, so it is not defined if RubyGems is updated (gem update --system). # Instead, we define it here in that case, similar to bundler/lib/bundler/rubygems_ext.rb. # We must define it here and not in platform.rb because platform.rb is loaded before defaults/truffleruby. class Gem::Platform if RUBY_ENGINE == "truffleruby" && !defined?(REUSE_AS_BINARY_ON_TRUFFLERUBY) REUSE_AS_BINARY_ON_TRUFFLERUBY = %w[libv8 libv8-node sorbet-static].freeze end end ## # Loads the default specs. Gem::Specification.load_defaults require_relative "rubygems/core_ext/kernel_gem" path = File.join(__dir__, "rubygems/core_ext/kernel_require.rb") # When https://bugs.ruby-lang.org/issues/17259 is available, there is no need to override Kernel#warn if RUBY_ENGINE == "truffleruby" || RUBY_ENGINE == "ruby" file = "" else require_relative "rubygems/core_ext/kernel_warn" file = path end eval File.read(path), nil, file require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler) PK!5{y&-&-rubygems/request_set.rbnu[# frozen_string_literal: true require_relative "vendored_tsort" ## # A RequestSet groups a request to activate a set of dependencies. # # nokogiri = Gem::Dependency.new 'nokogiri', '~> 1.6' # pg = Gem::Dependency.new 'pg', '~> 0.14' # # set = Gem::RequestSet.new nokogiri, pg # # requests = set.resolve # # p requests.map { |r| r.full_name } # #=> ["nokogiri-1.6.0", "mini_portile-0.5.1", "pg-0.17.0"] class Gem::RequestSet include Gem::TSort ## # Array of gems to install even if already installed attr_accessor :always_install attr_reader :dependencies attr_accessor :development ## # Errors fetching gems during resolution. attr_reader :errors ## # Set to true if you want to install only direct development dependencies. attr_accessor :development_shallow ## # The set of git gems imported via load_gemdeps. attr_reader :git_set # :nodoc: ## # When true, dependency resolution is not performed, only the requested gems # are installed. attr_accessor :ignore_dependencies attr_reader :install_dir # :nodoc: ## # If true, allow dependencies to match prerelease gems. attr_accessor :prerelease ## # When false no remote sets are used for resolving gems. attr_accessor :remote attr_reader :resolver # :nodoc: ## # Sets used for resolution attr_reader :sets # :nodoc: ## # Treat missing dependencies as silent errors attr_accessor :soft_missing ## # The set of vendor gems imported via load_gemdeps. attr_reader :vendor_set # :nodoc: ## # The set of source gems imported via load_gemdeps. attr_reader :source_set ## # Creates a RequestSet for a list of Gem::Dependency objects, +deps+. You # can then #resolve and #install the resolved list of dependencies. # # nokogiri = Gem::Dependency.new 'nokogiri', '~> 1.6' # pg = Gem::Dependency.new 'pg', '~> 0.14' # # set = Gem::RequestSet.new nokogiri, pg def initialize(*deps) @dependencies = deps @always_install = [] @conservative = false @dependency_names = {} @development = false @development_shallow = false @errors = [] @git_set = nil @ignore_dependencies = false @install_dir = Gem.dir @prerelease = false @remote = true @requests = [] @sets = [] @soft_missing = false @sorted_requests = nil @specs = nil @vendor_set = nil @source_set = nil yield self if block_given? end ## # Declare that a gem of name +name+ with +reqs+ requirements is needed. def gem(name, *reqs) if dep = @dependency_names[name] dep.requirement.concat reqs else dep = Gem::Dependency.new name, *reqs @dependency_names[name] = dep @dependencies << dep end end ## # Add +deps+ Gem::Dependency objects to the set. def import(deps) @dependencies.concat deps end ## # Installs gems for this RequestSet using the Gem::Installer +options+. # # If a +block+ is given an activation +request+ and +installer+ are yielded. # The +installer+ will be +nil+ if a gem matching the request was already # installed. def install(options, &block) # :yields: request, installer if dir = options[:install_dir] requests = install_into dir, false, options, &block return requests end @prerelease = options[:prerelease] requests = [] download_queue = Thread::Queue.new # Create a thread-safe list of gems to download sorted_requests.each do |req| download_queue << req end # Create N threads in a pool, have them download all the gems threads = Array.new(Gem.configuration.concurrent_downloads) do # When a thread pops this item, it knows to stop running. The symbol # is queued here so that there will be one symbol per thread. download_queue << :stop Thread.new do # The pop method will block waiting for items, so the only way # to stop a thread from running is to provide a final item that # means the thread should stop. while req = download_queue.pop break if req == :stop req.spec.download options unless req.installed? end end end # Wait for all the downloads to finish before continuing threads.each(&:value) # Install requested gems after they have been downloaded sorted_requests.each do |req| if req.installed? req.spec.spec.build_extensions if @always_install.none? {|spec| spec == req.spec.spec } yield req, nil if block_given? next end end spec = begin req.spec.install options do |installer| yield req, installer if block_given? end rescue Gem::RuntimeRequirementNotMetError => e suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems" suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec) e.suggestion = suggestion raise end requests << spec end return requests if options[:gemdeps] install_hooks requests, options requests end ## # Installs from the gem dependencies files in the +:gemdeps+ option in # +options+, yielding to the +block+ as in #install. # # If +:without_groups+ is given in the +options+, those groups in the gem # dependencies file are not used. See Gem::Installer for other +options+. def install_from_gemdeps(options, &block) gemdeps = options[:gemdeps] @install_dir = options[:install_dir] || Gem.dir @prerelease = options[:prerelease] @remote = options[:domain] != :local @conservative = true if options[:conservative] gem_deps_api = load_gemdeps gemdeps, options[:without_groups], true resolve if options[:explain] puts "Gems to install:" sorted_requests.each do |spec| puts " #{spec.full_name}" end if Gem.configuration.really_verbose @resolver.stats.display end else installed = install options, &block if options.fetch :lock, true lockfile = Gem::RequestSet::Lockfile.build self, gemdeps, gem_deps_api.dependencies lockfile.write end installed end end def install_into(dir, force = true, options = {}) gem_home = ENV["GEM_HOME"] ENV["GEM_HOME"] = dir existing = force ? [] : specs_in(dir) existing.delete_if {|s| @always_install.include? s } dir = File.expand_path dir installed = [] options[:development] = false options[:install_dir] = dir options[:only_install_dir] = true @prerelease = options[:prerelease] sorted_requests.each do |request| spec = request.spec if existing.find {|s| s.full_name == spec.full_name } yield request, nil if block_given? next end spec.install options do |installer| yield request, installer if block_given? end installed << request end install_hooks installed, options installed ensure ENV["GEM_HOME"] = gem_home end ## # Call hooks on installed gems def install_hooks(requests, options) specs = requests.map do |request| case request when Gem::Resolver::ActivationRequest then request.spec.spec else request end end require_relative "dependency_installer" inst = Gem::DependencyInstaller.new options inst.installed_gems.replace specs Gem.done_installing_hooks.each do |hook| hook.call inst, specs end unless Gem.done_installing_hooks.empty? end ## # Load a dependency management file. def load_gemdeps(path, without_groups = [], installing = false) @git_set = Gem::Resolver::GitSet.new @vendor_set = Gem::Resolver::VendorSet.new @source_set = Gem::Resolver::SourceSet.new @git_set.root_dir = @install_dir lock_file = "#{File.expand_path(path)}.lock" begin tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file lock_file parser = tokenizer.make_parser self, [] parser.parse rescue Errno::ENOENT end gf = Gem::RequestSet::GemDependencyAPI.new self, path gf.installing = installing gf.without_groups = without_groups if without_groups gf.load end def pretty_print(q) # :nodoc: q.group 2, "[RequestSet:", "]" do q.breakable if @remote q.text "remote" q.breakable end if @prerelease q.text "prerelease" q.breakable end if @development_shallow q.text "shallow development" q.breakable elsif @development q.text "development" q.breakable end if @soft_missing q.text "soft missing" end q.group 2, "[dependencies:", "]" do q.breakable @dependencies.map do |dep| q.text dep.to_s q.breakable end end q.breakable q.text "sets:" q.breakable q.pp @sets.map(&:class) end end ## # Resolve the requested dependencies and return an Array of Specification # objects to be activated. def resolve(set = Gem::Resolver::BestSet.new) @sets << set @sets << @git_set @sets << @vendor_set @sets << @source_set set = Gem::Resolver.compose_sets(*@sets) set.remote = @remote set.prerelease = @prerelease resolver = Gem::Resolver.new @dependencies, set resolver.development = @development resolver.development_shallow = @development_shallow resolver.ignore_dependencies = @ignore_dependencies resolver.soft_missing = @soft_missing if @conservative installed_gems = {} Gem::Specification.find_all do |spec| (installed_gems[spec.name] ||= []) << spec end resolver.skip_gems = installed_gems end @resolver = resolver @requests = resolver.resolve @errors = set.errors @requests end ## # Resolve the requested dependencies against the gems available via Gem.path # and return an Array of Specification objects to be activated. def resolve_current resolve Gem::Resolver::CurrentSet.new end def sorted_requests @sorted_requests ||= strongly_connected_components.flatten end def specs @specs ||= @requests.map(&:full_spec) end def specs_in(dir) Gem::Util.glob_files_in_dir("*.gemspec", File.join(dir, "specifications")).map do |g| Gem::Specification.load g end end def tsort_each_node(&block) # :nodoc: @requests.each(&block) end def tsort_each_child(node) # :nodoc: node.spec.dependencies.each do |dep| next if dep.type == :development && !@development match = @requests.find do |r| dep.match?(r.spec.name, r.spec.version, r.spec.is_a?(Gem::Resolver::InstalledSpecification) || @prerelease) end unless match next if dep.type == :development && @development_shallow next if @soft_missing raise Gem::DependencyError, "Unresolved dependency found during sorting - #{dep} (requested by #{node.spec.full_name})" end yield match end end end require_relative "request_set/gem_dependency_api" require_relative "request_set/lockfile" require_relative "request_set/lockfile/tokenizer" PK!}33rubygems/version.rbnu[# frozen_string_literal: true require_relative "deprecate" ## # The Version class processes string versions into comparable # values. A version string should normally be a series of numbers # separated by periods. Each part (digits separated by periods) is # considered its own number, and these are used for sorting. So for # instance, 3.10 sorts higher than 3.2 because ten is greater than # two. # # If any part contains letters (currently only a-z are supported) then # that version is considered prerelease. Versions with a prerelease # part in the Nth part sort less than versions with N-1 # parts. Prerelease parts are sorted alphabetically using the normal # Ruby string sorting rules. If a prerelease part contains both # letters and numbers, it will be broken into multiple parts to # provide expected sort behavior (1.0.a10 becomes 1.0.a.10, and is # greater than 1.0.a9). # # Prereleases sort between real releases (newest to oldest): # # 1. 1.0 # 2. 1.0.b1 # 3. 1.0.a.2 # 4. 0.9 # # If you want to specify a version restriction that includes both prereleases # and regular releases of the 1.x series this is the best way: # # s.add_dependency 'example', '>= 1.0.0.a', '< 2.0.0' # # == How Software Changes # # Users expect to be able to specify a version constraint that gives them # some reasonable expectation that new versions of a library will work with # their software if the version constraint is true, and not work with their # software if the version constraint is false. In other words, the perfect # system will accept all compatible versions of the library and reject all # incompatible versions. # # Libraries change in 3 ways (well, more than 3, but stay focused here!). # # 1. The change may be an implementation detail only and have no effect on # the client software. # 2. The change may add new features, but do so in a way that client software # written to an earlier version is still compatible. # 3. The change may change the public interface of the library in such a way # that old software is no longer compatible. # # Some examples are appropriate at this point. Suppose I have a Stack class # that supports a push and a pop method. # # === Examples of Category 1 changes: # # * Switch from an array based implementation to a linked-list based # implementation. # * Provide an automatic (and transparent) backing store for large stacks. # # === Examples of Category 2 changes might be: # # * Add a depth method to return the current depth of the stack. # * Add a top method that returns the current top of stack (without # changing the stack). # * Change push so that it returns the item pushed (previously it # had no usable return value). # # === Examples of Category 3 changes might be: # # * Changes pop so that it no longer returns a value (you must use # top to get the top of the stack). # * Rename the methods to push_item and pop_item. # # == RubyGems Rational Versioning # # * Versions shall be represented by three non-negative integers, separated # by periods (e.g. 3.1.4). The first integers is the "major" version # number, the second integer is the "minor" version number, and the third # integer is the "build" number. # # * A category 1 change (implementation detail) will increment the build # number. # # * A category 2 change (backwards compatible) will increment the minor # version number and reset the build number. # # * A category 3 change (incompatible) will increment the major build number # and reset the minor and build numbers. # # * Any "public" release of a gem should have a different version. Normally # that means incrementing the build number. This means a developer can # generate builds all day long, but as soon as they make a public release, # the version must be updated. # # === Examples # # Let's work through a project lifecycle using our Stack example from above. # # Version 0.0.1:: The initial Stack class is release. # Version 0.0.2:: Switched to a linked=list implementation because it is # cooler. # Version 0.1.0:: Added a depth method. # Version 1.0.0:: Added top and made pop return nil # (pop used to return the old top item). # Version 1.1.0:: push now returns the value pushed (it used it # return nil). # Version 1.1.1:: Fixed a bug in the linked list implementation. # Version 1.1.2:: Fixed a bug introduced in the last fix. # # Client A needs a stack with basic push/pop capability. They write to the # original interface (no top), so their version constraint looks like: # # gem 'stack', '>= 0.0' # # Essentially, any version is OK with Client A. An incompatible change to # the library will cause them grief, but they are willing to take the chance # (we call Client A optimistic). # # Client B is just like Client A except for two things: (1) They use the # depth method and (2) they are worried about future # incompatibilities, so they write their version constraint like this: # # gem 'stack', '~> 0.1' # # The depth method was introduced in version 0.1.0, so that version # or anything later is fine, as long as the version stays below version 1.0 # where incompatibilities are introduced. We call Client B pessimistic # because they are worried about incompatible future changes (it is OK to be # pessimistic!). # # == Preventing Version Catastrophe: # # From: https://www.zenspider.com/ruby/2008/10/rubygems-how-to-preventing-catastrophe.html # # Let's say you're depending on the fnord gem version 2.y.z. If you # specify your dependency as ">= 2.0.0" then, you're good, right? What # happens if fnord 3.0 comes out and it isn't backwards compatible # with 2.y.z? Your stuff will break as a result of using ">=". The # better route is to specify your dependency with an "approximate" version # specifier ("~>"). They're a tad confusing, so here is how the dependency # specifiers work: # # Specification From ... To (exclusive) # ">= 3.0" 3.0 ... ∞ # "~> 3.0" 3.0 ... 4.0 # "~> 3.0.0" 3.0.0 ... 3.1 # "~> 3.5" 3.5 ... 4.0 # "~> 3.5.0" 3.5.0 ... 3.6 # "~> 3" 3.0 ... 4.0 # # For the last example, single-digit versions are automatically extended with # a zero to give a sensible result. class Gem::Version include Comparable VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc: ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc: ## # A string representation of this Version. def version @version end alias_method :to_s, :version ## # True if the +version+ string matches RubyGems' requirements. def self.correct?(version) nil_versions_are_discouraged! if version.nil? ANCHORED_VERSION_PATTERN.match?(version.to_s) end ## # Factory method to create a Version object. Input may be a Version # or a String. Intended to simplify client code. # # ver1 = Version.create('1.3.17') # -> (Version object) # ver2 = Version.create(ver1) # -> (ver1) # ver3 = Version.create(nil) # -> nil def self.create(input) if self === input # check yourself before you wreck yourself input elsif input.nil? nil_versions_are_discouraged! nil else new input end end @@all = {} @@bump = {} @@release = {} def self.new(version) # :nodoc: return super unless self == Gem::Version @@all[version] ||= super end def self.nil_versions_are_discouraged! unless Gem::Deprecate.skip warn "nil versions are discouraged and will be deprecated in Rubygems 4" end end private_class_method :nil_versions_are_discouraged! ## # Constructs a Version from the +version+ string. A version string is a # series of digits or ASCII letters separated by dots. def initialize(version) unless self.class.correct?(version) raise ArgumentError, "Malformed version number string #{version}" end # If version is an empty string convert it to 0 version = 0 if version.is_a?(String) && /\A\s*\Z/.match?(version) @version = version.to_s # optimization to avoid allocation when given an integer, since we know # it's to_s won't have any spaces or dashes unless version.is_a?(Integer) @version = @version.strip @version.gsub!("-",".pre.") end @version = -@version @segments = nil end ## # Return a new version object where the next to the last revision # number is one greater (e.g., 5.3.1 => 5.4). # # Pre-release (alpha) parts, e.g, 5.3.1.b.2 => 5.4, are ignored. def bump @@bump[self] ||= begin segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop if segments.size > 1 segments[-1] = segments[-1].succ self.class.new segments.join(".") end end ## # A Version is only eql? to another version if it's specified to the # same precision. Version "1.0" is not the same as version "1". def eql?(other) self.class === other && @version == other.version end def hash # :nodoc: canonical_segments.hash end def init_with(coder) # :nodoc: yaml_initialize coder.tag, coder.map end def inspect # :nodoc: "#<#{self.class} #{version.inspect}>" end ## # Dump only the raw version string, not the complete object. It's a # string for backwards (RubyGems 1.3.5 and earlier) compatibility. def marshal_dump [@version] end ## # Load custom marshal format. It's a string for backwards (RubyGems # 1.3.5 and earlier) compatibility. def marshal_load(array) initialize array[0] end def yaml_initialize(tag, map) # :nodoc: @version = -map["version"] @segments = nil @hash = nil end def to_yaml_properties # :nodoc: ["@version"] end def encode_with(coder) # :nodoc: coder.add "version", @version end ## # A version is considered a prerelease if it contains a letter. def prerelease? unless instance_variable_defined? :@prerelease @prerelease = /[a-zA-Z]/.match?(version) end @prerelease end def pretty_print(q) # :nodoc: q.text "Gem::Version.new(#{version.inspect})" end ## # The release for this version (e.g. 1.2.0.a -> 1.2.0). # Non-prerelease versions return themselves. def release @@release[self] ||= if prerelease? segments = self.segments segments.pop while segments.any? {|s| String === s } self.class.new segments.join(".") else self end end def segments # :nodoc: _segments.dup end ## # A recommended version for use with a ~> Requirement. def approximate_recommendation segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 recommendation = "~> #{segments.join(".")}" recommendation += ".a" if prerelease? recommendation end ## # Compares this version with +other+ returning -1, 0, or 1 if the # other version is larger, the same, or smaller than this # one. Attempts to compare to something that's not a # Gem::Version or a valid version String return +nil+. def <=>(other) return self <=> self.class.new(other) if (String === other) && self.class.correct?(other) return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments lhsegments = canonical_segments rhsegments = other.canonical_segments lhsize = lhsegments.size rhsize = rhsegments.size limit = (lhsize > rhsize ? lhsize : rhsize) - 1 i = 0 while i <= limit lhs = lhsegments[i] || 0 rhs = rhsegments[i] || 0 i += 1 next if lhs == rhs return -1 if String === lhs && Numeric === rhs return 1 if Numeric === lhs && String === rhs return lhs <=> rhs end 0 end # remove trailing zeros segments before first letter or at the end of the version def canonical_segments @canonical_segments ||= begin # remove trailing 0 segments, using dot or letter as anchor # may leave a trailing dot which will be ignored by partition_segments canonical_version = @version.sub(/(?<=[a-zA-Z.])[.0]+\z/, "") # remove 0 segments before the first letter in a prerelease version canonical_version.sub!(/(?<=\.|\A)[0.]+(?=[a-zA-Z])/, "") if prerelease? partition_segments(canonical_version) end end def freeze prerelease? _segments canonical_segments super end protected def _segments # segments is lazy so it can pick up version values that come from # old marshaled versions, which don't go through marshal_load. # since this version object is cached in @@all, its @segments should be frozen @segments ||= partition_segments(@version) end def partition_segments(ver) ver.scan(/\d+|[a-z]+/i).map! do |s| /\A\d/.match?(s) ? s.to_i : -s end.freeze end end PK!t BBrubygems/text.rbnu[# frozen_string_literal: true ## # A collection of text-wrangling methods module Gem::Text ## # Remove any non-printable characters and make the text suitable for # printing. def clean_text(text) text.gsub(/[\000-\b\v-\f\016-\037\177]/, ".") end def truncate_text(text, description, max_length = 100_000) raise ArgumentError, "max_length must be positive" unless max_length > 0 return text if text.size <= max_length "Truncating #{description} to #{max_length.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse} characters:\n" + text[0, max_length] end ## # Wraps +text+ to +wrap+ characters and optionally indents by +indent+ # characters def format_text(text, wrap, indent=0) result = [] work = clean_text(text) while work.length > wrap do if work =~ /^(.{0,#{wrap}})[ \n]/ result << $1.rstrip work.slice!(0, $&.length) else result << work.slice!(0, wrap) end end result << work if work.length.nonzero? result.join("\n").gsub(/^/, " " * indent) end def min3(a, b, c) # :nodoc: if a < b && a < c a elsif b < c b else c end end # Returns a value representing the "cost" of transforming str1 into str2 # Vendored version of DidYouMean::Levenshtein.distance from the ruby/did_you_mean gem @ 1.4.0 # https://github.com/ruby/did_you_mean/blob/2ddf39b874808685965dbc47d344cf6c7651807c/lib/did_you_mean/levenshtein.rb#L7-L37 def levenshtein_distance(str1, str2) n = str1.length m = str2.length return m if n.zero? return n if m.zero? d = (0..m).to_a x = nil # to avoid duplicating an enumerable object, create it outside of the loop str2_codepoints = str2.codepoints str1.each_codepoint.with_index(1) do |char1, i| j = 0 while j < m cost = char1 == str2_codepoints[j] ? 0 : 1 x = min3( d[j + 1] + 1, # insertion i + 1, # deletion d[j] + cost # substitution ) d[j] = i i = x j += 1 end d[m] = x end x end end PK!Z_ $rubygems/request/connection_pools.rbnu[# frozen_string_literal: true class Gem::Request::ConnectionPools # :nodoc: @client = Gem::Net::HTTP class << self attr_accessor :client end def initialize(proxy_uri, cert_files) @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} @pool_mutex = Thread::Mutex.new end def pool_for(uri) http_args = net_http_args(uri, @proxy_uri) key = http_args + [https?(uri)] @pool_mutex.synchronize do @pools[key] ||= if https? uri Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri) else Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri) end end end def close_all @pools.each_value(&:close_all) end private ## # Returns list of no_proxy entries (if any) from the environment def get_no_proxy_from_env env_no_proxy = ENV["no_proxy"] || ENV["NO_PROXY"] return [] if env_no_proxy.nil? || env_no_proxy.empty? env_no_proxy.split(/\s*,\s*/) end def https?(uri) uri.scheme.casecmp("https").zero? end def no_proxy?(host, env_no_proxy) host = host.downcase env_no_proxy.any? do |pattern| env_no_proxy_pattern = pattern.downcase.dup # Remove dot in front of pattern for wildcard matching env_no_proxy_pattern[0] = "" if env_no_proxy_pattern[0] == "." host_tokens = host.split(".") pattern_tokens = env_no_proxy_pattern.split(".") intersection = (host_tokens - pattern_tokens) | (pattern_tokens - host_tokens) # When we do the split into tokens we miss a dot character, so add it back if we need it missing_dot = intersection.length > 0 ? 1 : 0 start = intersection.join(".").size + missing_dot no_proxy_host = host[start..-1] env_no_proxy_pattern == no_proxy_host end end def net_http_args(uri, proxy_uri) hostname = uri.hostname net_http_args = [hostname, uri.port] no_proxy = get_no_proxy_from_env if proxy_uri && !no_proxy?(hostname, no_proxy) proxy_hostname = proxy_uri.respond_to?(:hostname) ? proxy_uri.hostname : proxy_uri.host net_http_args + [ proxy_hostname, proxy_uri.port, Gem::UriFormatter.new(proxy_uri.user).unescape, Gem::UriFormatter.new(proxy_uri.password).unescape, ] elsif no_proxy? hostname, no_proxy net_http_args + [nil, nil] else net_http_args end end end PK!fޟrubygems/request/https_pool.rbnu[# frozen_string_literal: true class Gem::Request::HTTPSPool < Gem::Request::HTTPPool # :nodoc: private def setup_connection(connection) Gem::Request.configure_connection_for_https(connection, @cert_files) super end end PK! @rubygems/request/http_pool.rbnu[# frozen_string_literal: true ## # A connection "pool" that only manages one connection for now. Provides # thread safe `checkout` and `checkin` methods. The pool consists of one # connection that corresponds to `http_args`. This class is private, do not # use it. class Gem::Request::HTTPPool # :nodoc: attr_reader :cert_files, :proxy_uri def initialize(http_args, cert_files, proxy_uri) @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri @queue = Thread::SizedQueue.new 1 @queue << nil end def checkout @queue.pop || make_connection end def checkin(connection) @queue.push connection end def close_all until @queue.empty? if (connection = @queue.pop(true)) && connection.started? connection.finish end end @queue.push(nil) end private def make_connection setup_connection Gem::Request::ConnectionPools.client.new(*@http_args) end def setup_connection(connection) connection.start connection end end PK!*g!g!rubygems/platform.rbnu[# frozen_string_literal: true require_relative "deprecate" ## # Available list of platforms for targeting Gem installations. # # See `gem help platform` for information on platform matching. class Gem::Platform @local = nil attr_accessor :cpu, :os, :version def self.local @local ||= begin arch = RbConfig::CONFIG["arch"] arch = "#{arch}_60" if /mswin(?:32|64)$/.match?(arch) new(arch) end end def self.match(platform) match_platforms?(platform, Gem.platforms) end class << self extend Gem::Deprecate rubygems_deprecate :match, "Gem::Platform.match_spec? or match_gem?" end def self.match_platforms?(platform, platforms) platform = Gem::Platform.new(platform) unless platform.is_a?(Gem::Platform) platforms.any? do |local_platform| platform.nil? || local_platform == platform || (local_platform != Gem::Platform::RUBY && platform =~ local_platform) end end private_class_method :match_platforms? def self.match_spec?(spec) match_gem?(spec.platform, spec.name) end if RUBY_ENGINE == "truffleruby" def self.match_gem?(platform, gem_name) raise "Not a string: #{gem_name.inspect}" unless String === gem_name if REUSE_AS_BINARY_ON_TRUFFLERUBY.include?(gem_name) match_platforms?(platform, [Gem::Platform::RUBY, Gem::Platform.local]) else match_platforms?(platform, Gem.platforms) end end else def self.match_gem?(platform, gem_name) match_platforms?(platform, Gem.platforms) end end def self.sort_priority(platform) platform == Gem::Platform::RUBY ? -1 : 1 end def self.installable?(spec) if spec.respond_to? :installable_platform? spec.installable_platform? else match_spec? spec end end def self.new(arch) # :nodoc: case arch when Gem::Platform::CURRENT then Gem::Platform.local when Gem::Platform::RUBY, nil, "" then Gem::Platform::RUBY else super end end def initialize(arch) case arch when Array then @cpu, @os, @version = arch when String then arch = arch.split "-" if arch.length > 2 && !arch.last.match?(/\d+(\.\d+)?$/) # reassemble x86-linux-{libc} extra = arch.pop arch.last << "-#{extra}" end cpu = arch.shift @cpu = case cpu when /i\d86/ then "x86" else cpu end if arch.length == 2 && arch.last.match?(/^\d+(\.\d+)?$/) # for command-line @os, @version = arch return end os, = arch if os.nil? @cpu = nil os = cpu end # legacy jruby @os, @version = case os when /aix(\d+)?/ then ["aix", $1] when /cygwin/ then ["cygwin", nil] when /darwin(\d+)?/ then ["darwin", $1] when /^macruby$/ then ["macruby", nil] when /freebsd(\d+)?/ then ["freebsd", $1] when /^java$/, /^jruby$/ then ["java", nil] when /^java([\d.]*)/ then ["java", $1] when /^dalvik(\d+)?$/ then ["dalvik", $1] when /^dotnet$/ then ["dotnet", nil] when /^dotnet([\d.]*)/ then ["dotnet", $1] when /linux-?(\w+)?/ then ["linux", $1] when /mingw32/ then ["mingw32", nil] when /mingw-?(\w+)?/ then ["mingw", $1] when /(mswin\d+)(\_(\d+))?/ then os = $1 version = $3 @cpu = "x86" if @cpu.nil? && os =~ /32$/ [os, version] when /netbsdelf/ then ["netbsdelf", nil] when /openbsd(\d+\.\d+)?/ then ["openbsd", $1] when /solaris(\d+\.\d+)?/ then ["solaris", $1] when /wasi/ then ["wasi", nil] # test when /^(\w+_platform)(\d+)?/ then [$1, $2] else ["unknown", nil] end when Gem::Platform then @cpu = arch.cpu @os = arch.os @version = arch.version else raise ArgumentError, "invalid argument #{arch.inspect}" end end def to_a [@cpu, @os, @version] end def to_s to_a.compact.join "-" end ## # Is +other+ equal to this platform? Two platforms are equal if they have # the same CPU, OS and version. def ==(other) self.class === other && to_a == other.to_a end alias_method :eql?, :== def hash # :nodoc: to_a.hash end ## # Does +other+ match this platform? Two platforms match if they have the # same CPU, or either has a CPU of 'universal', they have the same OS, and # they have the same version, or either one has no version # # Additionally, the platform will match if the local CPU is 'arm' and the # other CPU starts with "armv" (for generic 32-bit ARM family support). # # Of note, this method is not commutative. Indeed the OS 'linux' has a # special case: the version is the libc name, yet while "no version" stands # as a wildcard for a binary gem platform (as for other OSes), for the # runtime platform "no version" stands for 'gnu'. To be able to distinguish # these, the method receiver is the gem platform, while the argument is # the runtime platform. # #-- # NOTE: Until it can be removed, changes to this method must also be reflected in `bundler/lib/bundler/rubygems_ext.rb` def ===(other) return nil unless Gem::Platform === other # universal-mingw32 matches x64-mingw-ucrt return true if (@cpu == "universal" || other.cpu == "universal") && @os.start_with?("mingw") && other.os.start_with?("mingw") # cpu ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu || (@cpu == "arm" && other.cpu.start_with?("armv"))) && # os @os == other.os && # version ( (@os != "linux" && (@version.nil? || other.version.nil?)) || (@os == "linux" && (normalized_linux_version == other.normalized_linux_version || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) || @version == other.version ) end #-- # NOTE: Until it can be removed, changes to this method must also be reflected in `bundler/lib/bundler/rubygems_ext.rb` def normalized_linux_version return nil unless @version without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "") return nil if without_gnu_nor_abi_modifiers.empty? without_gnu_nor_abi_modifiers end ## # Does +other+ match this platform? If +other+ is a String it will be # converted to a Gem::Platform first. See #=== for matching rules. def =~(other) case other when Gem::Platform then # nop when String then # This data is from http://gems.rubyforge.org/gems/yaml on 19 Aug 2007 other = case other when /^i686-darwin(\d)/ then ["x86", "darwin", $1] when /^i\d86-linux/ then ["x86", "linux", nil] when "java", "jruby" then [nil, "java", nil] when /^dalvik(\d+)?$/ then [nil, "dalvik", $1] when /dotnet(\-(\d+\.\d+))?/ then ["universal","dotnet", $2] when /mswin32(\_(\d+))?/ then ["x86", "mswin32", $2] when /mswin64(\_(\d+))?/ then ["x64", "mswin64", $2] when "powerpc-darwin" then ["powerpc", "darwin", nil] when /powerpc-darwin(\d)/ then ["powerpc", "darwin", $1] when /sparc-solaris2.8/ then ["sparc", "solaris", "2.8"] when /universal-darwin(\d)/ then ["universal", "darwin", $1] else other end other = Gem::Platform.new other else return nil end self === other end ## # A pure-Ruby gem that may use Gem::Specification#extensions to build # binary files. RUBY = "ruby" ## # A platform-specific gem that is built for the packaging Ruby's platform. # This will be replaced with Gem::Platform::local. CURRENT = "current" end PK!8p!!rubygems/query_utils.rbnu[# frozen_string_literal: true require_relative "local_remote_options" require_relative "spec_fetcher" require_relative "version_option" require_relative "text" module Gem::QueryUtils include Gem::Text include Gem::LocalRemoteOptions include Gem::VersionOption def add_query_options add_option("-i", "--[no-]installed", "Check for installed gem") do |value, options| options[:installed] = value end add_option("-I", "Equivalent to --no-installed") do |_value, options| options[:installed] = false end add_version_option command, "for use with --installed" add_option("-d", "--[no-]details", "Display detailed information of gem(s)") do |value, options| options[:details] = value end add_option("--[no-]versions", "Display only gem names") do |value, options| options[:versions] = value options[:details] = false unless value end add_option("-a", "--all", "Display all gem versions") do |value, options| options[:all] = value end add_option("-e", "--exact", "Name of gem(s) to query on matches the", "provided STRING") do |value, options| options[:exact] = value end add_option("--[no-]prerelease", "Display prerelease versions") do |value, options| options[:prerelease] = value end add_local_remote_options end def defaults_str # :nodoc: "--local --no-details --versions --no-installed" end def execute gem_names = if args.empty? [options[:name]] else options[:exact] ? args.map {|arg| /\A#{Regexp.escape(arg)}\Z/ } : args.map {|arg| /#{arg}/i } end terminate_interaction(check_installed_gems(gem_names)) if check_installed_gems? gem_names.each {|n| show_gems(n) } end private def check_installed_gems(gem_names) exit_code = 0 if args.empty? && !gem_name? alert_error "You must specify a gem name" exit_code = 4 elsif gem_names.count > 1 alert_error "You must specify only ONE gem!" exit_code = 4 else installed = installed?(gem_names.first, options[:version]) installed = !installed unless options[:installed] say(installed) exit_code = 1 unless installed end exit_code end def check_installed_gems? !options[:installed].nil? end def gem_name? !options[:name].nil? end def prerelease options[:prerelease] end def show_prereleases? prerelease.nil? || prerelease end def args options[:args].to_a end def display_header(type) if (ui.outs.tty? && Gem.configuration.verbose) || both? say say "*** #{type} GEMS ***" say end end # Guts of original execute def show_gems(name) show_local_gems(name) if local? show_remote_gems(name) if remote? end def show_local_gems(name, req = Gem::Requirement.default) display_header("LOCAL") specs = Gem::Specification.find_all do |s| name_matches = name ? s.name =~ name : true version_matches = show_prereleases? || !s.version.prerelease? name_matches && version_matches end.uniq(&:full_name) spec_tuples = specs.map do |spec| [spec.name_tuple, spec] end output_query_results(spec_tuples) end def show_remote_gems(name) display_header("REMOTE") fetcher = Gem::SpecFetcher.fetcher spec_tuples = if name.nil? fetcher.detect(specs_type) { true } else fetcher.detect(specs_type) do |name_tuple| name === name_tuple.name && options[:version].satisfied_by?(name_tuple.version) end end output_query_results(spec_tuples) end def specs_type if options[:all] || options[:version].specific? if options[:prerelease] :complete else :released end elsif options[:prerelease] :prerelease else :latest end end ## # Check if gem +name+ version +version+ is installed. def installed?(name, req = Gem::Requirement.default) Gem::Specification.any? {|s| s.name =~ name && req =~ s.version } end def output_query_results(spec_tuples) output = [] versions = Hash.new {|h,name| h[name] = [] } spec_tuples.each do |spec_tuple, source| versions[spec_tuple.name] << [spec_tuple, source] end versions = versions.sort_by do |(n,_),_| n.downcase end output_versions output, versions say output.join(options[:details] ? "\n\n" : "\n") end def output_versions(output, versions) versions.each do |_gem_name, matching_tuples| matching_tuples = matching_tuples.sort_by {|n,_| n.version }.reverse platforms = Hash.new {|h,version| h[version] = [] } matching_tuples.each do |n, _| platforms[n.version] << n.platform if n.platform end seen = {} matching_tuples.delete_if do |n,_| if seen[n.version] true else seen[n.version] = true false end end output << clean_text(make_entry(matching_tuples, platforms)) end end def entry_details(entry, detail_tuple, specs, platforms) return unless options[:details] name_tuple, spec = detail_tuple spec = spec.fetch_spec(name_tuple)if spec.respond_to?(:fetch_spec) entry << "\n" spec_platforms entry, platforms spec_authors entry, spec spec_homepage entry, spec spec_license entry, spec spec_loaded_from entry, spec, specs spec_summary entry, spec end def entry_versions(entry, name_tuples, platforms, specs) return unless options[:versions] list = if platforms.empty? || options[:details] name_tuples.map(&:version).uniq else platforms.sort.reverse.map do |version, pls| out = version.to_s if options[:domain] == :local default = specs.any? do |s| !s.is_a?(Gem::Source) && s.version == version && s.default_gem? end out = "default: #{out}" if default end if pls != [Gem::Platform::RUBY] platform_list = [pls.delete(Gem::Platform::RUBY), *pls.sort].compact out = platform_list.unshift(out).join(" ") end out end end entry << " (#{list.join ", "})" end def make_entry(entry_tuples, platforms) detail_tuple = entry_tuples.first name_tuples, specs = entry_tuples.flatten.partition do |item| Gem::NameTuple === item end entry = [name_tuples.first.name] entry_versions(entry, name_tuples, platforms, specs) entry_details(entry, detail_tuple, specs, platforms) entry.join end def spec_authors(entry, spec) authors = "Author#{spec.authors.length > 1 ? "s" : ""}: ".dup authors << spec.authors.join(", ") entry << format_text(authors, 68, 4) end def spec_homepage(entry, spec) return if spec.homepage.nil? || spec.homepage.empty? entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4) end def spec_license(entry, spec) return if spec.license.nil? || spec.license.empty? licenses = "License#{spec.licenses.length > 1 ? "s" : ""}: ".dup licenses << spec.licenses.join(", ") entry << "\n" << format_text(licenses, 68, 4) end def spec_loaded_from(entry, spec, specs) return unless spec.loaded_from if specs.length == 1 default = spec.default_gem? ? " (default)" : nil entry << "\n" << " Installed at#{default}: #{spec.base_dir}" else label = "Installed at" specs.each do |s| version = s.version.to_s default = ", default" if s.default_gem? entry << "\n" << " #{label} (#{version}#{default}): #{s.base_dir}" label = " " * label.length end end end def spec_platforms(entry, platforms) non_ruby = platforms.any? do |_, pls| pls.any? {|pl| pl != Gem::Platform::RUBY } end return unless non_ruby if platforms.length == 1 title = platforms.values.length == 1 ? "Platform" : "Platforms" entry << " #{title}: #{platforms.values.sort.join(", ")}\n" else entry << " Platforms:\n" sorted_platforms = platforms.sort sorted_platforms.each do |version, pls| label = " #{version}: " data = format_text pls.sort.join(", "), 68, label.length data[0, label.length] = label entry << data << "\n" end end end def spec_summary(entry, spec) summary = truncate_text(spec.summary, "the summary for #{spec.full_name}") entry << "\n\n" << format_text(summary, 68, 4) end end PK!Brubygems/errors.rbnu[# frozen_string_literal: true #-- # This file contains all the various exceptions and other errors that are used # inside of RubyGems. # # DOC: Confirm _all_ #++ module Gem ## # Raised when RubyGems is unable to load or activate a gem. Contains the # name and version requirements of the gem that either conflicts with # already activated gems or that RubyGems is otherwise unable to activate. class LoadError < ::LoadError # Name of gem attr_accessor :name # Version requirement of gem attr_accessor :requirement end ## # Raised when trying to activate a gem, and that gem does not exist on the # system. Instead of rescuing from this class, make sure to rescue from the # superclass Gem::LoadError to catch all types of load errors. class MissingSpecError < Gem::LoadError def initialize(name, requirement, extra_message=nil) @name = name @requirement = requirement @extra_message = extra_message super(message) end def message # :nodoc: build_message + "Checked in 'GEM_PATH=#{Gem.path.join(File::PATH_SEPARATOR)}' #{@extra_message}, execute `gem env` for more information" end private def build_message total = Gem::Specification.stubs.size "Could not find '#{name}' (#{requirement}) among #{total} total gem(s)\n" end end ## # Raised when trying to activate a gem, and the gem exists on the system, but # not the requested version. Instead of rescuing from this class, make sure to # rescue from the superclass Gem::LoadError to catch all types of load errors. class MissingSpecVersionError < MissingSpecError attr_reader :specs def initialize(name, requirement, specs) @specs = specs super(name, requirement) end private def build_message names = specs.map(&:full_name) "Could not find '#{name}' (#{requirement}) - did find: [#{names.join ","}]\n" end end # Raised when there are conflicting gem specs loaded class ConflictError < LoadError ## # A Hash mapping conflicting specifications to the dependencies that # caused the conflict attr_reader :conflicts ## # The specification that had the conflict attr_reader :target def initialize(target, conflicts) @target = target @conflicts = conflicts @name = target.name reason = conflicts.map do |act, dependencies| "#{act.full_name} conflicts with #{dependencies.join(", ")}" end.join ", " # TODO: improve message by saying who activated `con` super("Unable to activate #{target.full_name}, because #{reason}") end end class ErrorReason; end # Generated when trying to lookup a gem to indicate that the gem # was found, but that it isn't usable on the current platform. # # fetch and install read these and report them to the user to aid # in figuring out why a gem couldn't be installed. # class PlatformMismatch < ErrorReason ## # the name of the gem attr_reader :name ## # the version attr_reader :version ## # The platforms that are mismatched attr_reader :platforms def initialize(name, version) @name = name @version = version @platforms = [] end ## # append a platform to the list of mismatched platforms. # # Platforms are added via this instead of injected via the constructor # so that we can loop over a list of mismatches and just add them rather # than perform some kind of calculation mismatch summary before creation. def add_platform(platform) @platforms << platform end ## # A wordy description of the error. def wordy format("Found %s (%s), but was for platform%s %s", @name, @version, @platforms.size == 1 ? "" : "s", @platforms.join(" ,")) end end ## # An error that indicates we weren't able to fetch some # data from a source class SourceFetchProblem < ErrorReason ## # Creates a new SourceFetchProblem for the given +source+ and +error+. def initialize(source, error) @source = source @error = error end ## # The source that had the fetch problem. attr_reader :source ## # The fetch error which is an Exception subclass. attr_reader :error ## # An English description of the error. def wordy "Unable to download data from #{Gem::Uri.redact(@source.uri)} - #{@error.message}" end ## # The "exception" alias allows you to call raise on a SourceFetchProblem. alias_method :exception, :error end end PK!(ղ,,rubygems/gemcutter_utilities.rbnu[# frozen_string_literal: true require_relative "remote_fetcher" require_relative "text" require_relative "gemcutter_utilities/webauthn_listener" require_relative "gemcutter_utilities/webauthn_poller" ## # Utility methods for using the RubyGems API. module Gem::GemcutterUtilities ERROR_CODE = 1 API_SCOPES = [:index_rubygems, :push_rubygem, :yank_rubygem, :add_owner, :remove_owner, :access_webhooks].freeze EXCLUSIVELY_API_SCOPES = [:show_dashboard].freeze include Gem::Text attr_writer :host attr_writer :scope ## # Add the --key option def add_key_option add_option("-k", "--key KEYNAME", Symbol, "Use the given API key", "from #{Gem.configuration.credentials_path}") do |value,options| options[:key] = value end end ## # Add the --otp option def add_otp_option add_option("--otp CODE", "Digit code for multifactor authentication", "You can also use the environment variable GEM_HOST_OTP_CODE") do |value, options| options[:otp] = value end end ## # The API key from the command options or from the user's configuration. def api_key if ENV["GEM_HOST_API_KEY"] ENV["GEM_HOST_API_KEY"] elsif options[:key] verify_api_key options[:key] elsif Gem.configuration.api_keys.key?(host) Gem.configuration.api_keys[host] else Gem.configuration.rubygems_api_key end end ## # The OTP code from the command options or from the user's configuration. def otp options[:otp] || ENV["GEM_HOST_OTP_CODE"] end ## # The host to connect to either from the RUBYGEMS_HOST environment variable # or from the user's configuration def host configured_host = Gem.host unless Gem.configuration.disable_default_gem_server @host ||= begin env_rubygems_host = ENV["RUBYGEMS_HOST"] env_rubygems_host = nil if env_rubygems_host&.empty? env_rubygems_host || configured_host end end ## # Creates an RubyGems API to +host+ and +path+ with the given HTTP +method+. # # If +allowed_push_host+ metadata is present, then it will only allow that host. def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, credentials: {}, &block) require_relative "vendored_net_http" self.host = host if host unless self.host alert_error "You must specify a gem server" terminate_interaction(ERROR_CODE) end if allowed_push_host allowed_host_uri = Gem::URI.parse(allowed_push_host) host_uri = Gem::URI.parse(self.host) unless (host_uri.scheme == allowed_host_uri.scheme) && (host_uri.host == allowed_host_uri.host) alert_error "#{self.host.inspect} is not allowed by the gemspec, which only allows #{allowed_push_host.inspect}" terminate_interaction(ERROR_CODE) end end uri = Gem::URI.parse "#{self.host}/#{path}" response = request_with_otp(method, uri, &block) if mfa_unauthorized?(response) fetch_otp(credentials) response = request_with_otp(method, uri, &block) end if api_key_forbidden?(response) update_scope(scope) request_with_otp(method, uri, &block) else response end end def mfa_unauthorized?(response) response.is_a?(Gem::Net::HTTPUnauthorized) && response.body.start_with?("You have enabled multifactor authentication") end def update_scope(scope) sign_in_host = host pretty_host = pretty_host(sign_in_host) update_scope_params = { scope => true } say "The existing key doesn't have access of #{scope} on #{pretty_host}. Please sign in to update access." identifier = ask "Username/email: " password = ask_for_password " Password: " response = rubygems_api_request(:put, "api/v1/api_key", sign_in_host, scope: scope) do |request| request.basic_auth identifier, password request["OTP"] = otp if otp request.body = Gem::URI.encode_www_form({ api_key: api_key }.merge(update_scope_params)) end with_response response do |_resp| say "Added #{scope} scope to the existing API key" end end ## # Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API # key. def sign_in(sign_in_host = nil, scope: nil) sign_in_host ||= host return if api_key pretty_host = pretty_host(sign_in_host) say "Enter your #{pretty_host} credentials." say "Don't have an account yet? " \ "Create one at #{sign_in_host}/sign_up" identifier = ask "Username/email: " password = ask_for_password " Password: " say "\n" key_name = get_key_name(scope) scope_params = get_scope_params(scope) profile = get_user_profile(identifier, password) mfa_params = get_mfa_params(profile) all_params = scope_params.merge(mfa_params) warning = profile["warning"] credentials = { identifier: identifier, password: password } say "#{warning}\n" if warning response = rubygems_api_request(:post, "api/v1/api_key", sign_in_host, credentials: credentials, scope: scope) do |request| request.basic_auth identifier, password request["OTP"] = otp if otp request.body = Gem::URI.encode_www_form({ name: key_name }.merge(all_params)) end with_response response do |resp| say "Signed in with API key: #{key_name}." set_api_key host, resp.body end end ## # Retrieves the pre-configured API key +key+ or terminates interaction with # an error. def verify_api_key(key) if Gem.configuration.api_keys.key? key Gem.configuration.api_keys[key] else alert_error "No such API key. Please add it to your configuration (done automatically on initial `gem push`)." terminate_interaction(ERROR_CODE) end end ## # If +response+ is an HTTP Success (2XX) response, yields the response if a # block was given or shows the response body to the user. # # If the response was not successful, shows an error to the user including # the +error_prefix+ and the response body. If the response was a permanent redirect, # shows an error to the user including the redirect location. def with_response(response, error_prefix = nil) case response when Gem::Net::HTTPSuccess then if block_given? yield response else say clean_text(response.body) end when Gem::Net::HTTPPermanentRedirect, Gem::Net::HTTPRedirection then message = "The request has redirected permanently to #{response["location"]}. Please check your defined push host URL." message = "#{error_prefix}: #{message}" if error_prefix say clean_text(message) terminate_interaction(ERROR_CODE) else message = response.body message = "#{error_prefix}: #{message}" if error_prefix say clean_text(message) terminate_interaction(ERROR_CODE) end end ## # Returns true when the user has enabled multifactor authentication from # +response+ text and no otp provided by options. def set_api_key(host, key) if default_host? Gem.configuration.rubygems_api_key = key else Gem.configuration.set_api_key host, key end end private def request_with_otp(method, uri, &block) request_method = Gem::Net::HTTP.const_get method.to_s.capitalize Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req| req["OTP"] = otp if otp block.call(req) end end def fetch_otp(credentials) options[:otp] = if webauthn_url = webauthn_verification_url(credentials) server = TCPServer.new 0 port = server.addr[1].to_s url_with_port = "#{webauthn_url}?port=#{port}" say "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option." threads = [WebauthnListener.listener_thread(host, server), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)] otp_thread = wait_for_otp_thread(*threads) threads.each(&:join) if error = otp_thread[:error] alert_error error.message terminate_interaction(1) end say "You are verified with a security device. You may close the browser window." otp_thread[:otp] else say "You have enabled multi-factor authentication. Please enter OTP code." ask "Code: " end end def wait_for_otp_thread(*threads) loop do threads.each do |otp_thread| return otp_thread unless otp_thread.alive? end sleep 0.1 end ensure threads.each(&:exit) end def webauthn_verification_url(credentials) response = rubygems_api_request(:post, "api/v1/webauthn_verification") do |request| if credentials.empty? request.add_field "Authorization", api_key else request.basic_auth credentials[:identifier], credentials[:password] end end response.is_a?(Gem::Net::HTTPSuccess) ? response.body : nil end def pretty_host(host) if default_host? "RubyGems.org" else host end end def get_scope_params(scope) scope_params = { index_rubygems: true } if scope scope_params = { scope => true } else say "The default access scope is:" scope_params.each do |k, _v| say " #{k}: y" end say "\n" customise = ask_yes_no("Do you want to customise scopes?", false) if customise EXCLUSIVELY_API_SCOPES.each do |excl_scope| selected = ask_yes_no("#{excl_scope} (exclusive scope, answering yes will not prompt for other scopes)", false) next unless selected return { excl_scope => true } end scope_params = {} API_SCOPES.each do |s| selected = ask_yes_no(s.to_s, false) scope_params[s] = true if selected end end say "\n" end scope_params end def default_host? host == Gem::DEFAULT_HOST end def get_user_profile(identifier, password) return {} unless default_host? response = rubygems_api_request(:get, "api/v1/profile/me.yaml") do |request| request.basic_auth identifier, password end with_response response do |resp| Gem::ConfigFile.load_with_rubygems_config_hash(clean_text(resp.body)) end end def get_mfa_params(profile) mfa_level = profile["mfa"] params = {} if ["ui_only", "ui_and_gem_signin"].include?(mfa_level) selected = ask_yes_no("Would you like to enable MFA for this key? (strongly recommended)") params["mfa"] = true if selected end params end def get_key_name(scope) hostname = Socket.gethostname || "unknown-host" user = ENV["USER"] || ENV["USERNAME"] || "unknown-user" ts = Time.now.strftime("%Y%m%d%H%M%S") default_key_name = "#{hostname}-#{user}-#{ts}" key_name = ask "API Key name [#{default_key_name}]: " unless scope if key_name.nil? || key_name.empty? default_key_name else key_name end end def api_key_forbidden?(response) response.is_a?(Gem::Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access") end end PK!<$'' rubygems/dependency_installer.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "dependency_list" require_relative "package" require_relative "installer" require_relative "spec_fetcher" require_relative "user_interaction" require_relative "available_set" require_relative "deprecate" ## # Installs a gem along with all its dependencies from local and remote gems. class Gem::DependencyInstaller include Gem::UserInteraction extend Gem::Deprecate DEFAULT_OPTIONS = { # :nodoc: env_shebang: false, document: %w[ri], domain: :both, # HACK: dup force: false, format_executable: false, # HACK: dup ignore_dependencies: false, prerelease: false, security_policy: nil, # HACK: NoSecurity requires OpenSSL. AlmostNo? Low? wrappers: true, build_args: nil, build_docs_in_background: false, install_as_default: false, }.freeze ## # Documentation types. For use by the Gem.done_installing hook attr_reader :document ## # Errors from SpecFetcher while searching for remote specifications attr_reader :errors ## # List of gems installed by #install in alphabetic order attr_reader :installed_gems ## # Creates a new installer instance. # # Options are: # :cache_dir:: Alternate repository path to store .gem files in. # :domain:: :local, :remote, or :both. :local only searches gems in the # current directory. :remote searches only gems in Gem::sources. # :both searches both. # :env_shebang:: See Gem::Installer::new. # :force:: See Gem::Installer#install. # :format_executable:: See Gem::Installer#initialize. # :ignore_dependencies:: Don't install any dependencies. # :install_dir:: See Gem::Installer#install. # :prerelease:: Allow prerelease versions. See #install. # :security_policy:: See Gem::Installer::new and Gem::Security. # :user_install:: See Gem::Installer.new # :wrappers:: See Gem::Installer::new # :build_args:: See Gem::Installer::new def initialize(options = {}) @only_install_dir = !options[:install_dir].nil? @install_dir = options[:install_dir] || Gem.dir @build_root = options[:build_root] options = DEFAULT_OPTIONS.merge options @bin_dir = options[:bin_dir] @dev_shallow = options[:dev_shallow] @development = options[:development] @document = options[:document] @domain = options[:domain] @env_shebang = options[:env_shebang] @force = options[:force] @format_executable = options[:format_executable] @ignore_dependencies = options[:ignore_dependencies] @prerelease = options[:prerelease] @security_policy = options[:security_policy] @user_install = options[:user_install] @wrappers = options[:wrappers] @build_args = options[:build_args] @build_docs_in_background = options[:build_docs_in_background] @install_as_default = options[:install_as_default] @dir_mode = options[:dir_mode] @data_mode = options[:data_mode] @prog_mode = options[:prog_mode] # Indicates that we should not try to update any deps unless # we absolutely must. @minimal_deps = options[:minimal_deps] @available = nil @installed_gems = [] @toplevel_specs = nil @cache_dir = options[:cache_dir] || @install_dir @errors = [] end ## # Indicated, based on the requested domain, if local # gems should be considered. def consider_local? @domain == :both || @domain == :local end ## # Indicated, based on the requested domain, if remote # gems should be considered. def consider_remote? @domain == :both || @domain == :remote end ## # Returns a list of pairs of gemspecs and source_uris that match # Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources) # sources. Gems are sorted with newer gems preferred over older gems, and # local gems preferred over remote gems. def find_gems_with_sources(dep, best_only=false) # :nodoc: set = Gem::AvailableSet.new if consider_local? sl = Gem::Source::Local.new if spec = sl.find_gem(dep.name) if dep.matches_spec? spec set.add spec, sl end end end if consider_remote? begin # This is pulled from #spec_for_dependency to allow # us to filter tuples before fetching specs. tuples, errors = Gem::SpecFetcher.fetcher.search_for_dependency dep if best_only && !tuples.empty? tuples.sort! do |a,b| if b[0].version == a[0].version if b[0].platform != Gem::Platform::RUBY 1 else -1 end else b[0].version <=> a[0].version end end tuples = [tuples.first] end specs = [] tuples.each do |tup, source| spec = source.fetch_spec(tup) rescue Gem::RemoteFetcher::FetchError => e errors << Gem::SourceFetchProblem.new(source, e) else specs << [spec, source] end if @errors @errors += errors else @errors = errors end set << specs rescue Gem::RemoteFetcher::FetchError => e # FIX if there is a problem talking to the network, we either need to always tell # the user (no really_verbose) or fail hard, not silently tell them that we just # couldn't find their requested gem. verbose do "Error fetching remote data:\t\t#{e.message}\n" \ "Falling back to local-only install" end @domain = :local end end set end rubygems_deprecate :find_gems_with_sources def in_background(what) # :nodoc: fork_happened = false if @build_docs_in_background && Process.respond_to?(:fork) begin Process.fork do yield end fork_happened = true say "#{what} in a background process." rescue NotImplementedError end end yield unless fork_happened end ## # Installs the gem +dep_or_name+ and all its dependencies. Returns an Array # of installed gem specifications. # # If the +:prerelease+ option is set and there is a prerelease for # +dep_or_name+ the prerelease version will be installed. # # Unless explicitly specified as a prerelease dependency, prerelease gems # that +dep_or_name+ depend on will not be installed. # # If c-1.a depends on b-1 and a-1.a and there is a gem b-1.a available then # c-1.a, b-1 and a-1.a will be installed. b-1.a will need to be installed # separately. def install(dep_or_name, version = Gem::Requirement.default) request_set = resolve_dependencies dep_or_name, version @installed_gems = [] options = { bin_dir: @bin_dir, build_args: @build_args, document: @document, env_shebang: @env_shebang, force: @force, format_executable: @format_executable, ignore_dependencies: @ignore_dependencies, prerelease: @prerelease, security_policy: @security_policy, user_install: @user_install, wrappers: @wrappers, build_root: @build_root, install_as_default: @install_as_default, dir_mode: @dir_mode, data_mode: @data_mode, prog_mode: @prog_mode, } options[:install_dir] = @install_dir if @only_install_dir request_set.install options do |_, installer| @installed_gems << installer.spec if installer end @installed_gems.sort! # Since this is currently only called for docs, we can be lazy and just say # it's documentation. Ideally the hook adder could decide whether to be in # the background or not, and what to call it. in_background "Installing documentation" do Gem.done_installing_hooks.each do |hook| hook.call self, @installed_gems end end unless Gem.done_installing_hooks.empty? @installed_gems end def install_development_deps # :nodoc: if @development && @dev_shallow :shallow elsif @development :all else :none end end def resolve_dependencies(dep_or_name, version) # :nodoc: request_set = Gem::RequestSet.new request_set.development = @development request_set.development_shallow = @dev_shallow request_set.soft_missing = @force request_set.prerelease = @prerelease installer_set = Gem::Resolver::InstallerSet.new @domain installer_set.ignore_installed = (@minimal_deps == false) || @only_install_dir installer_set.force = @force if consider_local? if dep_or_name =~ /\.gem$/ && File.file?(dep_or_name) src = Gem::Source::SpecificFile.new dep_or_name installer_set.add_local dep_or_name, src.spec, src version = src.spec.version if version == Gem::Requirement.default elsif dep_or_name =~ /\.gem$/ # rubocop:disable Performance/RegexpMatch Dir[dep_or_name].each do |name| src = Gem::Source::SpecificFile.new name installer_set.add_local dep_or_name, src.spec, src rescue Gem::Package::FormatError end # else This is a dependency. InstallerSet handles this case end end dependency = if spec = installer_set.local?(dep_or_name) installer_set.remote = nil if spec.dependencies.none? Gem::Dependency.new spec.name, version elsif String === dep_or_name Gem::Dependency.new dep_or_name, version else dep_or_name end dependency.prerelease = @prerelease request_set.import [dependency] installer_set.add_always_install dependency request_set.always_install = installer_set.always_install request_set.remote = installer_set.consider_remote? if @ignore_dependencies installer_set.ignore_dependencies = true request_set.ignore_dependencies = true request_set.soft_missing = true end request_set.resolve installer_set @errors.concat request_set.errors request_set end end PK!t "install", "login" => "signin", "logout" => "signout", }.freeze ## # Return the authoritative instance of the command manager. def self.instance @instance ||= new end ## # Returns self. Allows a CommandManager instance to stand # in for the class itself. def instance self end ## # Reset the authoritative instance of the command manager. def self.reset @instance = nil end ## # Register all the subcommands supported by the gem command. def initialize require_relative "vendored_timeout" @commands = {} BUILTIN_COMMANDS.each do |name| register_command name end end ## # Register the Symbol +command+ as a gem command. def register_command(command, obj=false) @commands[command] = obj end ## # Unregister the Symbol +command+ as a gem command. def unregister_command(command) @commands.delete command end ## # Returns a Command instance for +command_name+ def [](command_name) command_name = command_name.intern return nil if @commands[command_name].nil? @commands[command_name] ||= load_and_instantiate(command_name) end ## # Return a sorted list of all command names as strings. def command_names @commands.keys.collect(&:to_s).sort end ## # Run the command specified by +args+. def run(args, build_args=nil) process_args(args, build_args) rescue StandardError, Gem::Timeout::Error => ex if ex.respond_to?(:detailed_message) msg = ex.detailed_message(highlight: false).sub(/\A(.*?)(?: \(.+?\))/) { $1 } else msg = ex.message end alert_error clean_text("While executing gem ... (#{ex.class})\n #{msg}") ui.backtrace ex terminate_interaction(1) rescue Interrupt alert_error clean_text("Interrupted") terminate_interaction(1) end def process_args(args, build_args=nil) if args.empty? say Gem::Command::HELP terminate_interaction 1 end case args.first when "-h", "--help" then say Gem::Command::HELP terminate_interaction 0 when "-v", "--version" then say Gem::VERSION terminate_interaction 0 when "-C" then args.shift start_point = args.shift if Dir.exist?(start_point) Dir.chdir(start_point) { invoke_command(args, build_args) } else alert_error clean_text("#{start_point} isn't a directory.") terminate_interaction 1 end when /^-/ then alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.") terminate_interaction 1 else invoke_command(args, build_args) end end def find_command(cmd_name) cmd_name = find_alias_command cmd_name possibilities = find_command_possibilities cmd_name if possibilities.size > 1 raise Gem::CommandLineError, "Ambiguous command #{cmd_name} matches [#{possibilities.join(", ")}]" elsif possibilities.empty? raise Gem::UnknownCommandError.new(cmd_name) end self[possibilities.first] end def find_alias_command(cmd_name) alias_name = ALIAS_COMMANDS[cmd_name] alias_name ? alias_name : cmd_name end def find_command_possibilities(cmd_name) len = cmd_name.length found = command_names.select {|name| cmd_name == name[0, len] } exact = found.find {|name| name == cmd_name } exact ? [exact] : found end private def load_and_instantiate(command_name) command_name = command_name.to_s const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase } << "Command" begin begin require "rubygems/commands/#{command_name}_command" rescue LoadError # it may have been defined from a rubygems_plugin.rb file end Gem::Commands.const_get(const_name).new rescue StandardError => e alert_error clean_text("Loading command: #{command_name} (#{e.class})\n\t#{e}") ui.backtrace e end end def invoke_command(args, build_args) cmd_name = args.shift.downcase cmd = find_command cmd_name terminate_interaction 1 unless cmd cmd.deprecation_warning if cmd.deprecated? cmd.invoke_with_build_args args, build_args end end PK!(CCrubygems/install_message.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "user_interaction" ## # A default post-install hook that displays "Successfully installed # some_gem-1.0" Gem.post_install do |installer| ui = Gem::DefaultUserInteraction.ui ui.say "Successfully installed #{installer.spec.full_name}" end PK!W$rubygems/gem_runner.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" require_relative "command_manager" require_relative "deprecate" ## # Run an instance of the gem program. # # Gem::GemRunner is only intended for internal use by RubyGems itself. It # does not form any public API and may change at any time for any reason. # # If you would like to duplicate functionality of `gem` commands, use the # classes they call directly. class Gem::GemRunner def initialize @command_manager_class = Gem::CommandManager @config_file_class = Gem::ConfigFile end ## # Run the gem command with the following arguments. def run(args) build_args = extract_build_args args do_configuration args begin Gem.load_env_plugins rescue StandardError nil end Gem.load_plugins cmd = @command_manager_class.instance cmd.command_names.each do |command_name| config_args = Gem.configuration[command_name] config_args = case config_args when String config_args.split " " else Array(config_args) end Gem::Command.add_specific_extra_args command_name, config_args end cmd.run Gem.configuration.args, build_args end ## # Separates the build arguments (those following --) from the # other arguments in the list. def extract_build_args(args) # :nodoc: return [] unless offset = args.index("--") build_args = args.slice!(offset...args.length) build_args.shift build_args end private def do_configuration(args) Gem.configuration = @config_file_class.new(args) Gem.use_paths Gem.configuration[:gemhome], Gem.configuration[:gempath] Gem::Command.extra_args = Gem.configuration[:gem] end end PK!rubygems/path_support.rbnu[# frozen_string_literal: true ## # # Gem::PathSupport facilitates the GEM_HOME and GEM_PATH environment settings # to the rest of RubyGems. # class Gem::PathSupport ## # The default system path for managing Gems. attr_reader :home ## # Array of paths to search for Gems. attr_reader :path ## # Directory with spec cache attr_reader :spec_cache_dir # :nodoc: ## # # Constructor. Takes a single argument which is to be treated like a # hashtable, or defaults to ENV, the system environment. # def initialize(env) @home = normalize_home_dir(env["GEM_HOME"] || Gem.default_dir) @path = split_gem_path env["GEM_PATH"], @home @spec_cache_dir = env["GEM_SPEC_CACHE"] || Gem.default_spec_cache_dir end private def normalize_home_dir(home) if File::ALT_SEPARATOR home = home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) end expand(home) end ## # Split the Gem search path (as reported by Gem.path). def split_gem_path(gpaths, home) # FIX: it should be [home, *path], not [*path, home] gem_path = [] if gpaths gem_path = gpaths.split(Gem.path_separator) # Handle the path_separator being set to a regexp, which will cause # end_with? to error if /#{Gem.path_separator}\z/.match?(gpaths) gem_path += default_path end if File::ALT_SEPARATOR gem_path.map! do |this_path| this_path.gsub File::ALT_SEPARATOR, File::SEPARATOR end end gem_path << home else gem_path = default_path end gem_path.map {|path| expand(path) }.uniq end # Return the default Gem path def default_path Gem.default_path + [@home] end def expand(path) if File.directory?(path) File.realpath(path) else path end end end PK!I5""rubygems/request.rbnu[# frozen_string_literal: true require_relative "vendored_net_http" require_relative "user_interaction" class Gem::Request extend Gem::UserInteraction include Gem::UserInteraction ### # Legacy. This is used in tests. def self.create_with_proxy(uri, request_class, last_modified, proxy) # :nodoc: cert_files = get_cert_files proxy ||= get_proxy_from_env(uri.scheme) pool = ConnectionPools.new proxy_uri(proxy), cert_files new(uri, request_class, last_modified, pool.pool_for(uri)) end def self.proxy_uri(proxy) # :nodoc: require_relative "vendor/uri/lib/uri" case proxy when :no_proxy then nil when Gem::URI::HTTP then proxy else Gem::URI.parse(proxy) end end def initialize(uri, request_class, last_modified, pool) @uri = uri @request_class = request_class @last_modified = last_modified @requests = Hash.new(0).compare_by_identity @user_agent = user_agent @connection_pool = pool end def proxy_uri @connection_pool.proxy_uri end def cert_files @connection_pool.cert_files end def self.get_cert_files pattern = File.expand_path("./ssl_certs/*/*.pem", __dir__) Dir.glob(pattern) end def self.configure_connection_for_https(connection, cert_files) raise Gem::Exception.new("OpenSSL is not available. Install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources") unless Gem::HAVE_OPENSSL connection.use_ssl = true connection.verify_mode = Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER store = OpenSSL::X509::Store.new if Gem.configuration.ssl_client_cert pem = File.read Gem.configuration.ssl_client_cert connection.cert = OpenSSL::X509::Certificate.new pem connection.key = OpenSSL::PKey::RSA.new pem end store.set_default_paths cert_files.each do |ssl_cert_file| store.add_file ssl_cert_file end if Gem.configuration.ssl_ca_cert if File.directory? Gem.configuration.ssl_ca_cert store.add_path Gem.configuration.ssl_ca_cert else store.add_file Gem.configuration.ssl_ca_cert end end connection.cert_store = store connection.verify_callback = proc do |preverify_ok, store_context| verify_certificate store_context unless preverify_ok preverify_ok end connection end def self.verify_certificate(store_context) depth = store_context.error_depth error = store_context.error_string number = store_context.error cert = store_context.current_cert ui.alert_error "SSL verification error at depth #{depth}: #{error} (#{number})" extra_message = verify_certificate_message number, cert ui.alert_error extra_message if extra_message end def self.verify_certificate_message(error_number, cert) return unless cert case error_number when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then require "time" "Certificate #{cert.subject} expired at #{cert.not_after.iso8601}" when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then require "time" "Certificate #{cert.subject} not valid until #{cert.not_before.iso8601}" when OpenSSL::X509::V_ERR_CERT_REJECTED then "Certificate #{cert.subject} is rejected" when OpenSSL::X509::V_ERR_CERT_UNTRUSTED then "Certificate #{cert.subject} is not trusted" when OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT then "Certificate #{cert.issuer} is not trusted" when OpenSSL::X509::V_ERR_INVALID_CA then "Certificate #{cert.subject} is an invalid CA certificate" when OpenSSL::X509::V_ERR_INVALID_PURPOSE then "Certificate #{cert.subject} has an invalid purpose" when OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN then "Root certificate is not trusted (#{cert.subject})" when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY then "You must add #{cert.issuer} to your local trusted store" when OpenSSL::X509::V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE then "Cannot verify certificate issued by #{cert.issuer}" end end ## # Creates or an HTTP connection based on +uri+, or retrieves an existing # connection, using a proxy if needed. def connection_for(uri) @connection_pool.checkout rescue Gem::HAVE_OPENSSL ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN, Errno::EHOSTDOWN => e raise Gem::RemoteFetcher::FetchError.new(e.message, uri) end def fetch request = @request_class.new @uri.request_uri unless @uri.nil? || @uri.user.nil? || @uri.user.empty? request.basic_auth Gem::UriFormatter.new(@uri.user).unescape, Gem::UriFormatter.new(@uri.password).unescape end request.add_field "User-Agent", @user_agent request.add_field "Connection", "keep-alive" request.add_field "Keep-Alive", "30" if @last_modified require "time" request.add_field "If-Modified-Since", @last_modified.httpdate end yield request if block_given? perform_request request end ## # Returns a proxy URI for the given +scheme+ if one is set in the # environment variables. def self.get_proxy_from_env(scheme = "http") downcase_scheme = scheme.downcase upcase_scheme = scheme.upcase env_proxy = ENV["#{downcase_scheme}_proxy"] || ENV["#{upcase_scheme}_PROXY"] no_env_proxy = env_proxy.nil? || env_proxy.empty? if no_env_proxy return ["https", "http"].include?(downcase_scheme) ? :no_proxy : get_proxy_from_env("http") end require "uri" uri = Gem::URI(Gem::UriFormatter.new(env_proxy).normalize) if uri && uri.user.nil? && uri.password.nil? user = ENV["#{downcase_scheme}_proxy_user"] || ENV["#{upcase_scheme}_PROXY_USER"] password = ENV["#{downcase_scheme}_proxy_pass"] || ENV["#{upcase_scheme}_PROXY_PASS"] uri.user = Gem::UriFormatter.new(user).escape uri.password = Gem::UriFormatter.new(password).escape end uri end def perform_request(request) # :nodoc: connection = connection_for @uri retried = false bad_response = false begin @requests[connection] += 1 verbose "#{request.method} #{Gem::Uri.redact(@uri)}" file_name = File.basename(@uri.path) # perform download progress reporter only for gems if request.response_body_permitted? && file_name =~ /\.gem$/ reporter = ui.download_reporter response = connection.request(request) do |incomplete_response| if Gem::Net::HTTPOK === incomplete_response reporter.fetch(file_name, incomplete_response.content_length) downloaded = 0 data = String.new incomplete_response.read_body do |segment| data << segment downloaded += segment.length reporter.update(downloaded) end reporter.done if incomplete_response.respond_to? :body= incomplete_response.body = data else incomplete_response.instance_variable_set(:@body, data) end end end else response = connection.request request end verbose "#{response.code} #{response.message}" rescue Gem::Net::HTTPBadResponse verbose "bad response" reset connection raise Gem::RemoteFetcher::FetchError.new("too many bad responses", @uri) if bad_response bad_response = true retry rescue Gem::Net::HTTPFatalError verbose "fatal error" raise Gem::RemoteFetcher::FetchError.new("fatal error", @uri) # HACK: work around EOFError bug in Gem::Net::HTTP # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible # to install gems. rescue EOFError, Gem::Timeout::Error, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE requests = @requests[connection] verbose "connection reset after #{requests} requests, retrying" raise Gem::RemoteFetcher::FetchError.new("too many connection resets", @uri) if retried reset connection retried = true retry end response ensure @connection_pool.checkin connection end ## # Resets HTTP connection +connection+. def reset(connection) @requests.delete connection connection.finish connection.start end def user_agent ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}".dup ruby_version = RUBY_VERSION ruby_version += "dev" if RUBY_PATCHLEVEL == -1 ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}" if RUBY_PATCHLEVEL >= 0 ua << " patchlevel #{RUBY_PATCHLEVEL}" else ua << " revision #{RUBY_REVISION}" end ua << ")" ua << " #{RUBY_ENGINE}" if RUBY_ENGINE != "ruby" ua end end require_relative "request/http_pool" require_relative "request/https_pool" require_relative "request/connection_pools" PK!">OQQrubygems/vendored_molinillo.rbnu[# frozen_string_literal: true require_relative "vendor/molinillo/lib/molinillo" PK!ђSÛ)rubygems/unknown_command_spell_checker.rbnu[# frozen_string_literal: true class Gem::UnknownCommandSpellChecker attr_reader :error def initialize(error) @error = error end def corrections @corrections ||= spell_checker.correct(error.unknown_command).map(&:inspect) end private def spell_checker dictionary = Gem::CommandManager.instance.command_names DidYouMean::SpellChecker.new(dictionary: dictionary) end end PK!^ 1QKQKrubygems/package.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments require_relative "security" require_relative "user_interaction" ## # Example using a Gem::Package # # Builds a .gem file given a Gem::Specification. A .gem file is a tarball # which contains a data.tar.gz, metadata.gz, checksums.yaml.gz and possibly # signatures. # # require 'rubygems' # require 'rubygems/package' # # spec = Gem::Specification.new do |s| # s.summary = "Ruby based make-like utility." # s.name = 'rake' # s.version = PKG_VERSION # s.requirements << 'none' # s.files = PKG_FILES # s.description = <<-EOF # Rake is a Make-like program implemented in Ruby. Tasks # and dependencies are specified in standard Ruby syntax. # EOF # end # # Gem::Package.build spec # # Reads a .gem file. # # require 'rubygems' # require 'rubygems/package' # # the_gem = Gem::Package.new(path_to_dot_gem) # the_gem.contents # get the files in the gem # the_gem.extract_files destination_directory # extract the gem into a directory # the_gem.spec # get the spec out of the gem # the_gem.verify # check the gem is OK (contains valid gem specification, contains a not corrupt contents archive) # # #files are the files in the .gem tar file, not the Ruby files in the gem # #extract_files and #contents automatically call #verify class Gem::Package include Gem::UserInteraction class Error < Gem::Exception; end class FormatError < Error attr_reader :path def initialize(message, source = nil) if source @path = source.is_a?(String) ? source : source.path message += " in #{path}" if path end super message end end class PathError < Error def initialize(destination, destination_dir) super format("installing into parent path %s of %s is not allowed", destination, destination_dir) end end class SymlinkError < Error def initialize(name, destination, destination_dir) super format("installing symlink '%s' pointing to parent path %s of %s is not allowed", name, destination, destination_dir) end end class NonSeekableIO < Error; end class TooLongFileName < Error; end ## # Raised when a tar file is corrupt class TarInvalidError < Error; end attr_accessor :build_time # :nodoc: ## # Checksums for the contents of the package attr_reader :checksums ## # The files in this package. This is not the contents of the gem, just the # files in the top-level container. attr_reader :files ## # Reference to the gem being packaged. attr_reader :gem ## # The security policy used for verifying the contents of this package. attr_accessor :security_policy ## # Sets the Gem::Specification to use to build this package. attr_writer :spec ## # Permission for directories attr_accessor :dir_mode ## # Permission for program files attr_accessor :prog_mode ## # Permission for other files attr_accessor :data_mode def self.build(spec, skip_validation = false, strict_validation = false, file_name = nil) gem_file = file_name || spec.file_name package = new gem_file package.spec = spec package.build skip_validation, strict_validation gem_file end ## # Creates a new Gem::Package for the file at +gem+. +gem+ can also be # provided as an IO object. # # If +gem+ is an existing file in the old format a Gem::Package::Old will be # returned. def self.new(gem, security_policy = nil) gem = if gem.is_a?(Gem::Package::Source) gem elsif gem.respond_to? :read Gem::Package::IOSource.new gem else Gem::Package::FileSource.new gem end return super unless self == Gem::Package return super unless gem.present? return super unless gem.start return super unless gem.start.include? "MD5SUM =" Gem::Package::Old.new gem end ## # Extracts the Gem::Specification and raw metadata from the .gem file at # +path+. #-- def self.raw_spec(path, security_policy = nil) format = new(path, security_policy) spec = format.spec metadata = nil File.open path, Gem.binary_mode do |io| tar = Gem::Package::TarReader.new io tar.each_entry do |entry| case entry.full_name when "metadata" then metadata = entry.read when "metadata.gz" then metadata = Gem::Util.gunzip entry.read end end end [spec, metadata] end ## # Creates a new package that will read or write to the file +gem+. def initialize(gem, security_policy) # :notnew: require "zlib" @gem = gem @build_time = Gem.source_date_epoch @checksums = {} @contents = nil @digests = Hash.new {|h, algorithm| h[algorithm] = {} } @files = nil @security_policy = security_policy @signatures = {} @signer = nil @spec = nil end ## # Copies this package to +path+ (if possible) def copy_to(path) FileUtils.cp @gem.path, path unless File.exist? path end ## # Adds a checksum for each entry in the gem to checksums.yaml.gz. def add_checksums(tar) Gem.load_yaml checksums_by_algorithm = Hash.new {|h, algorithm| h[algorithm] = {} } @checksums.each do |name, digests| digests.each do |algorithm, digest| checksums_by_algorithm[algorithm][name] = digest.hexdigest end end tar.add_file_signed "checksums.yaml.gz", 0o444, @signer do |io| gzip_to io do |gz_io| Psych.dump checksums_by_algorithm, gz_io end end end ## # Adds the files listed in the packages's Gem::Specification to data.tar.gz # and adds this file to the +tar+. def add_contents(tar) # :nodoc: digests = tar.add_file_signed "data.tar.gz", 0o444, @signer do |io| gzip_to io do |gz_io| Gem::Package::TarWriter.new gz_io do |data_tar| add_files data_tar end end end @checksums["data.tar.gz"] = digests end ## # Adds files included the package's Gem::Specification to the +tar+ file def add_files(tar) # :nodoc: @spec.files.each do |file| stat = File.lstat file if stat.symlink? tar.add_symlink file, File.readlink(file), stat.mode end next unless stat.file? tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| copy_stream(src_io, dst_io) end end end end ## # Adds the package's Gem::Specification to the +tar+ file def add_metadata(tar) # :nodoc: digests = tar.add_file_signed "metadata.gz", 0o444, @signer do |io| gzip_to io do |gz_io| gz_io.write @spec.to_yaml end end @checksums["metadata.gz"] = digests end ## # Builds this package based on the specification set by #spec= def build(skip_validation = false, strict_validation = false) raise ArgumentError, "skip_validation = true and strict_validation = true are incompatible" if skip_validation && strict_validation Gem.load_yaml @spec.validate true, strict_validation unless skip_validation setup_signer( signer_options: { expiration_length_days: Gem.configuration.cert_expiration_length_days, } ) @gem.with_write_io do |gem_io| Gem::Package::TarWriter.new gem_io do |gem| add_metadata gem add_contents gem add_checksums gem end end say <<-EOM Successfully built RubyGem Name: #{@spec.name} Version: #{@spec.version} File: #{File.basename @gem.path} EOM ensure @signer = nil end ## # A list of file names contained in this gem def contents return @contents if @contents verify unless @spec @contents = [] @gem.with_read_io do |io| gem_tar = Gem::Package::TarReader.new io gem_tar.each do |entry| next unless entry.full_name == "data.tar.gz" open_tar_gz entry do |pkg_tar| pkg_tar.each do |contents_entry| @contents << contents_entry.full_name end end return @contents end end rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end ## # Creates a digest of the TarEntry +entry+ from the digest algorithm set by # the security policy. def digest(entry) # :nodoc: algorithms = if @checksums @checksums.to_h {|algorithm, _| [algorithm, Gem::Security.create_digest(algorithm)] } elsif Gem::Security::DIGEST_NAME { Gem::Security::DIGEST_NAME => Gem::Security.create_digest(Gem::Security::DIGEST_NAME) } end return @digests if algorithms.nil? || algorithms.empty? buf = String.new(capacity: 16_384, encoding: Encoding::BINARY) until entry.eof? entry.readpartial(16_384, buf) algorithms.each_value {|digester| digester << buf } end entry.rewind algorithms.each do |algorithm, digester| @digests[algorithm][entry.full_name] = digester end @digests end ## # Extracts the files in this package into +destination_dir+ # # If +pattern+ is specified, only entries matching that glob will be # extracted. def extract_files(destination_dir, pattern = "*") verify unless @spec FileUtils.mkdir_p destination_dir, mode: dir_mode && 0o755 @gem.with_read_io do |io| reader = Gem::Package::TarReader.new io reader.each do |entry| next unless entry.full_name == "data.tar.gz" extract_tar_gz entry, destination_dir, pattern break # ignore further entries end end rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end ## # Extracts all the files in the gzipped tar archive +io+ into # +destination_dir+. # # If an entry in the archive contains a relative path above # +destination_dir+ or an absolute path is encountered an exception is # raised. # # If +pattern+ is specified, only entries matching that glob will be # extracted. def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: destination_dir = File.realpath(destination_dir) directories = [] symlinks = [] open_tar_gz io do |tar| tar.each do |entry| full_name = entry.full_name next unless File.fnmatch pattern, full_name, File::FNM_DOTMATCH destination = install_location full_name, destination_dir if entry.symlink? link_target = entry.header.linkname real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination)) raise Gem::Package::SymlinkError.new(full_name, real_destination, destination_dir) unless normalize_path(real_destination).start_with? normalize_path(destination_dir + "/") symlinks << [full_name, link_target, destination, real_destination] end FileUtils.rm_rf destination mkdir = if entry.directory? destination else File.dirname destination end unless directories.include?(mkdir) FileUtils.mkdir_p mkdir, mode: dir_mode ? 0o755 : (entry.header.mode if entry.directory?) directories << mkdir end if entry.file? File.open(destination, "wb") {|out| copy_stream(entry, out) } FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination end verbose destination end end symlinks.each do |name, target, destination, real_destination| if File.exist?(real_destination) File.symlink(target, destination) else alert_warning "#{@spec.full_name} ships with a dangling symlink named #{name} pointing to missing #{target} file. Ignoring" end end if dir_mode File.chmod(dir_mode, *directories) end end def file_mode(mode) # :nodoc: ((mode & 0o111).zero? ? data_mode : prog_mode) || # If we're not using one of the default modes, then we're going to fall # back to the mode from the tarball. In this case we need to mask it down # to fit into 2^16 bits (the maximum value for a mode in CRuby since it # gets put into an unsigned short). (mode & ((1 << 16) - 1)) end ## # Gzips content written to +gz_io+ to +io+. #-- # Also sets the gzip modification time to the package build time to ease # testing. def gzip_to(io) # :yields: gz_io gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION gz_io.mtime = @build_time yield gz_io ensure gz_io.close end ## # Returns the full path for installing +filename+. # # If +filename+ is not inside +destination_dir+ an exception is raised. def install_location(filename, destination_dir) # :nodoc: raise Gem::Package::PathError.new(filename, destination_dir) if filename.start_with? "/" destination_dir = File.realpath(destination_dir) destination = File.expand_path(filename, destination_dir) raise Gem::Package::PathError.new(destination, destination_dir) unless normalize_path(destination).start_with? normalize_path(destination_dir + "/") destination end def normalize_path(pathname) if Gem.win_platform? pathname.downcase else pathname end end ## # Loads a Gem::Specification from the TarEntry +entry+ def load_spec(entry) # :nodoc: limit = 10 * 1024 * 1024 case entry.full_name when "metadata" then @spec = Gem::Specification.from_yaml limit_read(entry, "metadata", limit) when "metadata.gz" then Zlib::GzipReader.wrap(entry, external_encoding: Encoding::UTF_8) do |gzio| @spec = Gem::Specification.from_yaml limit_read(gzio, "metadata.gz", limit) end end end ## # Opens +io+ as a gzipped tar archive def open_tar_gz(io) # :nodoc: Zlib::GzipReader.wrap io do |gzio| tar = Gem::Package::TarReader.new gzio yield tar end end ## # Reads and loads checksums.yaml.gz from the tar file +gem+ def read_checksums(gem) Gem.load_yaml @checksums = gem.seek "checksums.yaml.gz" do |entry| Zlib::GzipReader.wrap entry do |gz_io| Gem::SafeYAML.safe_load limit_read(gz_io, "checksums.yaml.gz", 10 * 1024 * 1024) end end end ## # Prepares the gem for signing and checksum generation. If a signing # certificate and key are not present only checksum generation is set up. def setup_signer(signer_options: {}) passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"] if @spec.signing_key @signer = Gem::Security::Signer.new( @spec.signing_key, @spec.cert_chain, passphrase, signer_options ) @spec.signing_key = nil @spec.cert_chain = @signer.cert_chain.map(&:to_s) else @signer = Gem::Security::Signer.new nil, nil, passphrase @spec.cert_chain = @signer.cert_chain.map(&:to_pem) if @signer.cert_chain end end ## # The spec for this gem. # # If this is a package for a built gem the spec is loaded from the # gem and returned. If this is a package for a gem being built the provided # spec is returned. def spec verify unless @spec @spec end ## # Verifies that this gem: # # * Contains a valid gem specification # * Contains a contents archive # * The contents archive is not corrupt # # After verification the gem specification from the gem is available from # #spec def verify @files = [] @spec = nil @gem.with_read_io do |io| Gem::Package::TarReader.new io do |reader| read_checksums reader verify_files reader end end verify_checksums @digests, @checksums @security_policy&.verify_signatures @spec, @digests, @signatures true rescue Gem::Security::Exception @spec = nil @files = [] raise rescue Errno::ENOENT => e raise Gem::Package::FormatError.new e.message rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end ## # Verifies the +checksums+ against the +digests+. This check is not # cryptographically secure. Missing checksums are ignored. def verify_checksums(digests, checksums) # :nodoc: return unless checksums checksums.sort.each do |algorithm, gem_digests| gem_digests.sort.each do |file_name, gem_hexdigest| computed_digest = digests[algorithm][file_name] unless computed_digest.hexdigest == gem_hexdigest raise Gem::Package::FormatError.new \ "#{algorithm} checksum mismatch for #{file_name}", @gem end end end end ## # Verifies +entry+ in a .gem file. def verify_entry(entry) file_name = entry.full_name @files << file_name case file_name when /\.sig$/ then @signatures[$`] = limit_read(entry, file_name, 1024 * 1024) if @security_policy return else digest entry end case file_name when "metadata", "metadata.gz" then load_spec entry when "data.tar.gz" then verify_gz entry end rescue StandardError warn "Exception while verifying #{@gem.path}" raise end ## # Verifies the files of the +gem+ def verify_files(gem) gem.each do |entry| verify_entry entry end unless @spec raise Gem::Package::FormatError.new "package metadata is missing", @gem end unless @files.include? "data.tar.gz" raise Gem::Package::FormatError.new \ "package content (data.tar.gz) is missing", @gem end if (duplicates = @files.group_by {|f| f }.select {|_k,v| v.size > 1 }.map(&:first)) && duplicates.any? raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(", ")})" end end ## # Verifies that +entry+ is a valid gzipped file. def verify_gz(entry) # :nodoc: Zlib::GzipReader.wrap entry do |gzio| # TODO: read into a buffer once zlib supports it gzio.read 16_384 until gzio.eof? # gzip checksum verification end rescue Zlib::GzipFile::Error => e raise Gem::Package::FormatError.new(e.message, entry.full_name) end if RUBY_ENGINE == "truffleruby" def copy_stream(src, dst) # :nodoc: dst.write src.read end else def copy_stream(src, dst) # :nodoc: IO.copy_stream(src, dst) end end def limit_read(io, name, limit) bytes = io.read(limit + 1) raise Gem::Package::FormatError, "#{name} is too big (over #{limit} bytes)" if bytes.size > limit bytes end end require_relative "package/digest_io" require_relative "package/source" require_relative "package/file_source" require_relative "package/io_source" require_relative "package/old" require_relative "package/tar_header" require_relative "package/tar_reader" require_relative "package/tar_reader/entry" require_relative "package/tar_writer" PK!]Y(rubygems/version_option.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" ## # Mixin methods for --version and --platform Gem::Command options. module Gem::VersionOption ## # Add the --platform option to the option parser. def add_platform_option(task = command, *wrap) Gem::OptionParser.accept Gem::Platform do |value| if value == Gem::Platform::RUBY value else Gem::Platform.new value end end add_option("--platform PLATFORM", Gem::Platform, "Specify the platform of gem to #{task}", *wrap) do |value, options| unless options[:added_platform] Gem.platforms = [Gem::Platform::RUBY] options[:added_platform] = true end Gem.platforms << value unless Gem.platforms.include? value end end ## # Add the --prerelease option to the option parser. def add_prerelease_option(*wrap) add_option("--[no-]prerelease", "Allow prerelease versions of a gem", *wrap) do |value, options| options[:prerelease] = value options[:explicit_prerelease] = true end end ## # Add the --version option to the option parser. def add_version_option(task = command, *wrap) Gem::OptionParser.accept Gem::Requirement do |value| Gem::Requirement.new(*value.split(/\s*,\s*/)) end add_option("-v", "--version VERSION", Gem::Requirement, "Specify version of gem to #{task}", *wrap) do |value, options| # Allow handling for multiple --version operators if options[:version] && !options[:version].none? options[:version].concat([value]) else options[:version] = value end explicit_prerelease_set = !options[:explicit_prerelease].nil? options[:explicit_prerelease] = false unless explicit_prerelease_set options[:prerelease] = value.prerelease? unless options[:explicit_prerelease] end end ## # Extract platform given on the command line def get_platform_from_requirements(requirements) Gem.platforms[1].to_s if requirements.key? :added_platform end end PK!U''rubygems/deprecate.rbnu[# frozen_string_literal: true ## # Provides 3 methods for declaring when something is going away. # # +deprecate(name, repl, year, month)+: # Indicate something may be removed on/after a certain date. # # +rubygems_deprecate(name, replacement=:none)+: # Indicate something will be removed in the next major RubyGems version, # and (optionally) a replacement for it. # # +rubygems_deprecate_command+: # Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be # removed in the next RubyGems version. # # Also provides +skip_during+ for temporarily turning off deprecation warnings. # This is intended to be used in the test suite, so deprecation warnings # don't cause test failures if you need to make sure stderr is otherwise empty. # # # Example usage of +deprecate+ and +rubygems_deprecate+: # # class Legacy # def self.some_class_method # # ... # end # # def some_instance_method # # ... # end # # def some_old_method # # ... # end # # extend Gem::Deprecate # deprecate :some_instance_method, "X.z", 2011, 4 # rubygems_deprecate :some_old_method, "Modern#some_new_method" # # class << self # extend Gem::Deprecate # deprecate :some_class_method, :none, 2011, 4 # end # end # # # Example usage of +rubygems_deprecate_command+: # # class Gem::Commands::QueryCommand < Gem::Command # extend Gem::Deprecate # rubygems_deprecate_command # # # ... # end # # # Example usage of +skip_during+: # # class TestSomething < Gem::Testcase # def test_some_thing_with_deprecations # Gem::Deprecate.skip_during do # actual_stdout, actual_stderr = capture_output do # Gem.something_deprecated # end # assert_empty actual_stdout # assert_equal(expected, actual_stderr) # end # end # end module Gem module Deprecate def self.skip # :nodoc: @skip ||= false end def self.skip=(v) # :nodoc: @skip = v end ## # Temporarily turn off warnings. Intended for tests only. def skip_during original = Gem::Deprecate.skip Gem::Deprecate.skip = true yield ensure Gem::Deprecate.skip = original end def self.next_rubygems_major_version # :nodoc: Gem::Version.new(Gem.rubygems_version.segments.first).bump end ## # Simple deprecation method that deprecates +name+ by wrapping it up # in a dummy method. It warns on each call to the dummy method # telling the user of +repl+ (unless +repl+ is :none) and the # year/month that it is planned to go away. def deprecate(name, repl, year, month) class_eval do old = "_deprecated_#{name}" alias_method old, name define_method name do |*args, &block| klass = is_a? Module target = klass ? "#{self}." : "#{self.class}#" msg = [ "NOTE: #{target}#{name} is deprecated", repl == :none ? " with no replacement" : "; use #{repl} instead", format(". It will be removed on or after %4d-%02d.", year, month), "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", ] warn "#{msg.join}." unless Gem::Deprecate.skip send old, *args, &block end ruby2_keywords name if respond_to?(:ruby2_keywords, true) end end ## # Simple deprecation method that deprecates +name+ by wrapping it up # in a dummy method. It warns on each call to the dummy method # telling the user of +repl+ (unless +repl+ is :none) and the # Rubygems version that it is planned to go away. def rubygems_deprecate(name, replacement=:none) class_eval do old = "_deprecated_#{name}" alias_method old, name define_method name do |*args, &block| klass = is_a? Module target = klass ? "#{self}." : "#{self.class}#" msg = [ "NOTE: #{target}#{name} is deprecated", replacement == :none ? " with no replacement" : "; use #{replacement} instead", ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}", "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", ] warn "#{msg.join}." unless Gem::Deprecate.skip send old, *args, &block end ruby2_keywords name if respond_to?(:ruby2_keywords, true) end end # Deprecation method to deprecate Rubygems commands def rubygems_deprecate_command(version = Gem::Deprecate.next_rubygems_major_version) class_eval do define_method "deprecated?" do true end define_method "deprecation_warning" do msg = [ "#{command} command is deprecated", ". It will be removed in Rubygems #{version}.\n", ] alert_warning msg.join.to_s unless Gem::Deprecate.skip end end end module_function :rubygems_deprecate, :rubygems_deprecate_command, :skip_during end end PK!ݯgrubygems/security/signer.rbnu[# frozen_string_literal: true ## # Basic OpenSSL-based package signing class. require_relative "../user_interaction" class Gem::Security::Signer include Gem::UserInteraction ## # The chain of certificates for signing including the signing certificate attr_accessor :cert_chain ## # The private key for the signing certificate attr_accessor :key ## # The digest algorithm used to create the signature attr_reader :digest_algorithm ## # The name of the digest algorithm, used to pull digests out of the hash by # name. attr_reader :digest_name # :nodoc: ## # Gem::Security::Signer options attr_reader :options DEFAULT_OPTIONS = { expiration_length_days: 365, }.freeze ## # Attempts to re-sign an expired cert with a given private key def self.re_sign_cert(expired_cert, expired_cert_path, private_key) return unless expired_cert.not_after < Time.now expiry = expired_cert.not_after.strftime("%Y%m%d%H%M%S") expired_cert_file = "#{File.basename(expired_cert_path)}.expired.#{expiry}" new_expired_cert_path = File.join(Gem.user_home, ".gem", expired_cert_file) Gem::Security.write(expired_cert, new_expired_cert_path) re_signed_cert = Gem::Security.re_sign( expired_cert, private_key, (Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days) ) Gem::Security.write(re_signed_cert, expired_cert_path) yield(expired_cert_path, new_expired_cert_path) if block_given? end ## # Creates a new signer with an RSA +key+ or path to a key, and a certificate # +chain+ containing X509 certificates, encoding certificates or paths to # certificates. def initialize(key, cert_chain, passphrase = nil, options = {}) @cert_chain = cert_chain @key = key @passphrase = passphrase @options = DEFAULT_OPTIONS.merge(options) unless @key default_key = File.join Gem.default_key_path @key = default_key if File.exist? default_key end unless @cert_chain default_cert = File.join Gem.default_cert_path @cert_chain = [default_cert] if File.exist? default_cert end @digest_name = Gem::Security::DIGEST_NAME @digest_algorithm = Gem::Security.create_digest(@digest_name) if @key && !@key.is_a?(OpenSSL::PKey::PKey) @key = OpenSSL::PKey.read(File.read(@key), @passphrase) end if @cert_chain @cert_chain = @cert_chain.compact.map do |cert| next cert if OpenSSL::X509::Certificate === cert cert = File.read cert if File.exist? cert OpenSSL::X509::Certificate.new cert end load_cert_chain end end ## # Extracts the full name of +cert+. If the certificate has a subjectAltName # this value is preferred, otherwise the subject is used. def extract_name(cert) # :nodoc: subject_alt_name = cert.extensions.find {|e| e.oid == "subjectAltName" } if subject_alt_name /\Aemail:/ =~ subject_alt_name.value # rubocop:disable Performance/StartWith $' || subject_alt_name.value else cert.subject end end ## # Loads any missing issuers in the cert chain from the trusted certificates. # # If the issuer does not exist it is ignored as it will be checked later. def load_cert_chain # :nodoc: return if @cert_chain.empty? while @cert_chain.first.issuer.to_s != @cert_chain.first.subject.to_s do issuer = Gem::Security.trust_dir.issuer_of @cert_chain.first break unless issuer # cert chain is verified later @cert_chain.unshift issuer end end ## # Sign data with given digest algorithm def sign(data) return unless @key raise Gem::Security::Exception, "no certs provided" if @cert_chain.empty? if @cert_chain.length == 1 && @cert_chain.last.not_after < Time.now alert("Your certificate has expired, trying to re-sign it...") re_sign_key( expiration_length: (Gem::Security::ONE_DAY * options[:expiration_length_days]) ) end full_name = extract_name @cert_chain.last Gem::Security::SigningPolicy.verify @cert_chain, @key, {}, {}, full_name @key.sign @digest_algorithm.new, data end ## # Attempts to re-sign the private key if the signing certificate is expired. # # The key will be re-signed if: # * The expired certificate is self-signed # * The expired certificate is saved at ~/.gem/gem-public_cert.pem # and the private key is saved at ~/.gem/gem-private_key.pem # * There is no file matching the expiry date at # ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S # # If the signing certificate can be re-signed the expired certificate will # be saved as ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S where the # expiry time (not after) is used for the timestamp. def re_sign_key(expiration_length: Gem::Security::ONE_YEAR) # :nodoc: old_cert = @cert_chain.last disk_cert_path = File.join(Gem.default_cert_path) disk_cert = begin File.read(disk_cert_path) rescue StandardError nil end disk_key_path = File.join(Gem.default_key_path) disk_key = begin OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue StandardError nil end return unless disk_key if disk_key.to_pem == @key.to_pem && disk_cert == old_cert.to_pem expiry = old_cert.not_after.strftime("%Y%m%d%H%M%S") old_cert_file = "gem-public_cert.pem.expired.#{expiry}" old_cert_path = File.join(Gem.user_home, ".gem", old_cert_file) unless File.exist?(old_cert_path) Gem::Security.write(old_cert, old_cert_path) cert = Gem::Security.re_sign(old_cert, @key, expiration_length) Gem::Security.write(cert, disk_cert_path) alert("Your cert: #{disk_cert_path} has been auto re-signed with the key: #{disk_key_path}") alert("Your expired cert will be located at: #{old_cert_path}") @cert_chain = [cert] end end end end PK!@D rubygems/security/trust_dir.rbnu[# frozen_string_literal: true ## # The TrustDir manages the trusted certificates for gem signature # verification. class Gem::Security::TrustDir ## # Default permissions for the trust directory and its contents DEFAULT_PERMISSIONS = { trust_dir: 0o700, trusted_cert: 0o600, }.freeze ## # The directory where trusted certificates will be stored. attr_reader :dir ## # Creates a new TrustDir using +dir+ where the directory and file # permissions will be checked according to +permissions+ def initialize(dir, permissions = DEFAULT_PERMISSIONS) @dir = dir @permissions = permissions @digester = Gem::Security.create_digest end ## # Returns the path to the trusted +certificate+ def cert_path(certificate) name_path certificate.subject end ## # Enumerates trusted certificates. def each_certificate return enum_for __method__ unless block_given? glob = File.join @dir, "*.pem" Dir[glob].each do |certificate_file| certificate = load_certificate certificate_file yield certificate, certificate_file rescue OpenSSL::X509::CertificateError next # HACK: warn end end ## # Returns the issuer certificate of the given +certificate+ if it exists in # the trust directory. def issuer_of(certificate) path = name_path certificate.issuer return unless File.exist? path load_certificate path end ## # Returns the path to the trusted certificate with the given ASN.1 +name+ def name_path(name) digest = @digester.hexdigest name.to_s File.join @dir, "cert-#{digest}.pem" end ## # Loads the given +certificate_file+ def load_certificate(certificate_file) pem = File.read certificate_file OpenSSL::X509::Certificate.new pem end ## # Add a certificate to trusted certificate list. def trust_cert(certificate) verify destination = cert_path certificate File.open destination, "wb", 0o600 do |io| io.write certificate.to_pem io.chmod(@permissions[:trusted_cert]) end end ## # Make sure the trust directory exists. If it does exist, make sure it's # actually a directory. If not, then create it with the appropriate # permissions. def verify require "fileutils" if File.exist? @dir raise Gem::Security::Exception, "trust directory #{@dir} is not a directory" unless File.directory? @dir FileUtils.chmod 0o700, @dir else FileUtils.mkdir_p @dir, mode: @permissions[:trust_dir] end end end PK!T11rubygems/security/policy.rbnu[# frozen_string_literal: true require_relative "../user_interaction" ## # A Gem::Security::Policy object encapsulates the settings for verifying # signed gem files. This is the base class. You can either declare an # instance of this or use one of the preset security policies in # Gem::Security::Policies. class Gem::Security::Policy include Gem::UserInteraction attr_reader :name attr_accessor :only_signed attr_accessor :only_trusted attr_accessor :verify_chain attr_accessor :verify_data attr_accessor :verify_root attr_accessor :verify_signer ## # Create a new Gem::Security::Policy object with the given mode and # options. def initialize(name, policy = {}, opt = {}) @name = name @opt = opt # Default to security @only_signed = true @only_trusted = true @verify_chain = true @verify_data = true @verify_root = true @verify_signer = true policy.each_pair do |key, val| case key when :verify_data then @verify_data = val when :verify_signer then @verify_signer = val when :verify_chain then @verify_chain = val when :verify_root then @verify_root = val when :only_trusted then @only_trusted = val when :only_signed then @only_signed = val end end end ## # Verifies each certificate in +chain+ has signed the following certificate # and is valid for the given +time+. def check_chain(chain, time) raise Gem::Security::Exception, "missing signing chain" unless chain raise Gem::Security::Exception, "empty signing chain" if chain.empty? begin chain.each_cons 2 do |issuer, cert| check_cert cert, issuer, time end true rescue Gem::Security::Exception => e raise Gem::Security::Exception, "invalid signing chain: #{e.message}" end end ## # Verifies that +data+ matches the +signature+ created by +public_key+ and # the +digest+ algorithm. def check_data(public_key, digest, signature, data) raise Gem::Security::Exception, "invalid signature" unless public_key.verify digest, signature, data.digest true end ## # Ensures that +signer+ is valid for +time+ and was signed by the +issuer+. # If the +issuer+ is +nil+ no verification is performed. def check_cert(signer, issuer, time) raise Gem::Security::Exception, "missing signing certificate" unless signer message = "certificate #{signer.subject}" if (not_before = signer.not_before) && not_before > time raise Gem::Security::Exception, "#{message} not valid before #{not_before}" end if (not_after = signer.not_after) && not_after < time raise Gem::Security::Exception, "#{message} not valid after #{not_after}" end if issuer && !signer.verify(issuer.public_key) raise Gem::Security::Exception, "#{message} was not issued by #{issuer.subject}" end true end ## # Ensures the public key of +key+ matches the public key in +signer+ def check_key(signer, key) unless signer && key return true unless @only_signed raise Gem::Security::Exception, "missing key or signature" end raise Gem::Security::Exception, "certificate #{signer.subject} does not match the signing key" unless signer.check_private_key(key) true end ## # Ensures the root certificate in +chain+ is self-signed and valid for # +time+. def check_root(chain, time) raise Gem::Security::Exception, "missing signing chain" unless chain root = chain.first raise Gem::Security::Exception, "missing root certificate" unless root raise Gem::Security::Exception, "root certificate #{root.subject} is not self-signed " \ "(issuer #{root.issuer})" if root.issuer != root.subject check_cert root, root, time end ## # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and # the digests of the two certificates match according to +digester+ def check_trust(chain, digester, trust_dir) raise Gem::Security::Exception, "missing signing chain" unless chain root = chain.first raise Gem::Security::Exception, "missing root certificate" unless root path = Gem::Security.trust_dir.cert_path root unless File.exist? path message = "root cert #{root.subject} is not trusted".dup message << " (root of signing cert #{chain.last.subject})" if chain.length > 1 raise Gem::Security::Exception, message end save_cert = OpenSSL::X509::Certificate.new File.read path save_dgst = digester.digest save_cert.public_key.to_pem pkey_str = root.public_key.to_pem cert_dgst = digester.digest pkey_str raise Gem::Security::Exception, "trusted root certificate #{root.subject} checksum " \ "does not match signing root certificate checksum" unless save_dgst == cert_dgst true end ## # Extracts the email or subject from +certificate+ def subject(certificate) # :nodoc: certificate.extensions.each do |extension| next unless extension.oid == "subjectAltName" return extension.value end certificate.subject.to_s end def inspect # :nodoc: format("[Policy: %s - data: %p signer: %p chain: %p root: %p " \ "signed-only: %p trusted-only: %p]", @name, @verify_chain, @verify_data, @verify_root, @verify_signer, @only_signed, @only_trusted) end ## # For +full_name+, verifies the certificate +chain+ is valid, the +digests+ # match the signatures +signatures+ created by the signer depending on the # +policy+ settings. # # If +key+ is given it is used to validate the signing certificate. def verify(chain, key = nil, digests = {}, signatures = {}, full_name = "(unknown)") if signatures.empty? if @only_signed raise Gem::Security::Exception, "unsigned gems are not allowed by the #{name} policy" elsif digests.empty? # lack of signatures is irrelevant if there is nothing to check # against else alert_warning "#{full_name} is not signed" return end end opt = @opt digester = Gem::Security.create_digest trust_dir = opt[:trust_dir] time = Time.now _, signer_digests = digests.find do |_algorithm, file_digests| file_digests.values.first.name == Gem::Security::DIGEST_NAME end if @verify_data raise Gem::Security::Exception, "no digests provided (probable bug)" if signer_digests.nil? || signer_digests.empty? else signer_digests = {} end signer = chain.last check_key signer, key if key check_cert signer, nil, time if @verify_signer check_chain chain, time if @verify_chain check_root chain, time if @verify_root if @only_trusted check_trust chain, digester, trust_dir elsif signatures.empty? && digests.empty? # trust is irrelevant if there's no signatures to verify else alert_warning "#{subject signer} is not trusted for #{full_name}" end signatures.each do |file, _| digest = signer_digests[file] raise Gem::Security::Exception, "missing digest for #{file}" unless digest end signer_digests.each do |file, digest| signature = signatures[file] raise Gem::Security::Exception, "missing signature for #{file}" unless signature check_data signer.public_key, digester, signature, digest if @verify_data end true end ## # Extracts the certificate chain from the +spec+ and calls #verify to ensure # the signatures and certificate chain is valid according to the policy.. def verify_signatures(spec, digests, signatures) chain = spec.cert_chain.map do |cert_pem| OpenSSL::X509::Certificate.new cert_pem end verify chain, nil, digests, signatures, spec.full_name true end alias_method :to_s, :name # :nodoc: end PK!ҽ? ? rubygems/security/policies.rbnu[# frozen_string_literal: true module Gem::Security ## # No security policy: all package signature checks are disabled. NoSecurity = Policy.new( "No Security", verify_data: false, verify_signer: false, verify_chain: false, verify_root: false, only_trusted: false, only_signed: false ) ## # AlmostNo security policy: only verify that the signing certificate is the # one that actually signed the data. Make no attempt to verify the signing # certificate chain. # # This policy is basically useless. better than nothing, but can still be # easily spoofed, and is not recommended. AlmostNoSecurity = Policy.new( "Almost No Security", verify_data: true, verify_signer: false, verify_chain: false, verify_root: false, only_trusted: false, only_signed: false ) ## # Low security policy: only verify that the signing certificate is actually # the gem signer, and that the signing certificate is valid. # # This policy is better than nothing, but can still be easily spoofed, and # is not recommended. LowSecurity = Policy.new( "Low Security", verify_data: true, verify_signer: true, verify_chain: false, verify_root: false, only_trusted: false, only_signed: false ) ## # Medium security policy: verify the signing certificate, verify the signing # certificate chain all the way to the root certificate, and only trust root # certificates that we have explicitly allowed trust for. # # This security policy is reasonable, but it allows unsigned packages, so a # malicious person could simply delete the package signature and pass the # gem off as unsigned. MediumSecurity = Policy.new( "Medium Security", verify_data: true, verify_signer: true, verify_chain: true, verify_root: true, only_trusted: true, only_signed: false ) ## # High security policy: only allow signed gems to be installed, verify the # signing certificate, verify the signing certificate chain all the way to # the root certificate, and only trust root certificates that we have # explicitly allowed trust for. # # This security policy is significantly more difficult to bypass, and offers # a reasonable guarantee that the contents of the gem have not been altered. HighSecurity = Policy.new( "High Security", verify_data: true, verify_signer: true, verify_chain: true, verify_root: true, only_trusted: true, only_signed: true ) ## # Policy used to verify a certificate and key when signing a gem SigningPolicy = Policy.new( "Signing Policy", verify_data: false, verify_signer: true, verify_chain: true, verify_root: true, only_trusted: false, only_signed: false ) ## # Hash of configured security policies Policies = { "NoSecurity" => NoSecurity, "AlmostNoSecurity" => AlmostNoSecurity, "LowSecurity" => LowSecurity, "MediumSecurity" => MediumSecurity, "HighSecurity" => HighSecurity, # SigningPolicy is not intended for use by `gem -P` so do not list it }.freeze end PK!֖i=rubygems/validator.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "package" require_relative "installer" ## # Validator performs various gem file and gem database validation class Gem::Validator include Gem::UserInteraction def initialize # :nodoc: require "find" end private def find_files_for_gem(gem_directory) installed_files = [] Find.find gem_directory do |file_name| fn = file_name[gem_directory.size..file_name.size - 1].sub(%r{^/}, "") installed_files << fn unless fn.empty? || fn.include?("CVS") || File.directory?(file_name) end installed_files end public ## # Describes a problem with a file in a gem. ErrorData = Struct.new :path, :problem do def <=>(other) # :nodoc: return nil unless self.class === other [path, problem] <=> [other.path, other.problem] end end ## # Checks the gem directory for the following potential # inconsistencies/problems: # # * Checksum gem itself # * For each file in each gem, check consistency of installed versions # * Check for files that aren't part of the gem but are in the gems directory # * 1 cache - 1 spec - 1 directory. # # returns a hash of ErrorData objects, keyed on the problem gem's name. #-- # TODO needs further cleanup def alien(gems=[]) errors = Hash.new {|h,k| h[k] = {} } Gem::Specification.each do |spec| unless gems.empty? next unless gems.include? spec.name end next if spec.default_gem? gem_name = spec.file_name gem_path = spec.cache_file spec_path = spec.spec_file gem_directory = spec.full_gem_path unless File.directory? gem_directory errors[gem_name][spec.full_name] = "Gem registered but doesn't exist at #{gem_directory}" next end unless File.exist? spec_path errors[gem_name][spec_path] = "Spec file missing for installed gem" end begin unless File.readable?(gem_path) raise Gem::VerificationError, "missing gem file #{gem_path}" end good, gone, unreadable = nil, nil, nil, nil File.open gem_path, Gem.binary_mode do |_file| package = Gem::Package.new gem_path good, gone = package.contents.partition do |file_name| File.exist? File.join(gem_directory, file_name) end gone.sort.each do |path| errors[gem_name][path] = "Missing file" end good, unreadable = good.partition do |file_name| File.readable? File.join(gem_directory, file_name) end unreadable.sort.each do |path| errors[gem_name][path] = "Unreadable file" end good.each do |entry, data| next unless data # HACK: `gem check -a mkrf` source = File.join gem_directory, entry["path"] File.open source, Gem.binary_mode do |f| unless f.read == data errors[gem_name][entry["path"]] = "Modified from original" end end end end installed_files = find_files_for_gem(gem_directory) extras = installed_files - good - unreadable extras.each do |extra| errors[gem_name][extra] = "Extra file" end rescue Gem::VerificationError => e errors[gem_name][gem_path] = e.message end end errors.each do |name, subhash| errors[name] = subhash.map do |path, msg| ErrorData.new path, msg end.sort end errors end end PK!ǀrubygems/ext.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ ## # Classes for building C extensions live here. module Gem::Ext; end require_relative "ext/build_error" require_relative "ext/builder" require_relative "ext/configure_builder" require_relative "ext/ext_conf_builder" require_relative "ext/rake_builder" require_relative "ext/cmake_builder" require_relative "ext/cargo_builder" PK!|+44rubygems/dependency_list.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "vendored_tsort" require_relative "deprecate" ## # Gem::DependencyList is used for installing and uninstalling gems in the # correct order to avoid conflicts. #-- # TODO: It appears that all but topo-sort functionality is being duplicated # (or is planned to be duplicated) elsewhere in rubygems. Is the majority of # this class necessary anymore? Especially #ok?, #why_not_ok? class Gem::DependencyList attr_reader :specs include Enumerable include Gem::TSort ## # Allows enabling/disabling use of development dependencies attr_accessor :development ## # Creates a DependencyList from the current specs. def self.from_specs list = new list.add(*Gem::Specification.to_a) list end ## # Creates a new DependencyList. If +development+ is true, development # dependencies will be included. def initialize(development = false) @specs = [] @development = development end ## # Adds +gemspecs+ to the dependency list. def add(*gemspecs) @specs.concat gemspecs end def clear @specs.clear end ## # Return a list of the gem specifications in the dependency list, sorted in # order so that no gemspec in the list depends on a gemspec earlier in the # list. # # This is useful when removing gems from a set of installed gems. By # removing them in the returned order, you don't get into as many dependency # issues. # # If there are circular dependencies (yuck!), then gems will be returned in # order until only the circular dependents and anything they reference are # left. Then arbitrary gemspecs will be returned until the circular # dependency is broken, after which gems will be returned in dependency # order again. def dependency_order sorted = strongly_connected_components.flatten result = [] seen = {} sorted.each do |spec| if index = seen[spec.name] if result[index].version < spec.version result[index] = spec end else seen[spec.name] = result.length result << spec end end result.reverse end ## # Iterator over dependency_order def each(&block) dependency_order.each(&block) end def find_name(full_name) @specs.find {|spec| spec.full_name == full_name } end def inspect # :nodoc: format("%s %p>", super[0..-2], map(&:full_name)) end ## # Are all the dependencies in the list satisfied? def ok? why_not_ok?(:quick).empty? end def why_not_ok?(quick = false) unsatisfied = Hash.new {|h,k| h[k] = [] } each do |spec| spec.runtime_dependencies.each do |dep| inst = Gem::Specification.any? do |installed_spec| dep.name == installed_spec.name && dep.requirement.satisfied_by?(installed_spec.version) end unless inst || @specs.find {|s| s.satisfies_requirement? dep } unsatisfied[spec.name] << dep return unsatisfied if quick end end end unsatisfied end ## # It is ok to remove a gemspec from the dependency list? # # If removing the gemspec creates breaks a currently ok dependency, then it # is NOT ok to remove the gemspec. def ok_to_remove?(full_name, check_dev=true) gem_to_remove = find_name full_name # If the state is inconsistent, at least don't crash return true unless gem_to_remove siblings = @specs.find_all do |s| s.name == gem_to_remove.name && s.full_name != gem_to_remove.full_name end deps = [] @specs.each do |spec| check = check_dev ? spec.dependencies : spec.runtime_dependencies check.each do |dep| deps << dep if gem_to_remove.satisfies_requirement?(dep) end end deps.all? do |dep| siblings.any? do |s| s.satisfies_requirement? dep end end end ## # Remove everything in the DependencyList that matches but doesn't # satisfy items in +dependencies+ (a hash of gem names to arrays of # dependencies). def remove_specs_unsatisfied_by(dependencies) specs.reject! do |spec| dep = dependencies[spec.name] dep && !dep.requirement.satisfied_by?(spec.version) end end ## # Removes the gemspec matching +full_name+ from the dependency list def remove_by_name(full_name) @specs.delete_if {|spec| spec.full_name == full_name } end ## # Return a hash of predecessors. result[spec] is an Array of # gemspecs that have a dependency satisfied by the named gemspec. def spec_predecessors result = Hash.new {|h,k| h[k] = [] } specs = @specs.sort.reverse specs.each do |spec| specs.each do |other| next if spec == other other.dependencies.each do |dep| if spec.satisfies_requirement? dep result[spec] << other end end end end result end def tsort_each_node(&block) @specs.each(&block) end def tsort_each_child(node) specs = @specs.sort.reverse dependencies = node.runtime_dependencies dependencies.push(*node.development_dependencies) if @development dependencies.each do |dep| specs.each do |spec| if spec.satisfies_requirement? dep yield spec break end end end end private ## # Count the number of gemspecs in the list +specs+ that are not in # +ignored+. def active_count(specs, ignored) specs.count {|spec| ignored[spec.full_name].nil? } end end PK!Brubygems/spec_fetcher.rbnu[# frozen_string_literal: true require_relative "remote_fetcher" require_relative "user_interaction" require_relative "errors" require_relative "text" require_relative "name_tuple" ## # SpecFetcher handles metadata updates from remote gem repositories. class Gem::SpecFetcher include Gem::UserInteraction include Gem::Text ## # Cache of latest specs attr_reader :latest_specs # :nodoc: ## # Sources for this SpecFetcher attr_reader :sources # :nodoc: ## # Cache of all released specs attr_reader :specs # :nodoc: ## # Cache of prerelease specs attr_reader :prerelease_specs # :nodoc: @fetcher = nil ## # Default fetcher instance. Use this instead of ::new to reduce object # allocation. def self.fetcher @fetcher ||= new end def self.fetcher=(fetcher) # :nodoc: @fetcher = fetcher end ## # Creates a new SpecFetcher. Ordinarily you want to use the default fetcher # from Gem::SpecFetcher::fetcher which uses the Gem.sources. # # If you need to retrieve specifications from a different +source+, you can # send it as an argument. def initialize(sources = nil) @sources = sources || Gem.sources @update_cache = begin File.stat(Gem.user_home).uid == Process.uid rescue Errno::EACCES, Errno::ENOENT false end @specs = {} @latest_specs = {} @prerelease_specs = {} @caches = { latest: @latest_specs, prerelease: @prerelease_specs, released: @specs, } @fetcher = Gem::RemoteFetcher.fetcher end ## # # Find and fetch gem name tuples that match +dependency+. # # If +matching_platform+ is false, gems for all platforms are returned. def search_for_dependency(dependency, matching_platform=true) found = {} rejected_specs = {} list, errors = available_specs(dependency.identity) list.each do |source, specs| if dependency.name.is_a?(String) && specs.respond_to?(:bsearch) start_index = (0...specs.length).bsearch {|i| specs[i].name >= dependency.name } end_index = (0...specs.length).bsearch {|i| specs[i].name > dependency.name } specs = specs[start_index...end_index] if start_index && end_index end found[source] = specs.select do |tup| if dependency.match?(tup) if matching_platform && !Gem::Platform.match_gem?(tup.platform, tup.name) pm = ( rejected_specs[dependency] ||= \ Gem::PlatformMismatch.new(tup.name, tup.version)) pm.add_platform tup.platform false else true end end end end errors += rejected_specs.values tuples = [] found.each do |source, specs| specs.each do |s| tuples << [s, source] end end tuples = tuples.sort_by {|x| x[0].version } [tuples, errors] end ## # Return all gem name tuples who's names match +obj+ def detect(type=:complete) tuples = [] list, _ = available_specs(type) list.each do |source, specs| specs.each do |tup| if yield(tup) tuples << [tup, source] end end end tuples end ## # Find and fetch specs that match +dependency+. # # If +matching_platform+ is false, gems for all platforms are returned. def spec_for_dependency(dependency, matching_platform=true) tuples, errors = search_for_dependency(dependency, matching_platform) specs = [] tuples.each do |tup, source| spec = source.fetch_spec(tup) rescue Gem::RemoteFetcher::FetchError => e errors << Gem::SourceFetchProblem.new(source, e) else specs << [spec, source] end [specs, errors] end ## # Suggests gems based on the supplied +gem_name+. Returns an array of # alternative gem names. def suggest_gems_from_name(gem_name, type = :latest, num_results = 5) gem_name = gem_name.downcase.tr("_-", "") max = gem_name.size / 2 names = available_specs(type).first.values.flatten(1) matches = names.map do |n| next unless n.match_platform? distance = levenshtein_distance gem_name, n.name.downcase.tr("_-", "") next if distance >= max return [n.name] if distance == 0 [n.name, distance] end.compact matches = if matches.empty? && type != :prerelease suggest_gems_from_name gem_name, :prerelease else matches.uniq.sort_by {|_name, dist| dist } end matches.map {|name, _dist| name }.uniq.first(num_results) end ## # Returns a list of gems available for each source in Gem::sources. # # +type+ can be one of 3 values: # :released => Return the list of all released specs # :complete => Return the list of all specs # :latest => Return the list of only the highest version of each gem # :prerelease => Return the list of all prerelease only specs # def available_specs(type) errors = [] list = {} @sources.each_source do |source| names = case type when :latest tuples_for source, :latest when :released tuples_for source, :released when :complete names = tuples_for(source, :prerelease, true) + tuples_for(source, :released) names.sort when :abs_latest names = tuples_for(source, :prerelease, true) + tuples_for(source, :latest) names.sort when :prerelease tuples_for(source, :prerelease) else raise Gem::Exception, "Unknown type - :#{type}" end rescue Gem::RemoteFetcher::FetchError => e errors << Gem::SourceFetchProblem.new(source, e) else list[source] = names end [list, errors] end ## # Retrieves NameTuples from +source+ of the given +type+ (:prerelease, # etc.). If +gracefully_ignore+ is true, errors are ignored. def tuples_for(source, type, gracefully_ignore=false) # :nodoc: @caches[type][source.uri] ||= source.load_specs(type).sort_by(&:name) rescue Gem::RemoteFetcher::FetchError raise unless gracefully_ignore [] end end PK!-hrubygems/ci_detector.rbnu[# frozen_string_literal: true module Gem module CIDetector # NOTE: Any changes made here will need to be made to both lib/rubygems/ci_detector.rb and # bundler/lib/bundler/ci_detector.rb (which are enforced duplicates). # TODO: Drop that duplication once bundler drops support for RubyGems 3.4 # # ## Recognized CI providers, their signifiers, and the relevant docs ## # # Travis CI - CI, TRAVIS https://docs.travis-ci.com/user/environment-variables/#default-environment-variables # Cirrus CI - CI, CIRRUS_CI https://cirrus-ci.org/guide/writing-tasks/#environment-variables # Circle CI - CI, CIRCLECI https://circleci.com/docs/variables/#built-in-environment-variables # Gitlab CI - CI, GITLAB_CI https://docs.gitlab.com/ee/ci/variables/ # AppVeyor - CI, APPVEYOR https://www.appveyor.com/docs/environment-variables/ # CodeShip - CI_NAME https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables#_default_environment_variables # dsari - CI, DSARI https://github.com/rfinnie/dsari#running # Jenkins - BUILD_NUMBER https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables # TeamCity - TEAMCITY_VERSION https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters # Appflow - CI_BUILD_ID https://ionic.io/docs/appflow/automation/environments#predefined-environments # TaskCluster - TASKCLUSTER_ROOT_URL https://docs.taskcluster.net/docs/manual/design/env-vars # Semaphore - CI, SEMAPHORE https://docs.semaphoreci.com/ci-cd-environment/environment-variables/ # BuildKite - CI, BUILDKITE https://buildkite.com/docs/pipelines/environment-variables # GoCD - GO_SERVER_URL https://docs.gocd.org/current/faq/dev_use_current_revision_in_build.html # GH Actions - CI, GITHUB_ACTIONS https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables # # ### Some "standard" ENVs that multiple providers may set ### # # * CI - this is set by _most_ (but not all) CI providers now; it's approaching a standard. # * CI_NAME - Not as frequently used, but some providers set this to specify their own name # Any of these being set is a reasonably reliable indicator that we are # executing in a CI environment. ENV_INDICATORS = [ "CI", "CI_NAME", "CONTINUOUS_INTEGRATION", "BUILD_NUMBER", "CI_APP_ID", "CI_BUILD_ID", "CI_BUILD_NUMBER", "RUN_ID", "TASKCLUSTER_ROOT_URL", ].freeze # For each CI, this env suffices to indicate that we're on _that_ CI's # containers. (A few of them only supply a CI_NAME variable, which is also # nice). And if they set "CI" but we can't tell which one they are, we also # want to know that - a bare "ci" without another token tells us as much. ENV_DESCRIPTORS = { "TRAVIS" => "travis", "CIRCLECI" => "circle", "CIRRUS_CI" => "cirrus", "DSARI" => "dsari", "SEMAPHORE" => "semaphore", "JENKINS_URL" => "jenkins", "BUILDKITE" => "buildkite", "GO_SERVER_URL" => "go", "GITLAB_CI" => "gitlab", "GITHUB_ACTIONS" => "github", "TASKCLUSTER_ROOT_URL" => "taskcluster", "CI" => "ci", }.freeze def self.ci? ENV_INDICATORS.any? {|var| ENV.include?(var) } end def self.ci_strings matching_names = ENV_DESCRIPTORS.select {|env, _| ENV[env] }.values matching_names << ENV["CI_NAME"].downcase if ENV["CI_NAME"] matching_names.reject(&:empty?).sort.uniq end end end PK!rubygems/resolver/lock_set.rbnu[# frozen_string_literal: true ## # A set of gems from a gem dependencies lockfile. class Gem::Resolver::LockSet < Gem::Resolver::Set attr_reader :specs # :nodoc: ## # Creates a new LockSet from the given +sources+ def initialize(sources) super() @sources = sources.map do |source| Gem::Source::Lock.new source end @specs = [] end ## # Creates a new IndexSpecification in this set using the given +name+, # +version+ and +platform+. # # The specification's set will be the current set, and the source will be # the current set's source. def add(name, version, platform) # :nodoc: version = Gem::Version.new version specs = [ Gem::Resolver::LockSpecification.new(self, name, version, @sources, platform), ] @specs.concat specs specs end ## # Returns an Array of IndexSpecification objects matching the # DependencyRequest +req+. def find_all(req) @specs.select do |spec| req.match? spec end end ## # Loads a Gem::Specification with the given +name+, +version+ and # +platform+. +source+ is ignored. def load_spec(name, version, platform, source) # :nodoc: dep = Gem::Dependency.new name, version found = @specs.find do |spec| dep.matches_spec?(spec) && spec.platform == platform end tuple = Gem::NameTuple.new found.name, found.version, found.platform found.source.fetch_spec tuple end def pretty_print(q) # :nodoc: q.group 2, "[LockSet", "]" do q.breakable q.text "source:" q.breakable q.pp @source q.breakable q.text "specs:" q.breakable q.pp @specs.map(&:full_name) end end end PK!h  'rubygems/resolver/dependency_request.rbnu[# frozen_string_literal: true ## # Used Internally. Wraps a Dependency object to also track which spec # contained the Dependency. class Gem::Resolver::DependencyRequest ## # The wrapped Gem::Dependency attr_reader :dependency ## # The request for this dependency. attr_reader :requester ## # Creates a new DependencyRequest for +dependency+ from +requester+. # +requester may be nil if the request came from a user. def initialize(dependency, requester) @dependency = dependency @requester = requester end def ==(other) # :nodoc: case other when Gem::Dependency @dependency == other when Gem::Resolver::DependencyRequest @dependency == other.dependency else false end end ## # Is this dependency a development dependency? def development? @dependency.type == :development end ## # Does this dependency request match +spec+? # # NOTE: #match? only matches prerelease versions when #dependency is a # prerelease dependency. def match?(spec, allow_prerelease = false) @dependency.match? spec, nil, allow_prerelease end ## # Does this dependency request match +spec+? # # NOTE: #matches_spec? matches prerelease versions. See also #match? def matches_spec?(spec) @dependency.matches_spec? spec end ## # The name of the gem this dependency request is requesting. def name @dependency.name end def type @dependency.type end ## # Indicate that the request is for a gem explicitly requested by the user def explicit? @requester.nil? end ## # Indicate that the request is for a gem requested as a dependency of # another gem def implicit? !explicit? end ## # Return a String indicating who caused this request to be added (only # valid for implicit requests) def request_context @requester ? @requester.request : "(unknown)" end def pretty_print(q) # :nodoc: q.group 2, "[Dependency request ", "]" do q.breakable q.text @dependency.to_s q.breakable q.text " requested by " q.pp @requester end end ## # The version requirement for this dependency request def requirement @dependency.requirement end def to_s # :nodoc: @dependency.to_s end end PK!3=##(rubygems/resolver/local_specification.rbnu[# frozen_string_literal: true ## # A LocalSpecification comes from a .gem file on the local filesystem. class Gem::Resolver::LocalSpecification < Gem::Resolver::SpecSpecification ## # Returns +true+ if this gem is installable for the current platform. def installable_platform? return true if @source.is_a? Gem::Source::SpecificFile super end def local? # :nodoc: true end def pretty_print(q) # :nodoc: q.group 2, "[LocalSpecification", "]" do q.breakable q.text "name: #{name}" q.breakable q.text "version: #{version}" q.breakable q.text "platform: #{platform}" q.breakable q.text "dependencies:" q.breakable q.pp dependencies q.breakable q.text "source: #{@source.path}" end end end PK! &rubygems/resolver/api_specification.rbnu[# frozen_string_literal: true ## # Represents a specification retrieved via the rubygems.org API. # # This is used to avoid loading the full Specification object when all we need # is the name, version, and dependencies. class Gem::Resolver::APISpecification < Gem::Resolver::Specification ## # We assume that all instances of this class are immutable; # so avoid duplicated generation for performance. @@cache = {} def self.new(set, api_data) cache_key = [set, api_data] cache = @@cache[cache_key] return cache if cache @@cache[cache_key] = super end ## # Creates an APISpecification for the given +set+ from the rubygems.org # +api_data+. # # See https://guides.rubygems.org/rubygems-org-api/#misc-methods for the # format of the +api_data+. def initialize(set, api_data) super() @set = set @name = api_data[:name] @version = Gem::Version.new(api_data[:number]).freeze @platform = Gem::Platform.new(api_data[:platform]).freeze @original_platform = api_data[:platform].freeze @dependencies = api_data[:dependencies].map do |name, ver| Gem::Dependency.new(name, ver.split(/\s*,\s*/)).freeze end.freeze @required_ruby_version = Gem::Requirement.new(api_data.dig(:requirements, :ruby)).freeze @required_rubygems_version = Gem::Requirement.new(api_data.dig(:requirements, :rubygems)).freeze end def ==(other) # :nodoc: self.class === other && @set == other.set && @name == other.name && @version == other.version && @platform == other.platform end def hash @set.hash ^ @name.hash ^ @version.hash ^ @platform.hash end def fetch_development_dependencies # :nodoc: spec = source.fetch_spec Gem::NameTuple.new @name, @version, @platform @dependencies = spec.dependencies end def installable_platform? # :nodoc: Gem::Platform.match_gem? @platform, @name end def pretty_print(q) # :nodoc: q.group 2, "[APISpecification", "]" do q.breakable q.text "name: #{name}" q.breakable q.text "version: #{version}" q.breakable q.text "platform: #{platform}" q.breakable q.text "dependencies:" q.breakable q.pp @dependencies q.breakable q.text "set uri: #{@set.dep_uri}" end end ## # Fetches a Gem::Specification for this APISpecification. def spec # :nodoc: @spec ||= begin tuple = Gem::NameTuple.new @name, @version, @platform source.fetch_spec tuple rescue Gem::RemoteFetcher::FetchError raise if @original_platform == @platform tuple = Gem::NameTuple.new @name, @version, @original_platform source.fetch_spec tuple end end def source # :nodoc: @set.source end end PK!Kjrubygems/resolver/stats.rbnu[# frozen_string_literal: true class Gem::Resolver::Stats def initialize @max_depth = 0 @max_requirements = 0 @requirements = 0 @backtracking = 0 @iterations = 0 end def record_depth(stack) if stack.size > @max_depth @max_depth = stack.size end end def record_requirements(reqs) if reqs.size > @max_requirements @max_requirements = reqs.size end end def requirement! @requirements += 1 end def backtracking! @backtracking += 1 end def iteration! @iterations += 1 end PATTERN = "%20s: %d\n" def display $stdout.puts "=== Resolver Statistics ===" $stdout.printf PATTERN, "Max Depth", @max_depth $stdout.printf PATTERN, "Total Requirements", @requirements $stdout.printf PATTERN, "Max Requirements", @max_requirements $stdout.printf PATTERN, "Backtracking #", @backtracking $stdout.printf PATTERN, "Iteration #", @iterations end end PK!Mrrubygems/resolver/set.rbnu[# frozen_string_literal: true ## # Resolver sets are used to look up specifications (and their # dependencies) used in resolution. This set is abstract. class Gem::Resolver::Set ## # Set to true to disable network access for this set attr_accessor :remote ## # Errors encountered when resolving gems attr_accessor :errors ## # When true, allows matching of requests to prerelease gems. attr_accessor :prerelease def initialize # :nodoc: @prerelease = false @remote = true @errors = [] end ## # The find_all method must be implemented. It returns all Resolver # Specification objects matching the given DependencyRequest +req+. def find_all(req) raise NotImplementedError end ## # The #prefetch method may be overridden, but this is not necessary. This # default implementation does nothing, which is suitable for sets where # looking up a specification is cheap (such as installed gems). # # When overridden, the #prefetch method should look up specifications # matching +reqs+. def prefetch(reqs) end ## # When true, this set is allowed to access the network when looking up # specifications or dependencies. def remote? # :nodoc: @remote end end PK!w w (rubygems/resolver/index_specification.rbnu[# frozen_string_literal: true ## # Represents a possible Specification object returned from IndexSet. Used to # delay needed to download full Specification objects when only the +name+ # and +version+ are needed. class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification ## # An IndexSpecification is created from the index format described in `gem # help generate_index`. # # The +set+ contains other specifications for this (URL) +source+. # # The +name+, +version+ and +platform+ are the name, version and platform of # the gem. def initialize(set, name, version, source, platform) super() @set = set @name = name @version = version @source = source @platform = Gem::Platform.new(platform.to_s) @original_platform = platform.to_s @spec = nil end ## # The dependencies of the gem for this specification def dependencies spec.dependencies end ## # The required_ruby_version constraint for this specification # # A fallback is included because when generated, some marshalled specs have it # set to +nil+. def required_ruby_version spec.required_ruby_version || Gem::Requirement.default end ## # The required_rubygems_version constraint for this specification # # A fallback is included because the original version of the specification # API didn't include that field, so some marshalled specs in the index have it # set to +nil+. def required_rubygems_version spec.required_rubygems_version || Gem::Requirement.default end def ==(other) self.class === other && @name == other.name && @version == other.version && @platform == other.platform end def hash @name.hash ^ @version.hash ^ @platform.hash end def inspect # :nodoc: format("#<%s %s source %s>", self.class, full_name, @source) end def pretty_print(q) # :nodoc: q.group 2, "[Index specification", "]" do q.breakable q.text full_name unless @platform == Gem::Platform::RUBY q.breakable q.text @platform.to_s end q.breakable q.text "source " q.pp @source end end ## # Fetches a Gem::Specification for this IndexSpecification from the #source. def spec # :nodoc: @spec ||= begin tuple = Gem::NameTuple.new @name, @version, @original_platform @source.fetch_spec tuple end end end PK!:Ĩrubygems/resolver/index_set.rbnu[# frozen_string_literal: true ## # The global rubygems pool represented via the traditional # source index. class Gem::Resolver::IndexSet < Gem::Resolver::Set def initialize(source = nil) # :nodoc: super() @f = if source sources = Gem::SourceList.from [source] Gem::SpecFetcher.new sources else Gem::SpecFetcher.fetcher end @all = Hash.new {|h,k| h[k] = [] } list, errors = @f.available_specs :complete @errors.concat errors list.each do |uri, specs| specs.each do |n| @all[n.name] << [uri, n] end end @specs = {} end ## # Return an array of IndexSpecification objects matching # DependencyRequest +req+. def find_all(req) res = [] return res unless @remote name = req.dependency.name @all[name].each do |uri, n| next unless req.match? n, @prerelease res << Gem::Resolver::IndexSpecification.new( self, n.name, n.version, uri, n.platform ) end res end def pretty_print(q) # :nodoc: q.group 2, "[IndexSet", "]" do q.breakable q.text "sources:" q.breakable q.pp @f.sources q.breakable q.text "specs:" q.breakable names = @all.values.map do |tuples| tuples.map do |_, tuple| tuple.full_name end end.flatten q.seplist names do |name| q.text name end end end end PK!1۔'rubygems/resolver/api_set/gem_parser.rbnu[# frozen_string_literal: true class Gem::Resolver::APISet::GemParser EMPTY_ARRAY = [].freeze private_constant :EMPTY_ARRAY def parse(line) version_and_platform, rest = line.split(" ", 2) version, platform = version_and_platform.split("-", 2) dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : EMPTY_ARRAY requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : EMPTY_ARRAY [version, platform, dependencies, requirements] end private def parse_dependency(string) dependency = string.split(":") dependency[-1] = dependency[-1].split("&") if dependency.size > 1 dependency[0] = -dependency[0] dependency end end PK!Ϙ 'rubygems/resolver/activation_request.rbnu[# frozen_string_literal: true ## # Specifies a Specification object that should be activated. Also contains a # dependency that was used to introduce this activation. class Gem::Resolver::ActivationRequest ## # The parent request for this activation request. attr_reader :request ## # The specification to be activated. attr_reader :spec ## # Creates a new ActivationRequest that will activate +spec+. The parent # +request+ is used to provide diagnostics in case of conflicts. def initialize(spec, request) @spec = spec @request = request end def ==(other) # :nodoc: case other when Gem::Specification @spec == other when Gem::Resolver::ActivationRequest @spec == other.spec else false end end def eql?(other) self == other end def hash @spec.hash end ## # Is this activation request for a development dependency? def development? @request.development? end ## # Downloads a gem at +path+ and returns the file path. def download(path) Gem.ensure_gem_subdirectories path if @spec.respond_to? :sources exception = nil path = @spec.sources.find do |source| source.download full_spec, path rescue exception end return path if path raise exception if exception elsif @spec.respond_to? :source source = @spec.source source.download full_spec, path else source = Gem.sources.first source.download full_spec, path end end ## # The full name of the specification to be activated. def full_name name_tuple.full_name end alias_method :to_s, :full_name ## # The Gem::Specification for this activation request. def full_spec Gem::Specification === @spec ? @spec : @spec.spec end def inspect # :nodoc: format("#<%s for %p from %s>", self.class, @spec, @request) end ## # True if the requested gem has already been installed. def installed? case @spec when Gem::Resolver::VendorSpecification then true else this_spec = full_spec Gem::Specification.any? do |s| s == this_spec && s.base_dir == this_spec.base_dir end end end ## # The name of this activation request's specification def name @spec.name end ## # Return the ActivationRequest that contained the dependency # that we were activated for. def parent @request.requester end def pretty_print(q) # :nodoc: q.group 2, "[Activation request", "]" do q.breakable q.pp @spec q.breakable q.text " for " q.pp @request end end ## # The version of this activation request's specification def version @spec.version end ## # The platform of this activation request's specification def platform @spec.platform end private def name_tuple @name_tuple ||= Gem::NameTuple.new(name, version, platform) end end PK!NBB'rubygems/resolver/lock_specification.rbnu[# frozen_string_literal: true ## # The LockSpecification comes from a lockfile (Gem::RequestSet::Lockfile). # # A LockSpecification's dependency information is pre-filled from the # lockfile. class Gem::Resolver::LockSpecification < Gem::Resolver::Specification attr_reader :sources def initialize(set, name, version, sources, platform) super() @name = name @platform = platform @set = set @source = sources.first @sources = sources @version = version @dependencies = [] @spec = nil end ## # This is a null install as a locked specification is considered installed. # +options+ are ignored. def install(options = {}) destination = options[:install_dir] || Gem.dir if File.exist? File.join(destination, "specifications", spec.spec_name) yield nil return end super end ## # Adds +dependency+ from the lockfile to this specification def add_dependency(dependency) # :nodoc: @dependencies << dependency end def pretty_print(q) # :nodoc: q.group 2, "[LockSpecification", "]" do q.breakable q.text "name: #{@name}" q.breakable q.text "version: #{@version}" unless @platform == Gem::Platform::RUBY q.breakable q.text "platform: #{@platform}" end unless @dependencies.empty? q.breakable q.text "dependencies:" q.breakable q.pp @dependencies end end end ## # A specification constructed from the lockfile is returned def spec @spec ||= Gem::Specification.find do |spec| spec.name == @name && spec.version == @version end @spec ||= Gem::Specification.new do |s| s.name = @name s.version = @version s.platform = @platform s.dependencies.concat @dependencies end end end PK!*i rubygems/resolver/git_set.rbnu[# frozen_string_literal: true ## # A GitSet represents gems that are sourced from git repositories. # # This is used for gem dependency file support. # # Example: # # set = Gem::Resolver::GitSet.new # set.add_git_gem 'rake', 'git://example/rake.git', tag: 'rake-10.1.0' class Gem::Resolver::GitSet < Gem::Resolver::Set ## # The root directory for git gems in this set. This is usually Gem.dir, the # installation directory for regular gems. attr_accessor :root_dir ## # Contains repositories needing submodules attr_reader :need_submodules # :nodoc: ## # A Hash containing git gem names for keys and a Hash of repository and # git commit reference as values. attr_reader :repositories # :nodoc: ## # A hash of gem names to Gem::Resolver::GitSpecifications attr_reader :specs # :nodoc: def initialize # :nodoc: super() @git = ENV["git"] || "git" @need_submodules = {} @repositories = {} @root_dir = Gem.dir @specs = {} end def add_git_gem(name, repository, reference, submodules) # :nodoc: @repositories[name] = [repository, reference] @need_submodules[repository] = submodules end ## # Adds and returns a GitSpecification with the given +name+ and +version+ # which came from a +repository+ at the given +reference+. If +submodules+ # is true they are checked out along with the repository. # # This fills in the prefetch information as enough information about the gem # is present in the arguments. def add_git_spec(name, version, repository, reference, submodules) # :nodoc: add_git_gem name, repository, reference, submodules source = Gem::Source::Git.new name, repository, reference source.root_dir = @root_dir spec = Gem::Specification.new do |s| s.name = name s.version = version end git_spec = Gem::Resolver::GitSpecification.new self, spec, source @specs[spec.name] = git_spec git_spec end ## # Finds all git gems matching +req+ def find_all(req) prefetch nil specs.values.select do |spec| req.match? spec end end ## # Prefetches specifications from the git repositories in this set. def prefetch(reqs) return unless @specs.empty? @repositories.each do |name, (repository, reference)| source = Gem::Source::Git.new name, repository, reference source.root_dir = @root_dir source.remote = @remote source.specs.each do |spec| git_spec = Gem::Resolver::GitSpecification.new self, spec, source @specs[spec.name] = git_spec end end end def pretty_print(q) # :nodoc: q.group 2, "[GitSet", "]" do next if @repositories.empty? q.breakable repos = @repositories.map do |name, (repository, reference)| "#{name}: #{repository}@#{reference}" end q.seplist repos do |repo| q.text repo end end end end PK!NY'ܳ!rubygems/resolver/composed_set.rbnu[# frozen_string_literal: true ## # A ComposedSet allows multiple sets to be queried like a single set. # # To create a composed set with any number of sets use: # # Gem::Resolver.compose_sets set1, set2 # # This method will eliminate nesting of composed sets. class Gem::Resolver::ComposedSet < Gem::Resolver::Set attr_reader :sets # :nodoc: ## # Creates a new ComposedSet containing +sets+. Use # Gem::Resolver::compose_sets instead. def initialize(*sets) super() @sets = sets end ## # When +allow_prerelease+ is set to +true+ prereleases gems are allowed to # match dependencies. def prerelease=(allow_prerelease) super sets.each do |set| set.prerelease = allow_prerelease end end ## # Sets the remote network access for all composed sets. def remote=(remote) super @sets.each {|set| set.remote = remote } end def errors @errors + @sets.map(&:errors).flatten end ## # Finds all specs matching +req+ in all sets. def find_all(req) @sets.map do |s| s.find_all req end.flatten end ## # Prefetches +reqs+ in all sets. def prefetch(reqs) @sets.each {|s| s.prefetch(reqs) } end end PK!){Y%rubygems/resolver/best_set.rbnu[# frozen_string_literal: true ## # The BestSet chooses the best available method to query a remote index. # # It combines IndexSet and APISet class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet ## # Creates a BestSet for the given +sources+ or Gem::sources if none are # specified. +sources+ must be a Gem::SourceList. def initialize(sources = Gem.sources) super() @sources = sources end ## # Picks which sets to use for the configured sources. def pick_sets # :nodoc: @sources.each_source do |source| @sets << source.dependency_resolver_set end end def find_all(req) # :nodoc: pick_sets if @remote && @sets.empty? super end def prefetch(reqs) # :nodoc: pick_sets if @remote && @sets.empty? super end def pretty_print(q) # :nodoc: q.group 2, "[BestSet", "]" do q.breakable q.text "sets:" q.breakable q.pp @sets end end end PK!^&rubygems/resolver/git_specification.rbnu[# frozen_string_literal: true ## # A GitSpecification represents a gem that is sourced from a git repository # and is being loaded through a gem dependencies file through the +git:+ # option. class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification def ==(other) # :nodoc: self.class === other && @set == other.set && @spec == other.spec && @source == other.source end def add_dependency(dependency) # :nodoc: spec.dependencies << dependency end ## # Installing a git gem only involves building the extensions and generating # the executables. def install(options = {}) require_relative "../installer" installer = Gem::Installer.for_spec spec, options yield installer if block_given? installer.run_pre_install_hooks installer.build_extensions installer.run_post_build_hooks installer.generate_bin installer.run_post_install_hooks end def pretty_print(q) # :nodoc: q.group 2, "[GitSpecification", "]" do q.breakable q.text "name: #{name}" q.breakable q.text "version: #{version}" q.breakable q.text "dependencies:" q.breakable q.pp dependencies q.breakable q.text "source:" q.breakable q.pp @source end end end PK!޻ڢ rubygems/resolver/api_set.rbnu[# frozen_string_literal: true ## # The global rubygems pool, available via the rubygems.org API. # Returns instances of APISpecification. class Gem::Resolver::APISet < Gem::Resolver::Set autoload :GemParser, File.expand_path("api_set/gem_parser", __dir__) ## # The URI for the dependency API this APISet uses. attr_reader :dep_uri # :nodoc: ## # The Gem::Source that gems are fetched from attr_reader :source ## # The corresponding place to fetch gems. attr_reader :uri ## # Creates a new APISet that will retrieve gems from +uri+ using the RubyGems # API URL +dep_uri+ which is described at # https://guides.rubygems.org/rubygems-org-api def initialize(dep_uri = "https://index.rubygems.org/info/") super() dep_uri = Gem::URI dep_uri unless Gem::URI === dep_uri @dep_uri = dep_uri @uri = dep_uri + ".." @data = Hash.new {|h,k| h[k] = [] } @source = Gem::Source.new @uri @to_fetch = [] end ## # Return an array of APISpecification objects matching # DependencyRequest +req+. def find_all(req) res = [] return res unless @remote if @to_fetch.include?(req.name) prefetch_now end versions(req.name).each do |ver| if req.dependency.match? req.name, ver[:number], @prerelease res << Gem::Resolver::APISpecification.new(self, ver) end end res end ## # A hint run by the resolver to allow the Set to fetch # data for DependencyRequests +reqs+. def prefetch(reqs) return unless @remote names = reqs.map {|r| r.dependency.name } needed = names - @data.keys - @to_fetch @to_fetch += needed end def prefetch_now # :nodoc: needed = @to_fetch @to_fetch = [] needed.sort.each do |name| versions(name) end end def pretty_print(q) # :nodoc: q.group 2, "[APISet", "]" do q.breakable q.text "URI: #{@dep_uri}" q.breakable q.text "gem names:" q.pp @data.keys end end ## # Return data for all versions of the gem +name+. def versions(name) # :nodoc: if @data.key?(name) return @data[name] end uri = @dep_uri + name begin str = Gem::RemoteFetcher.fetcher.fetch_path uri rescue Gem::RemoteFetcher::FetchError @data[name] = [] else lines(str).each do |ver| number, platform, dependencies, requirements = parse_gem(ver) platform ||= "ruby" dependencies = dependencies.map {|dep_name, reqs| [dep_name, reqs.join(", ")] } requirements = requirements.map {|req_name, reqs| [req_name.to_sym, reqs] }.to_h @data[name] << { name: name, number: number, platform: platform, dependencies: dependencies, requirements: requirements } end end @data[name] end private def lines(str) lines = str.split("\n") header = lines.index("---") header ? lines[header + 1..-1] : lines end def parse_gem(string) @gem_parser ||= GemParser.new @gem_parser.parse(string) end end PK! @rubygems/resolver/source_set.rbnu[# frozen_string_literal: true ## # The SourceSet chooses the best available method to query a remote index. # # Kind off like BestSet but filters the sources for gems class Gem::Resolver::SourceSet < Gem::Resolver::Set ## # Creates a SourceSet for the given +sources+ or Gem::sources if none are # specified. +sources+ must be a Gem::SourceList. def initialize super() @links = {} @sets = {} end def find_all(req) # :nodoc: if set = get_set(req.dependency.name) set.find_all req else [] end end # potentially no-op def prefetch(reqs) # :nodoc: reqs.each do |req| if set = get_set(req.dependency.name) set.prefetch reqs end end end def add_source_gem(name, source) @links[name] = source end private def get_set(name) link = @links[name] @sets[link] ||= Gem::Source.new(link).dependency_resolver_set if link end end PK!͞AA)rubygems/resolver/vendor_specification.rbnu[# frozen_string_literal: true ## # A VendorSpecification represents a gem that has been unpacked into a project # and is being loaded through a gem dependencies file through the +path:+ # option. class Gem::Resolver::VendorSpecification < Gem::Resolver::SpecSpecification def ==(other) # :nodoc: self.class === other && @set == other.set && @spec == other.spec && @source == other.source end ## # This is a null install as this gem was unpacked into a directory. # +options+ are ignored. def install(options = {}) yield nil end end PK!qXX%rubygems/resolver/requirement_list.rbnu[# frozen_string_literal: true ## # The RequirementList is used to hold the requirements being considered # while resolving a set of gems. # # The RequirementList acts like a queue where the oldest items are removed # first. class Gem::Resolver::RequirementList include Enumerable ## # Creates a new RequirementList. def initialize @exact = [] @list = [] end def initialize_copy(other) # :nodoc: @exact = @exact.dup @list = @list.dup end ## # Adds Resolver::DependencyRequest +req+ to this requirements list. def add(req) if req.requirement.exact? @exact.push req else @list.push req end req end ## # Enumerates requirements in the list def each # :nodoc: return enum_for __method__ unless block_given? @exact.each do |requirement| yield requirement end @list.each do |requirement| yield requirement end end ## # How many elements are in the list def size @exact.size + @list.size end ## # Is the list empty? def empty? @exact.empty? && @list.empty? end ## # Remove the oldest DependencyRequest from the list. def remove return @exact.shift unless @exact.empty? @list.shift end ## # Returns the oldest five entries from the list. def next5 x = @exact[0,5] x + @list[0,5 - x.size] end end PK!֚rubygems/resolver/vendor_set.rbnu[# frozen_string_literal: true ## # A VendorSet represents gems that have been unpacked into a specific # directory that contains a gemspec. # # This is used for gem dependency file support. # # Example: # # set = Gem::Resolver::VendorSet.new # # set.add_vendor_gem 'rake', 'vendor/rake' # # The directory vendor/rake must contain an unpacked rake gem along with a # rake.gemspec (watching the given name). class Gem::Resolver::VendorSet < Gem::Resolver::Set ## # The specifications for this set. attr_reader :specs # :nodoc: def initialize # :nodoc: super() @directories = {} @specs = {} end ## # Adds a specification to the set with the given +name+ which has been # unpacked into the given +directory+. def add_vendor_gem(name, directory) # :nodoc: gemspec = File.join directory, "#{name}.gemspec" spec = Gem::Specification.load gemspec raise Gem::GemNotFoundException, "unable to find #{gemspec} for gem #{name}" unless spec spec.full_gem_path = File.expand_path directory @specs[spec.name] = spec @directories[spec] = directory spec end ## # Returns an Array of VendorSpecification objects matching the # DependencyRequest +req+. def find_all(req) @specs.values.select do |spec| req.match? spec end.map do |spec| source = Gem::Source::Vendor.new @directories[spec] Gem::Resolver::VendorSpecification.new self, spec, source end end ## # Loads a spec with the given +name+. +version+, +platform+ and +source+ are # ignored. def load_spec(name, version, platform, source) # :nodoc: @specs.fetch name end def pretty_print(q) # :nodoc: q.group 2, "[VendorSet", "]" do next if @directories.empty? q.breakable dirs = @directories.map do |spec, directory| "#{spec.full_name}: #{directory}" end q.seplist dirs do |dir| q.text dir end end end end PK!Vå "rubygems/resolver/specification.rbnu[# frozen_string_literal: true ## # A Resolver::Specification contains a subset of the information # contained in a Gem::Specification. Only the information necessary for # dependency resolution in the resolver is included. class Gem::Resolver::Specification ## # The dependencies of the gem for this specification attr_reader :dependencies ## # The name of the gem for this specification attr_reader :name ## # The platform this gem works on. attr_reader :platform ## # The set this specification came from. attr_reader :set ## # The source for this specification attr_reader :source ## # The Gem::Specification for this Resolver::Specification. # # Implementers, note that #install updates @spec, so be sure to cache the # Gem::Specification in @spec when overriding. attr_reader :spec ## # The version of the gem for this specification. attr_reader :version ## # The required_ruby_version constraint for this specification. attr_reader :required_ruby_version ## # The required_ruby_version constraint for this specification. attr_reader :required_rubygems_version ## # Sets default instance variables for the specification. def initialize @dependencies = nil @name = nil @platform = nil @set = nil @source = nil @version = nil @required_ruby_version = Gem::Requirement.default @required_rubygems_version = Gem::Requirement.default end ## # Fetches development dependencies if the source does not provide them by # default (see APISpecification). def fetch_development_dependencies # :nodoc: end ## # The name and version of the specification. # # Unlike Gem::Specification#full_name, the platform is not included. def full_name "#{@name}-#{@version}" end ## # Installs this specification using the Gem::Installer +options+. The # install method yields a Gem::Installer instance, which indicates the # gem will be installed, or +nil+, which indicates the gem is already # installed. # # After installation #spec is updated to point to the just-installed # specification. def install(options = {}) require_relative "../installer" gem = download options installer = Gem::Installer.at gem, options yield installer if block_given? @spec = installer.install end def download(options) dir = options[:install_dir] || Gem.dir Gem.ensure_gem_subdirectories dir source.download spec, dir end ## # Returns true if this specification is installable on this platform. def installable_platform? Gem::Platform.match_spec? spec end def local? # :nodoc: false end end PK!eh'rubygems/resolver/spec_specification.rbnu[# frozen_string_literal: true ## # The Resolver::SpecSpecification contains common functionality for # Resolver specifications that are backed by a Gem::Specification. class Gem::Resolver::SpecSpecification < Gem::Resolver::Specification ## # A SpecSpecification is created for a +set+ for a Gem::Specification in # +spec+. The +source+ is either where the +spec+ came from, or should be # loaded from. def initialize(set, spec, source = nil) @set = set @source = source @spec = spec end ## # The dependencies of the gem for this specification def dependencies spec.dependencies end ## # The required_ruby_version constraint for this specification def required_ruby_version spec.required_ruby_version end ## # The required_rubygems_version constraint for this specification def required_rubygems_version spec.required_rubygems_version end ## # The name and version of the specification. # # Unlike Gem::Specification#full_name, the platform is not included. def full_name "#{spec.name}-#{spec.version}" end ## # The name of the gem for this specification def name spec.name end ## # The platform this gem works on. def platform spec.platform end ## # The version of the gem for this specification. def version spec.version end ## # The hash value for this specification. def hash spec.hash end end PK!Qh9,rubygems/resolver/installed_specification.rbnu[# frozen_string_literal: true ## # An InstalledSpecification represents a gem that is already installed # locally. class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification def ==(other) # :nodoc: self.class === other && @set == other.set && @spec == other.spec end ## # This is a null install as this specification is already installed. # +options+ are ignored. def install(options = {}) yield nil end ## # Returns +true+ if this gem is installable for the current platform. def installable_platform? # BACKCOMPAT If the file is coming out of a specified file, then we # ignore the platform. This code can be removed in RG 3.0. return true if @source.is_a? Gem::Source::SpecificFile super end def pretty_print(q) # :nodoc: q.group 2, "[InstalledSpecification", "]" do q.breakable q.text "name: #{name}" q.breakable q.text "version: #{version}" q.breakable q.text "platform: #{platform}" q.breakable q.text "dependencies:" q.breakable q.pp spec.dependencies end end ## # The source for this specification def source @source ||= Gem::Source::Installed.new end end PK!]Z^^"rubygems/resolver/installer_set.rbnu[# frozen_string_literal: true ## # A set of gems for installation sourced from remote sources and local .gem # files class Gem::Resolver::InstallerSet < Gem::Resolver::Set ## # List of Gem::Specification objects that must always be installed. attr_reader :always_install # :nodoc: ## # Only install gems in the always_install list attr_accessor :ignore_dependencies # :nodoc: ## # Do not look in the installed set when finding specifications. This is # used by the --install-dir option to `gem install` attr_accessor :ignore_installed # :nodoc: ## # The remote_set looks up remote gems for installation. attr_reader :remote_set # :nodoc: ## # Ignore ruby & rubygems specification constraints. # attr_accessor :force # :nodoc: ## # Creates a new InstallerSet that will look for gems in +domain+. def initialize(domain) super() @domain = domain @f = Gem::SpecFetcher.fetcher @always_install = [] @ignore_dependencies = false @ignore_installed = false @local = {} @local_source = Gem::Source::Local.new @remote_set = Gem::Resolver::BestSet.new @force = false @specs = {} end ## # Looks up the latest specification for +dependency+ and adds it to the # always_install list. def add_always_install(dependency) request = Gem::Resolver::DependencyRequest.new dependency, nil found = find_all request found.delete_if do |s| s.version.prerelease? && !s.local? end unless dependency.prerelease? found = found.select do |s| Gem::Source::SpecificFile === s.source || Gem::Platform.match_spec?(s) end found = found.sort_by do |s| [s.version, Gem::Platform.sort_priority(s.platform)] end newest = found.last unless newest exc = Gem::UnsatisfiableDependencyError.new request exc.errors = errors raise exc end unless @force found_matching_metadata = found.reverse.find do |spec| metadata_satisfied?(spec) end if found_matching_metadata.nil? ensure_required_ruby_version_met(newest.spec) ensure_required_rubygems_version_met(newest.spec) else newest = found_matching_metadata end end @always_install << newest.spec end ## # Adds a local gem requested using +dep_name+ with the given +spec+ that can # be loaded and installed using the +source+. def add_local(dep_name, spec, source) @local[dep_name] = [spec, source] end ## # Should local gems should be considered? def consider_local? # :nodoc: @domain == :both || @domain == :local end ## # Should remote gems should be considered? def consider_remote? # :nodoc: @domain == :both || @domain == :remote end ## # Errors encountered while resolving gems def errors @errors + @remote_set.errors end ## # Returns an array of IndexSpecification objects matching DependencyRequest # +req+. def find_all(req) res = [] dep = req.dependency return res if @ignore_dependencies && @always_install.none? {|spec| dep.match? spec } name = dep.name dep.matching_specs.each do |gemspec| next if @always_install.any? {|spec| spec.name == gemspec.name } res << Gem::Resolver::InstalledSpecification.new(self, gemspec) end unless @ignore_installed matching_local = [] if consider_local? matching_local = @local.values.select do |spec, _| req.match? spec end.map do |spec, source| Gem::Resolver::LocalSpecification.new self, spec, source end res.concat matching_local begin if local_spec = @local_source.find_gem(name, dep.requirement) res << Gem::Resolver::IndexSpecification.new( self, local_spec.name, local_spec.version, @local_source, local_spec.platform ) end rescue Gem::Package::FormatError # ignore end end res.concat @remote_set.find_all req if consider_remote? && matching_local.empty? res end def prefetch(reqs) @remote_set.prefetch(reqs) if consider_remote? end def prerelease=(allow_prerelease) super @remote_set.prerelease = allow_prerelease end def inspect # :nodoc: always_install = @always_install.map(&:full_name) format("#<%s domain: %s specs: %p always install: %p>", self.class, @domain, @specs.keys, always_install) end ## # Called from IndexSpecification to get a true Specification # object. def load_spec(name, ver, platform, source) # :nodoc: key = "#{name}-#{ver}-#{platform}" @specs.fetch key do tuple = Gem::NameTuple.new name, ver, platform @specs[key] = source.fetch_spec tuple end end ## # Has a local gem for +dep_name+ been added to this set? def local?(dep_name) # :nodoc: spec, _ = @local[dep_name] spec end def pretty_print(q) # :nodoc: q.group 2, "[InstallerSet", "]" do q.breakable q.text "domain: #{@domain}" q.breakable q.text "specs: " q.pp @specs.keys q.breakable q.text "always install: " q.pp @always_install end end def remote=(remote) # :nodoc: case @domain when :local then @domain = :both if remote when :remote then @domain = nil unless remote when :both then @domain = :local unless remote end end private def metadata_satisfied?(spec) spec.required_ruby_version.satisfied_by?(Gem.ruby_version) && spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version) end def ensure_required_ruby_version_met(spec) # :nodoc: if rrv = spec.required_ruby_version ruby_version = Gem.ruby_version unless rrv.satisfied_by? ruby_version raise Gem::RuntimeRequirementNotMetError, "#{spec.full_name} requires Ruby version #{rrv}. The current ruby version is #{ruby_version}." end end end def ensure_required_rubygems_version_met(spec) # :nodoc: if rrgv = spec.required_rubygems_version unless rrgv.satisfied_by? Gem.rubygems_version rg_version = Gem::VERSION raise Gem::RuntimeRequirementNotMetError, "#{spec.full_name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " \ "Try 'gem update --system' to update RubyGems itself." end end end end PK!Nh rubygems/resolver/conflict.rbnu[# frozen_string_literal: true ## # Used internally to indicate that a dependency conflicted # with a spec that would be activated. class Gem::Resolver::Conflict ## # The specification that was activated prior to the conflict attr_reader :activated ## # The dependency that is in conflict with the activated gem. attr_reader :dependency attr_reader :failed_dep # :nodoc: ## # Creates a new resolver conflict when +dependency+ is in conflict with an # already +activated+ specification. def initialize(dependency, activated, failed_dep=dependency) @dependency = dependency @activated = activated @failed_dep = failed_dep end def ==(other) # :nodoc: self.class === other && @dependency == other.dependency && @activated == other.activated && @failed_dep == other.failed_dep end ## # A string explanation of the conflict. def explain "" end ## # Return the 2 dependency objects that conflicted def conflicting_dependencies [@failed_dep.dependency, @activated.request.dependency] end ## # Explanation of the conflict used by exceptions to print useful messages def explanation activated = @activated.spec.full_name dependency = @failed_dep.dependency requirement = dependency.requirement alternates = dependency.matching_specs.map(&:full_name) unless alternates.empty? matching = <<-MATCHING.chomp Gems matching %s: %s MATCHING matching = format(matching, dependency, alternates.join(", ")) end explanation = <<-EXPLANATION Activated %s which does not match conflicting dependency (%s) Conflicting dependency chains: %s versus: %s %s EXPLANATION format(explanation, activated, requirement, request_path(@activated).reverse.join(", depends on\n "), request_path(@failed_dep).reverse.join(", depends on\n "), matching) end ## # Returns true if the conflicting dependency's name matches +spec+. def for_spec?(spec) @dependency.name == spec.name end def pretty_print(q) # :nodoc: q.group 2, "[Dependency conflict: ", "]" do q.breakable q.text "activated " q.pp @activated q.breakable q.text " dependency " q.pp @dependency q.breakable if @dependency == @failed_dep q.text " failed" else q.text " failed dependency " q.pp @failed_dep end end end ## # Path of activations from the +current+ list. def request_path(current) path = [] while current do case current when Gem::Resolver::ActivationRequest then path << "#{current.request.dependency}, #{current.spec.version} activated" current = current.parent when Gem::Resolver::DependencyRequest then path << current.dependency.to_s current = current.requester else raise Gem::Exception, "[BUG] unknown request class #{current.class}" end end path = ["user request (gem command or Gemfile)"] if path.empty? path end ## # Return the Specification that listed the dependency def requester @failed_dep.requester end end PK!F0T rubygems/resolver/current_set.rbnu[# frozen_string_literal: true ## # A set which represents the installed gems. Respects # all the normal settings that control where to look # for installed gems. class Gem::Resolver::CurrentSet < Gem::Resolver::Set def find_all(req) req.dependency.matching_specs end end PK!p)%rubygems/defaults/operating_system.rbnu[module Gem class << self ## # Returns full path of previous but one directory of dir in path # E.g. for '/usr/share/ruby', 'ruby', it returns '/usr' def previous_but_one_dir_to(path, dir) return unless path split_path = path.split(File::SEPARATOR) File.join(split_path.take_while { |one_dir| one_dir !~ /^#{dir}$/ }[0..-2]) end private :previous_but_one_dir_to ## # Detects --install-dir option specified on command line. def opt_install_dir? @opt_install_dir ||= ARGV.include?('--install-dir') || ARGV.include?('-i') end private :opt_install_dir? ## # Detects --build-root option specified on command line. def opt_build_root? @opt_build_root ||= ARGV.include?('--build-root') end private :opt_build_root? ## # Tries to detect, if arguments and environment variables suggest that # 'gem install' is executed from rpmbuild. def rpmbuild? @rpmbuild ||= ENV['RPM_PACKAGE_NAME'] && (opt_install_dir? || opt_build_root?) end private :rpmbuild? ## # Default gems locations allowed on FHS system (/usr, /usr/share). # The locations are derived from directories specified during build # configuration. def default_locations @default_locations ||= { :system => previous_but_one_dir_to(RbConfig::CONFIG['vendordir'], RbConfig::CONFIG['RUBY_INSTALL_NAME']), :local => previous_but_one_dir_to(RbConfig::CONFIG['sitedir'], RbConfig::CONFIG['RUBY_INSTALL_NAME']) } end ## # For each location provides set of directories for binaries (:bin_dir) # platform independent (:gem_dir) and dependent (:ext_dir) files. def default_dirs @libdir ||= case RUBY_PLATFORM when 'java' RbConfig::CONFIG['datadir'] else RbConfig::CONFIG['libdir'] end @default_dirs ||= default_locations.inject(Hash.new) do |hash, location| destination, path = location hash[destination] = if path { :bin_dir => File.join(path, RbConfig::CONFIG['bindir'].split(File::SEPARATOR).last), :gem_dir => File.join(path, RbConfig::CONFIG['datadir'].split(File::SEPARATOR).last, 'gems'), :ext_dir => File.join(path, @libdir.split(File::SEPARATOR).last, 'gems') } else { :bin_dir => '', :gem_dir => '', :ext_dir => '' } end hash end end ## # Remove methods we are going to override. This avoids "method redefined;" # warnings otherwise issued by Ruby. remove_method :operating_system_defaults if method_defined? :operating_system_defaults remove_method :default_dir if method_defined? :default_dir remove_method :default_path if method_defined? :default_path remove_method :default_ext_dir_for if method_defined? :default_ext_dir_for ## # Regular user installs into user directory, root manages /usr/local. def operating_system_defaults unless opt_build_root? options = if Process.uid == 0 "--install-dir=#{Gem.default_dirs[:local][:gem_dir]} --bindir #{Gem.default_dirs[:local][:bin_dir]}" end {"gem" => options} else {} end end ## # RubyGems default overrides. def default_dir Gem.default_dirs[:system][:gem_dir] end def default_path path = default_dirs.collect {|location, paths| paths[:gem_dir]} path.unshift Gem.user_dir if File.exist? Gem.user_home path end def default_ext_dir_for base_dir dir = if rpmbuild? build_dir = base_dir.chomp Gem.default_dirs[:system][:gem_dir] if build_dir != base_dir File.join build_dir, Gem.default_dirs[:system][:ext_dir] end else dirs = Gem.default_dirs.detect {|location, paths| paths[:gem_dir] == base_dir} dirs && dirs.last[:ext_dir] end dir && File.join(dir, RbConfig::CONFIG['RUBY_INSTALL_NAME']) end # This method should be available since RubyGems 2.2 until RubyGems 3.0. # https://github.com/rubygems/rubygems/issues/749 if method_defined? :install_extension_in_lib remove_method :install_extension_in_lib def install_extension_in_lib false end end end end PK![Wrubygems/ext/build_error.rbnu[# frozen_string_literal: true ## # Raised when there is an error while building extensions. require_relative "../exceptions" class Gem::Ext::BuildError < Gem::InstallError end PK!ԅVrubygems/ext/cmake_builder.rbnu[# frozen_string_literal: true class Gem::Ext::CmakeBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd) unless File.exist?(File.join(cmake_dir, "Makefile")) require_relative "../command" cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args] run cmd, results, class_name, cmake_dir end make dest_path, results, cmake_dir results end end PK!v''rubygems/ext/cargo_builder.rbnu[# frozen_string_literal: true require_relative "../shellwords" # This class is used by rubygems to build Rust extensions. It is a thin-wrapper # over the `cargo rustc` command which takes care of building Rust code in a way # that Ruby can use. class Gem::Ext::CargoBuilder < Gem::Ext::Builder attr_accessor :spec, :runner, :profile def initialize require_relative "../command" require_relative "cargo_builder/link_flag_converter" @runner = self.class.method(:run) @profile = :release end def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd) require "tempfile" require "fileutils" # Where's the Cargo.toml of the crate we're building cargo_toml = File.join(cargo_dir, "Cargo.toml") # What's the crate's name crate_name = cargo_crate_name(cargo_dir, cargo_toml, results) begin # Create a tmp dir to do the build in tmp_dest = Dir.mktmpdir(".gem.", cargo_dir) # Run the build cmd = cargo_command(cargo_toml, tmp_dest, args, crate_name) runner.call(cmd, results, "cargo", cargo_dir, build_env) # Where do we expect Cargo to write the compiled library dylib_path = cargo_dylib_path(tmp_dest, crate_name) # Helpful error if we didn't find the compiled library raise DylibNotFoundError, tmp_dest unless File.exist?(dylib_path) # Cargo and Ruby differ on how the library should be named, rename from # what Cargo outputs to what Ruby expects dlext_name = "#{crate_name}.#{makefile_config("DLEXT")}" dlext_path = File.join(File.dirname(dylib_path), dlext_name) FileUtils.cp(dylib_path, dlext_path) nesting = extension_nesting(extension) # TODO: remove in RubyGems 4 if Gem.install_extension_in_lib && lib_dir nested_lib_dir = File.join(lib_dir, nesting) FileUtils.mkdir_p nested_lib_dir FileUtils.cp_r dlext_path, nested_lib_dir, remove_destination: true end # move to final destination nested_dest_path = File.join(dest_path, nesting) FileUtils.mkdir_p nested_dest_path FileUtils.cp_r dlext_path, nested_dest_path, remove_destination: true ensure # clean up intermediary build artifacts FileUtils.rm_rf tmp_dest if tmp_dest end results end def build_env build_env = rb_config_env build_env["RUBY_STATIC"] = "true" if ruby_static? && ENV.key?("RUBY_STATIC") cfg = "--cfg=rb_sys_gem --cfg=rubygems --cfg=rubygems_#{Gem::VERSION.tr(".", "_")}" build_env["RUSTFLAGS"] = [ENV["RUSTFLAGS"], cfg].compact.join(" ") build_env end def cargo_command(cargo_toml, dest_path, args = [], crate_name = nil) cmd = [] cmd += [cargo, "rustc"] cmd += ["--crate-type", "cdylib"] cmd += ["--target", ENV["CARGO_BUILD_TARGET"]] if ENV["CARGO_BUILD_TARGET"] cmd += ["--target-dir", dest_path] cmd += ["--manifest-path", cargo_toml] cmd += ["--lib"] cmd += ["--profile", profile.to_s] cmd += ["--locked"] cmd += Gem::Command.build_args cmd += args cmd += ["--"] cmd += [*cargo_rustc_args(dest_path, crate_name)] cmd end private def cargo ENV.fetch("CARGO", "cargo") end # returns the directory nesting of the extension, ignoring the first part, so # "ext/foo/bar/Cargo.toml" becomes "foo/bar" def extension_nesting(extension) parts = extension.to_s.split(Regexp.union([File::SEPARATOR, File::ALT_SEPARATOR].compact)) parts = parts.each_with_object([]) do |segment, final| next if segment == "." if segment == ".." raise Gem::InstallError, "extension outside of gem root" if final.empty? next final.pop end final << segment end File.join(parts[1...-1]) end def rb_config_env result = {} RbConfig::CONFIG.each {|k, v| result["RBCONFIG_#{k}"] = v } result end def cargo_rustc_args(dest_dir, crate_name) [ *linker_args, *mkmf_libpath, *rustc_dynamic_linker_flags(dest_dir, crate_name), *rustc_lib_flags(dest_dir), *platform_specific_rustc_args(dest_dir), ] end def platform_specific_rustc_args(dest_dir, flags = []) if mingw_target? # On mingw platforms, mkmf adds libruby to the linker flags flags += libruby_args(dest_dir) # Make sure ALSR is used on mingw # see https://github.com/rust-lang/rust/pull/75406/files flags += ["-C", "link-arg=-Wl,--dynamicbase"] flags += ["-C", "link-arg=-Wl,--disable-auto-image-base"] # If the gem is installed on a host with build tools installed, but is # run on one that isn't the missing libraries will cause the extension # to fail on start. flags += ["-C", "link-arg=-static-libgcc"] elsif darwin_target? # Ventura does not always have this flag enabled flags += ["-C", "link-arg=-Wl,-undefined,dynamic_lookup"] end flags end # We want to use the same linker that Ruby uses, so that the linker flags from # mkmf work properly. def linker_args cc_flag = Shellwords.split(makefile_config("CC")) linker = cc_flag.shift link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] } return mswin_link_args if linker == "cl" ["-C", "linker=#{linker}", *link_args] end def mswin_link_args args = [] args += ["-l", makefile_config("LIBRUBYARG_SHARED").chomp(".lib")] args += split_flags("LIBS").flat_map {|lib| ["-l", lib.chomp(".lib")] } args += split_flags("LOCAL_LIBS").flat_map {|lib| ["-l", lib.chomp(".lib")] } args end def libruby_args(dest_dir) libs = makefile_config(ruby_static? ? "LIBRUBYARG_STATIC" : "LIBRUBYARG_SHARED") raw_libs = Shellwords.split(libs) raw_libs.flat_map {|l| ldflag_to_link_modifier(l) } end def ruby_static? return true if %w[1 true].include?(ENV["RUBY_STATIC"]) makefile_config("ENABLE_SHARED") == "no" end def cargo_dylib_path(dest_path, crate_name) so_ext = RbConfig::CONFIG["SOEXT"] prefix = so_ext == "dll" ? "" : "lib" path_parts = [dest_path] path_parts << ENV["CARGO_BUILD_TARGET"] if ENV["CARGO_BUILD_TARGET"] path_parts += ["release", "#{prefix}#{crate_name}.#{so_ext}"] File.join(*path_parts) end def cargo_crate_name(cargo_dir, manifest_path, results) require "open3" Gem.load_yaml output, status = begin Open3.capture2e(cargo, "metadata", "--no-deps", "--format-version", "1", chdir: cargo_dir) rescue StandardError => error raise Gem::InstallError, "cargo metadata failed #{error.message}" end unless status.success? if Gem.configuration.really_verbose puts output else results << output end exit_reason = if status.exited? ", exit code #{status.exitstatus}" elsif status.signaled? ", uncaught signal #{status.termsig}" end raise Gem::InstallError, "cargo metadata failed#{exit_reason}" end # cargo metadata output is specified as json, but with the # --format-version 1 option the output is compatible with YAML, so we can # avoid the json dependency metadata = Gem::SafeYAML.safe_load(output) package = metadata["packages"].find {|pkg| normalize_path(pkg["manifest_path"]) == manifest_path } unless package found = metadata["packages"].map {|md| "#{md["name"]} at #{md["manifest_path"]}" } raise Gem::InstallError, <<-EOF failed to determine cargo package name looking for: #{manifest_path} found: #{found.join("\n")} EOF end package["name"].tr("-", "_") end def normalize_path(path) return path unless File::ALT_SEPARATOR path.tr(File::ALT_SEPARATOR, File::SEPARATOR) end def rustc_dynamic_linker_flags(dest_dir, crate_name) split_flags("DLDFLAGS"). map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir, crate_name) }. compact. flat_map {|arg| ldflag_to_link_modifier(arg) } end def rustc_lib_flags(dest_dir) split_flags("LIBS").flat_map {|arg| ldflag_to_link_modifier(arg) } end def split_flags(var) Shellwords.split(RbConfig::CONFIG.fetch(var, "")) end def ldflag_to_link_modifier(arg) LinkFlagConverter.convert(arg) end def msvc_target? makefile_config("target_os").include?("msvc") end def darwin_target? makefile_config("target_os").include?("darwin") end def mingw_target? makefile_config("target_os").include?("mingw") end def win_target? target_platform = RbConfig::CONFIG["target_os"] !!Gem::WIN_PATTERNS.find {|r| target_platform =~ r } end # Interpolate substitution vars in the arg (i.e. $(DEFFILE)) def maybe_resolve_ldflag_variable(input_arg, dest_dir, crate_name) var_matches = input_arg.match(/\$\((\w+)\)/) return input_arg unless var_matches var_name = var_matches[1] return input_arg if var_name.nil? || var_name.chomp.empty? case var_name # On windows, it is assumed that mkmf has setup an exports file for the # extension, so we have to create one ourselves. when "DEFFILE" write_deffile(dest_dir, crate_name) else RbConfig::CONFIG[var_name] end end def write_deffile(dest_dir, crate_name) deffile_path = File.join(dest_dir, "#{crate_name}-#{RbConfig::CONFIG["arch"]}.def") export_prefix = makefile_config("EXPORT_PREFIX") || "" File.open(deffile_path, "w") do |f| f.puts "EXPORTS" f.puts "#{export_prefix.strip}Init_#{crate_name}" end deffile_path end # Corresponds to $(LIBPATH) in mkmf def mkmf_libpath ["-L", "native=#{makefile_config("libdir")}"] end def makefile_config(var_name) val = RbConfig::MAKEFILE_CONFIG[var_name] return unless val RbConfig.expand(val.dup) end # Error raised when no cdylib artifact was created class DylibNotFoundError < StandardError def initialize(dir) files = Dir.glob(File.join(dir, "**", "*")).map {|f| "- #{f}" }.join "\n" super <<~MSG Dynamic library not found for Rust extension (in #{dir}) Make sure you set "crate-type" in Cargo.toml to "cdylib" Found files: #{files} MSG end end end PK!_j rubygems/ext/rake_builder.rbnu[# frozen_string_literal: true require_relative "../shellwords" #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ class Gem::Ext::RakeBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd) if /mkrf_conf/i.match?(File.basename(extension)) run([Gem.ruby, File.basename(extension), *args], results, class_name, extension_dir) end rake = ENV["rake"] if rake rake = Shellwords.split(rake) else begin rake = ruby << "-rrubygems" << Gem.bin_path("rake", "rake") rescue Gem::Exception rake = [Gem.default_exec_format % "rake"] end end rake_args = ["RUBYARCHDIR=#{dest_path}", "RUBYLIBDIR=#{dest_path}", *args] run(rake + rake_args, results, class_name, extension_dir) results end end PK!>1rubygems/ext/cargo_builder/link_flag_converter.rbnu[# frozen_string_literal: true class Gem::Ext::CargoBuilder < Gem::Ext::Builder # Converts Ruby link flags into something cargo understands class LinkFlagConverter FILTERED_PATTERNS = [ /compress-debug-sections/, # Not supported by all linkers, and not required for Rust ].freeze def self.convert(arg) return [] if FILTERED_PATTERNS.any? {|p| p.match?(arg) } case arg.chomp when /^-L\s*(.+)$/ ["-L", "native=#{$1}"] when /^--library=(\w+\S+)$/, /^-l\s*(\w+\S+)$/ ["-l", $1] when /^-l\s*([^:\s])+/ # -lfoo, but not -l:libfoo.a ["-l", $1] when /^-F\s*(.*)$/ ["-l", "framework=#{$1}"] else ["-C", "link-args=#{arg}"] end end end end PK!erubygems/ext/builder.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../user_interaction" require_relative "../shellwords" class Gem::Ext::Builder include Gem::UserInteraction attr_accessor :build_args # :nodoc: def self.class_name name =~ /Ext::(.*)Builder/ $1.downcase end def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"]) unless File.exist? File.join(make_dir, "Makefile") raise Gem::InstallError, "Makefile not found" end # try to find make program from Ruby configure arguments first RbConfig::CONFIG["configure_args"] =~ /with-make-prog\=(\w+)/ make_program_name = ENV["MAKE"] || ENV["make"] || $1 make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make" make_program = Shellwords.split(make_program_name) # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform. destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" env = [destdir] if sitedir env << format("sitearchdir=%s", sitedir) env << format("sitelibdir=%s", sitedir) end targets.each do |target| # Pass DESTDIR via command line to override what's in MAKEFLAGS cmd = [ *make_program, *env, target, ].reject(&:empty?) begin run(cmd, results, "make #{target}".rstrip, make_dir) rescue Gem::InstallError raise unless target == "clean" # ignore clean failure end end end def self.ruby # Gem.ruby is quoted if it contains whitespace cmd = Shellwords.split(Gem.ruby) # This load_path is only needed when running rubygems test without a proper installation. # Prepending it in a normal installation will cause problem with order of $LOAD_PATH. # Therefore only add load_path if it is not present in the default $LOAD_PATH. load_path = File.expand_path("../..", __dir__) case load_path when RbConfig::CONFIG["sitelibdir"], RbConfig::CONFIG["vendorlibdir"], RbConfig::CONFIG["rubylibdir"] cmd else cmd << "-I#{load_path}" end end def self.run(command, results, command_name = nil, dir = Dir.pwd, env = {}) verbose = Gem.configuration.really_verbose begin rubygems_gemdeps = ENV["RUBYGEMS_GEMDEPS"] ENV["RUBYGEMS_GEMDEPS"] = nil if verbose puts("current directory: #{dir}") p(command) end results << "current directory: #{dir}" results << Shellwords.join(command) require "open3" # Set $SOURCE_DATE_EPOCH for the subprocess. build_env = { "SOURCE_DATE_EPOCH" => Gem.source_date_epoch_string }.merge(env) output, status = begin Open3.popen2e(build_env, *command, chdir: dir) do |_stdin, stdouterr, wait_thread| output = String.new while line = stdouterr.gets output << line if verbose print line end end [output, wait_thread.value] end rescue StandardError => error raise Gem::InstallError, "#{command_name || class_name} failed#{error.message}" end unless verbose results << output end ensure ENV["RUBYGEMS_GEMDEPS"] = rubygems_gemdeps end unless status.success? results << "Building has failed. See above output for more information on the failure." if verbose end yield(status, results) if block_given? unless status.success? exit_reason = if status.exited? ", exit code #{status.exitstatus}" elsif status.signaled? ", uncaught signal #{status.termsig}" end raise Gem::InstallError, "#{command_name || class_name} failed#{exit_reason}" end end ## # Creates a new extension builder for +spec+. If the +spec+ does not yet # have build arguments, saved, set +build_args+ which is an ARGV-style # array. def initialize(spec, build_args = spec.build_args) @spec = spec @build_args = build_args @gem_dir = spec.full_gem_path @ran_rake = false end ## # Chooses the extension builder class for +extension+ def builder_for(extension) # :nodoc: case extension when /extconf/ then Gem::Ext::ExtConfBuilder when /configure/ then Gem::Ext::ConfigureBuilder when /rakefile/i, /mkrf_conf/i then @ran_rake = true Gem::Ext::RakeBuilder when /CMakeLists.txt/ then Gem::Ext::CmakeBuilder when /Cargo.toml/ then Gem::Ext::CargoBuilder.new else build_error("No builder for extension '#{extension}'") end end ## # Logs the build +output+, then raises Gem::Ext::BuildError. def build_error(output, backtrace = nil) # :nodoc: gem_make_out = write_gem_make_out output message = <<-EOF ERROR: Failed to build gem native extension. #{output} Gem files will remain installed in #{@gem_dir} for inspection. Results logged to #{gem_make_out} EOF raise Gem::Ext::BuildError, message, backtrace end def build_extension(extension, dest_path) # :nodoc: results = [] builder = builder_for(extension) extension_dir = File.expand_path File.join(@gem_dir, File.dirname(extension)) lib_dir = File.join @spec.full_gem_path, @spec.raw_require_paths.first begin FileUtils.mkdir_p dest_path results = builder.build(extension, dest_path, results, @build_args, lib_dir, extension_dir) verbose { results.join("\n") } write_gem_make_out results.join "\n" rescue StandardError => e results << e.message build_error(results.join("\n"), $@) end end ## # Builds extensions. Valid types of extensions are extconf.rb files, # configure scripts and rakefiles or mkrf_conf files. def build_extensions return if @spec.extensions.empty? if @build_args.empty? say "Building native extensions. This could take a while..." else say "Building native extensions with: '#{@build_args.join " "}'" say "This could take a while..." end dest_path = @spec.extension_dir require "fileutils" FileUtils.rm_f @spec.gem_build_complete_path @spec.extensions.each do |extension| break if @ran_rake build_extension extension, dest_path end FileUtils.touch @spec.gem_build_complete_path end ## # Writes +output+ to gem_make.out in the extension install directory. def write_gem_make_out(output) # :nodoc: destination = File.join @spec.extension_dir, "gem_make.out" FileUtils.mkdir_p @spec.extension_dir File.open destination, "wb" do |io| io.puts output end destination end end PK! KM77!rubygems/ext/configure_builder.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd) unless File.exist?(File.join(configure_dir, "Makefile")) cmd = ["sh", "./configure", "--prefix=#{dest_path}", *args] run cmd, results, class_name, configure_dir end make dest_path, results, configure_dir results end end PK!z rubygems/ext/ext_conf_builder.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd) require "fileutils" require "tempfile" tmp_dest = Dir.mktmpdir(".gem.", extension_dir) # Some versions of `mktmpdir` return absolute paths, which will break make # if the paths contain spaces. # # As such, we convert to a relative path. tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir) destdir = ENV["DESTDIR"] begin cmd = ruby << File.basename(extension) cmd.push(*args) run(cmd, results, class_name, extension_dir) do |s, r| mkmf_log = File.join(extension_dir, "mkmf.log") if File.exist? mkmf_log unless s.success? r << "To see why this extension failed to compile, please check" \ " the mkmf.log which can be found here:\n" r << " " + File.join(dest_path, "mkmf.log") + "\n" end FileUtils.mv mkmf_log, dest_path end end ENV["DESTDIR"] = nil make dest_path, results, extension_dir, tmp_dest_relative full_tmp_dest = File.join(extension_dir, tmp_dest_relative) # TODO: remove in RubyGems 4 if Gem.install_extension_in_lib && lib_dir FileUtils.mkdir_p lib_dir entries = Dir.entries(full_tmp_dest) - %w[. ..] entries = entries.map {|entry| File.join full_tmp_dest, entry } FileUtils.cp_r entries, lib_dir, remove_destination: true end FileUtils::Entry_.new(full_tmp_dest).traverse do |ent| destent = ent.class.new(dest_path, ent.rel) destent.exist? || FileUtils.mv(ent.path, destent.path) end make dest_path, results, extension_dir, tmp_dest_relative, ["clean"] ensure ENV["DESTDIR"] = destdir end results ensure FileUtils.rm_rf tmp_dest if tmp_dest end def self.get_relative_path(path, base) path[0..base.length - 1] = "." if path.start_with?(base) path end end PK! 0 1rubygems/gemcutter_utilities/webauthn_listener.rbnu[# frozen_string_literal: true require_relative "webauthn_listener/response" ## # The WebauthnListener class retrieves an OTP after a user successfully WebAuthns with the Gem host. # An instance opens a socket using the TCPServer instance given and listens for a request from the Gem host. # The request should be a GET request to the root path and contains the OTP code in the form # of a query parameter `code`. The listener will return the code which will be used as the OTP for # API requests. # # Types of responses sent by the listener after receiving a request: # - 200 OK: OTP code was successfully retrieved # - 204 No Content: If the request was an OPTIONS request # - 400 Bad Request: If the request did not contain a query parameter `code` # - 404 Not Found: The request was not to the root path # - 405 Method Not Allowed: OTP code was not retrieved because the request was not a GET/OPTIONS request # # Example usage: # # thread = Gem::WebauthnListener.listener_thread("https://rubygems.example", server) # thread.join # otp = thread[:otp] # error = thread[:error] # module Gem::GemcutterUtilities class WebauthnListener attr_reader :host def initialize(host) @host = host end def self.listener_thread(host, server) Thread.new do thread = Thread.current thread.abort_on_exception = true thread.report_on_exception = false thread[:otp] = new(host).wait_for_otp_code(server) rescue Gem::WebauthnVerificationError => e thread[:error] = e ensure server.close end end def wait_for_otp_code(server) loop do socket = server.accept request_line = socket.gets method, req_uri, _protocol = request_line.split(" ") req_uri = Gem::URI.parse(req_uri) responder = SocketResponder.new(socket) unless root_path?(req_uri) responder.send(NotFoundResponse.for(host)) raise Gem::WebauthnVerificationError, "Page at #{req_uri.path} not found." end case method.upcase when "OPTIONS" responder.send(NoContentResponse.for(host)) next # will be GET when "GET" if otp = parse_otp_from_uri(req_uri) responder.send(OkResponse.for(host)) return otp end responder.send(BadRequestResponse.for(host)) raise Gem::WebauthnVerificationError, "Did not receive OTP from #{host}." else responder.send(MethodNotAllowedResponse.for(host)) raise Gem::WebauthnVerificationError, "Invalid HTTP method #{method.upcase} received." end end end private def root_path?(uri) uri.path == "/" end def parse_otp_from_uri(uri) require "cgi" return if uri.query.nil? CGI.parse(uri.query).dig("code", 0) end class SocketResponder def initialize(socket) @socket = socket end def send(response) @socket.print response.to_s @socket.close end end end end PK!L] /rubygems/gemcutter_utilities/webauthn_poller.rbnu[# frozen_string_literal: true ## # The WebauthnPoller class retrieves an OTP after a user successfully WebAuthns. An instance # polls the Gem host for the OTP code. The polling request (api/v1/webauthn_verification//status.json) # is sent to the Gem host every 5 seconds and will timeout after 5 minutes. If the status field in the json response # is "success", the code field will contain the OTP code. # # Example usage: # # thread = Gem::WebauthnPoller.poll_thread( # {}, # "RubyGems.org", # "https://rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY", # { email: "email@example.com", password: "password" } # ) # thread.join # otp = thread[:otp] # error = thread[:error] # module Gem::GemcutterUtilities class WebauthnPoller include Gem::GemcutterUtilities TIMEOUT_IN_SECONDS = 300 attr_reader :options, :host def initialize(options, host) @options = options @host = host end def self.poll_thread(options, host, webauthn_url, credentials) Thread.new do thread = Thread.current thread.abort_on_exception = true thread.report_on_exception = false thread[:otp] = new(options, host).poll_for_otp(webauthn_url, credentials) rescue Gem::WebauthnVerificationError, Gem::Timeout::Error => e thread[:error] = e end end def poll_for_otp(webauthn_url, credentials) Gem::Timeout.timeout(TIMEOUT_IN_SECONDS) do loop do response = webauthn_verification_poll_response(webauthn_url, credentials) raise Gem::WebauthnVerificationError, response.message unless response.is_a?(Gem::Net::HTTPSuccess) require "json" parsed_response = JSON.parse(response.body) case parsed_response["status"] when "pending" sleep 5 when "success" return parsed_response["code"] else raise Gem::WebauthnVerificationError, parsed_response.fetch("message", "Invalid response from server") end end end end private def webauthn_verification_poll_response(webauthn_url, credentials) webauthn_token = %r{(?<=\/)[^\/]+(?=$)}.match(webauthn_url)[0] rubygems_api_request(:get, "api/v1/webauthn_verification/#{webauthn_token}/status.json") do |request| if credentials.empty? request.add_field "Authorization", api_key elsif credentials[:identifier] && credentials[:password] request.basic_auth credentials[:identifier], credentials[:password] else raise Gem::WebauthnVerificationError, "Provided missing credentials" end end end end end PK!I :rubygems/gemcutter_utilities/webauthn_listener/response.rbnu[# frozen_string_literal: true ## # The WebauthnListener Response class is used by the WebauthnListener to create # responses to be sent to the Gem host. It creates a Gem::Net::HTTPResponse instance # when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`. # Gem::Net::HTTPResponse instances cannot be directly sent over a socket. # # Types of response classes: # - OkResponse # - NoContentResponse # - BadRequestResponse # - NotFoundResponse # - MethodNotAllowedResponse # # Example usage: # # server = TCPServer.new(0) # socket = server.accept # # response = OkResponse.for("https://rubygems.example") # socket.print response.to_s # socket.close # module Gem::GemcutterUtilities class WebauthnListener class Response attr_reader :http_response def self.for(host) new(host) end def initialize(host) @host = host build_http_response end def to_s status_line = "HTTP/#{@http_response.http_version} #{@http_response.code} #{@http_response.message}\r\n" headers = @http_response.to_hash.map {|header, value| "#{header}: #{value.join(", ")}\r\n" }.join + "\r\n" body = @http_response.body ? "#{@http_response.body}\n" : "" status_line + headers + body end private # Must be implemented in subclasses def code raise NotImplementedError end def reason_phrase raise NotImplementedError end def body; end def build_http_response response_class = Gem::Net::HTTPResponse::CODE_TO_OBJ[code.to_s] @http_response = response_class.new("1.1", code, reason_phrase) @http_response.instance_variable_set(:@read, true) add_connection_header add_access_control_headers add_body end def add_connection_header @http_response["connection"] = "close" end def add_access_control_headers @http_response["access-control-allow-origin"] = @host @http_response["access-control-allow-methods"] = "POST" @http_response["access-control-allow-headers"] = %w[Content-Type Authorization x-csrf-token] end def add_body return unless body @http_response["content-type"] = "text/plain; charset=utf-8" @http_response["content-length"] = body.bytesize @http_response.instance_variable_set(:@body, body) end end class OkResponse < Response private def code 200 end def reason_phrase "OK" end def body "success" end end class NoContentResponse < Response private def code 204 end def reason_phrase "No Content" end end class BadRequestResponse < Response private def code 400 end def reason_phrase "Bad Request" end def body "missing code parameter" end end class NotFoundResponse < Response private def code 404 end def reason_phrase "Not Found" end end class MethodNotAllowedResponse < Response private def code 405 end def reason_phrase "Method Not Allowed" end def add_access_control_headers super @http_response["allow"] = %w[GET OPTIONS] end end end end PK!U౤#rubygems/commands/unpack_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../security_option" require_relative "../remote_fetcher" require_relative "../package" # forward-declare module Gem::Security # :nodoc: class Policy # :nodoc: end end class Gem::Commands::UnpackCommand < Gem::Command include Gem::VersionOption include Gem::SecurityOption def initialize require "fileutils" super "unpack", "Unpack an installed gem to the current directory", version: Gem::Requirement.default, target: Dir.pwd add_option("--target=DIR", "target directory for unpacking") do |value, options| options[:target] = value end add_option("--spec", "unpack the gem specification") do |_value, options| options[:spec] = true end add_security_option add_version_option end def arguments # :nodoc: "GEMNAME name of gem to unpack" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}'" end def description <<-EOF The unpack command allows you to examine the contents of a gem or modify them to help diagnose a bug. You can add the contents of the unpacked gem to the load path using the RUBYLIB environment variable or -I: $ gem unpack my_gem Unpacked gem: '.../my_gem-1.0' [edit my_gem-1.0/lib/my_gem.rb] $ ruby -Imy_gem-1.0/lib -S other_program You can repackage an unpacked gem using the build command. See the build command help for an example. EOF end def usage # :nodoc: "#{program_name} GEMNAME" end #-- # TODO: allow, e.g., 'gem unpack rake-0.3.1'. Find a general solution for # this, so that it works for uninstall as well. (And check other commands # at the same time.) def execute security_policy = options[:security_policy] get_all_gem_names.each do |name| dependency = Gem::Dependency.new name, options[:version] path = get_path dependency unless path alert_error "Gem '#{name}' not installed nor fetchable." next end if @options[:spec] spec, metadata = Gem::Package.raw_spec(path, security_policy) if metadata.nil? alert_error "--spec is unsupported on '#{name}' (old format gem)" next end spec_file = File.basename spec.spec_file FileUtils.mkdir_p @options[:target] if @options[:target] destination = if @options[:target] File.join @options[:target], spec_file else spec_file end File.open destination, "w" do |io| io.write metadata end else basename = File.basename path, ".gem" target_dir = File.expand_path basename, options[:target] package = Gem::Package.new path, security_policy package.extract_files target_dir say "Unpacked gem: '#{target_dir}'" end end end ## # # Find cached filename in Gem.path. Returns nil if the file cannot be found. # #-- # TODO: see comments in get_path() about general service. def find_in_cache(filename) Gem.path.each do |path| this_path = File.join(path, "cache", filename) return this_path if File.exist? this_path end nil end ## # Return the full path to the cached gem file matching the given # name and version requirement. Returns 'nil' if no match. # # Example: # # get_path 'rake', '> 0.4' # "/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem" # get_path 'rake', '< 0.1' # nil # get_path 'rak' # nil (exact name required) #-- # TODO: This should be refactored so that it's a general service. I don't # think any of our existing classes are the right place though. Just maybe # 'Cache'? # # TODO: It just uses Gem.dir for now. What's an easy way to get the list of # source directories? def get_path(dependency) return dependency.name if /\.gem$/i.match?(dependency.name) specs = dependency.matching_specs selected = specs.max_by(&:version) return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless selected return unless /^#{selected.name}$/i.match?(dependency.name) # We expect to find (basename).gem in the 'cache' directory. Furthermore, # the name match must be exact (ignoring case). path = find_in_cache File.basename selected.cache_file return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless path path end end PK!R"rubygems/commands/stale_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::StaleCommand < Gem::Command def initialize super("stale", "List gems along with access times") end def description # :nodoc: <<-EOF The stale command lists the latest access time for all the files in your installed gems. You can use this command to discover gems and gem versions you are no longer using. EOF end def usage # :nodoc: program_name.to_s end def execute gem_to_atime = {} Gem::Specification.each do |spec| name = spec.full_name Dir["#{spec.full_gem_path}/**/*.*"].each do |file| next if File.directory?(file) stat = File.stat(file) gem_to_atime[name] ||= stat.atime gem_to_atime[name] = stat.atime if gem_to_atime[name] < stat.atime end end gem_to_atime.sort_by {|_, atime| atime }.each do |name, atime| say "#{name} at #{atime.strftime "%c"}" end end end PK!X9ll&rubygems/commands/uninstall_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../uninstaller" require "fileutils" ## # Gem uninstaller command line tool # # See `gem help uninstall` class Gem::Commands::UninstallCommand < Gem::Command include Gem::VersionOption def initialize super "uninstall", "Uninstall gems from the local repository", version: Gem::Requirement.default, user_install: true, check_dev: false, vendor: false add_option("-a", "--[no-]all", "Uninstall all matching versions") do |value, options| options[:all] = value end add_option("-I", "--[no-]ignore-dependencies", "Ignore dependency requirements while", "uninstalling") do |value, options| options[:ignore] = value end add_option("-D", "--[no-]check-development", "Check development dependencies while uninstalling", "(default: false)") do |value, options| options[:check_dev] = value end add_option("-x", "--[no-]executables", "Uninstall applicable executables without", "confirmation") do |value, options| options[:executables] = value end add_option("-i", "--install-dir DIR", "Directory to uninstall gem from") do |value, options| options[:install_dir] = File.expand_path(value) end add_option("-n", "--bindir DIR", "Directory to remove executables from") do |value, options| options[:bin_dir] = File.expand_path(value) end add_option("--[no-]user-install", "Uninstall from user's home directory", "in addition to GEM_HOME.") do |value, options| options[:user_install] = value end add_option("--[no-]format-executable", "Assume executable names match Ruby's prefix and suffix.") do |value, options| options[:format_executable] = value end add_option("--[no-]force", "Uninstall all versions of the named gems", "ignoring dependencies") do |value, options| options[:force] = value end add_option("--[no-]abort-on-dependent", "Prevent uninstalling gems that are", "depended on by other gems.") do |value, options| options[:abort_on_dependent] = value end add_version_option add_platform_option add_option("--vendor", "Uninstall gem from the vendor directory.", "Only for use by gem repackagers.") do |_value, options| unless Gem.vendor_dir raise Gem::OptionParser::InvalidOption.new "your platform is not supported" end alert_warning "Use your OS package manager to uninstall vendor gems" options[:vendor] = true options[:install_dir] = Gem.vendor_dir end end def arguments # :nodoc: "GEMNAME name of gem to uninstall" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}' --no-force " \ "--user-install" end def description # :nodoc: <<-EOF The uninstall command removes a previously installed gem. RubyGems will ask for confirmation if you are attempting to uninstall a gem that is a dependency of an existing gem. You can use the --ignore-dependencies option to skip this check. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def check_version # :nodoc: if options[:version] != Gem::Requirement.default && get_all_gem_names.size > 1 alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" terminate_interaction 1 end end def execute check_version # Consider only gem specifications installed at `--install-dir` Gem::Specification.dirs = options[:install_dir] if options[:install_dir] if options[:all] && !options[:args].empty? uninstall_specific elsif options[:all] uninstall_all else uninstall_specific end end def uninstall_all specs = Gem::Specification.reject(&:default_gem?) specs.each do |spec| options[:version] = spec.version uninstall_gem spec.name end alert "Uninstalled all gems in #{options[:install_dir] || Gem.dir}" end def uninstall_specific deplist = Gem::DependencyList.new original_gem_version = {} get_all_gem_names_and_versions.each do |name, version| original_gem_version[name] = version || options[:version] gem_specs = Gem::Specification.find_all_by_name(name, original_gem_version[name]) if gem_specs.empty? say("Gem '#{name}' is not installed") else gem_specs.reject!(&:default_gem?) if gem_specs.size > 1 gem_specs.each do |spec| deplist.add spec end end end deps = deplist.strongly_connected_components.flatten.reverse gems_to_uninstall = {} deps.each do |dep| if original_gem_version[dep.name] == Gem::Requirement.default next if gems_to_uninstall[dep.name] gems_to_uninstall[dep.name] = true else options[:version] = dep.version end uninstall_gem(dep.name) end end def uninstall_gem(gem_name) uninstall(gem_name) rescue Gem::GemNotInHomeException => e spec = e.spec alert("In order to remove #{spec.name}, please execute:\n" \ "\tgem uninstall #{spec.name} --install-dir=#{spec.base_dir}") rescue Gem::UninstallError => e spec = e.spec alert_error("Error: unable to successfully uninstall '#{spec.name}' which is " \ "located at '#{spec.full_gem_path}'. This is most likely because" \ "the current user does not have the appropriate permissions") terminate_interaction 1 end def uninstall(gem_name) Gem::Uninstaller.new(gem_name, options).uninstall end end PK! !rubygems/commands/rdoc_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../rdoc" require "fileutils" class Gem::Commands::RdocCommand < Gem::Command include Gem::VersionOption def initialize super "rdoc", "Generates RDoc for pre-installed gems", version: Gem::Requirement.default, include_rdoc: false, include_ri: true, overwrite: false add_option("--all", "Generate RDoc/RI documentation for all", "installed gems") do |value, options| options[:all] = value end add_option("--[no-]rdoc", "Generate RDoc HTML") do |value, options| options[:include_rdoc] = value end add_option("--[no-]ri", "Generate RI data") do |value, options| options[:include_ri] = value end add_option("--[no-]overwrite", "Overwrite installed documents") do |value, options| options[:overwrite] = value end add_version_option end def arguments # :nodoc: "GEMNAME gem to generate documentation for (unless --all)" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}' --ri --no-overwrite" end def description # :nodoc: <<-DESC The rdoc command builds documentation for installed gems. By default only documentation is built using rdoc, but additional types of documentation may be built through rubygems plugins and the Gem.post_installs hook. Use --overwrite to force rebuilding of documentation. DESC end def usage # :nodoc: "#{program_name} [args]" end def execute specs = if options[:all] Gem::Specification.to_a else get_all_gem_names.map do |name| Gem::Specification.find_by_name name, options[:version] end.flatten.uniq end if specs.empty? alert_error "No matching gems found" terminate_interaction 1 end specs.each do |spec| doc = Gem::RDoc.new spec, options[:include_rdoc], options[:include_ri] doc.force = options[:overwrite] if options[:overwrite] FileUtils.rm_rf File.join(spec.doc_dir, "ri") FileUtils.rm_rf File.join(spec.doc_dir, "rdoc") end doc.generate end end end PK!]h&< "rubygems/commands/build_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../gemspec_helpers" require_relative "../package" require_relative "../version_option" class Gem::Commands::BuildCommand < Gem::Command include Gem::VersionOption include Gem::GemspecHelpers def initialize super "build", "Build a gem from a gemspec" add_platform_option add_option "--force", "skip validation of the spec" do |_value, options| options[:force] = true end add_option "--strict", "consider warnings as errors when validating the spec" do |_value, options| options[:strict] = true end add_option "-o", "--output FILE", "output gem with the given filename" do |value, options| options[:output] = value end add_option "-C PATH", "Run as if gem build was started in instead of the current working directory." do |value, options| options[:build_path] = value end deprecate_option "-C", version: "4.0", extra_msg: "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead" end def arguments # :nodoc: "GEMSPEC_FILE gemspec file name to build a gem for" end def description # :nodoc: <<-EOF The build command allows you to create a gem from a ruby gemspec. The best way to build a gem is to use a Rakefile and the Gem::PackageTask which ships with RubyGems. The gemspec can either be created by hand or extracted from an existing gem with gem spec: $ gem unpack my_gem-1.0.gem Unpacked gem: '.../my_gem-1.0' $ gem spec my_gem-1.0.gem --ruby > my_gem-1.0/my_gem-1.0.gemspec $ cd my_gem-1.0 [edit gem contents] $ gem build my_gem-1.0.gemspec Gems can be saved to a specified filename with the output option: $ gem build my_gem-1.0.gemspec --output=release.gem EOF end def usage # :nodoc: "#{program_name} GEMSPEC_FILE" end def execute if build_path = options[:build_path] Dir.chdir(build_path) { build_gem } return end build_gem end private def build_gem gemspec = resolve_gem_name if gemspec build_package(gemspec) else alert_error error_message terminate_interaction(1) end end def build_package(gemspec) spec = Gem::Specification.load(gemspec) if spec Gem::Package.build( spec, options[:force], options[:strict], options[:output] ) else alert_error "Error loading gemspec. Aborting." terminate_interaction 1 end end def resolve_gem_name return find_gemspec unless gem_name if File.exist?(gem_name) gem_name else find_gemspec("#{gem_name}.gemspec") || find_gemspec(gem_name) end end def error_message if gem_name "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}" else "Couldn't find a gemspec file in #{Dir.pwd}" end end def gem_name get_one_optional_argument end end PK!D^dl "rubygems/commands/fetch_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" class Gem::Commands::FetchCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize defaults = { suggest_alternate: true, version: Gem::Requirement.default, } super "fetch", "Download a gem and place it in the current directory", defaults add_bulk_threshold_option add_proxy_option add_source_option add_clear_sources_option add_version_option add_platform_option add_prerelease_option add_option "--[no-]suggestions", "Suggest alternates when gems are not found" do |value, options| options[:suggest_alternate] = value end end def arguments # :nodoc: "GEMNAME name of gem to download" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}'" end def description # :nodoc: <<-EOF The fetch command fetches gem files that can be stored for later use or unpacked to examine their contents. See the build command help for an example of unpacking a gem, modifying it, then repackaging it. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def check_version # :nodoc: if options[:version] != Gem::Requirement.default && get_all_gem_names.size > 1 alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" terminate_interaction 1 end end def execute check_version exit_code = fetch_gems terminate_interaction exit_code end private def fetch_gems exit_code = 0 version = options[:version] platform = Gem.platforms.last gem_names = get_all_gem_names_and_versions gem_names.each do |gem_name, gem_version| gem_version ||= version dep = Gem::Dependency.new gem_name, gem_version dep.prerelease = options[:prerelease] suppress_suggestions = !options[:suggest_alternate] specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep if platform filtered = specs_and_sources.select {|s,| s.platform == platform } specs_and_sources = filtered unless filtered.empty? end spec, source = specs_and_sources.max_by {|s,| s } if spec.nil? show_lookup_failure gem_name, gem_version, errors, suppress_suggestions, options[:domain] exit_code |= 2 next end source.download spec say "Downloaded #{spec.full_name}" end exit_code end end PK!&&$rubygems/commands/install_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../install_update_options" require_relative "../dependency_installer" require_relative "../local_remote_options" require_relative "../validator" require_relative "../version_option" require_relative "../update_suggestion" ## # Gem installer command line tool # # See `gem help install` class Gem::Commands::InstallCommand < Gem::Command attr_reader :installed_specs # :nodoc: include Gem::VersionOption include Gem::LocalRemoteOptions include Gem::InstallUpdateOptions include Gem::UpdateSuggestion def initialize defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ format_executable: false, lock: true, suggest_alternate: true, version: Gem::Requirement.default, without_groups: [], }) defaults.merge!(install_update_options) super "install", "Install a gem into the local repository", defaults add_install_update_options add_local_remote_options add_platform_option add_version_option add_prerelease_option "to be installed. (Only for listed gems)" @installed_specs = [] end def arguments # :nodoc: "GEMNAME name of gem to install" end def defaults_str # :nodoc: "--both --version '#{Gem::Requirement.default}' --no-force\n" \ "--install-dir #{Gem.dir} --lock\n" + install_update_defaults_str end def description # :nodoc: <<-EOF The install command installs local or remote gem into a gem repository. For gems with executables ruby installs a wrapper file into the executable directory by default. This can be overridden with the --no-wrappers option. The wrapper allows you to choose among alternate gem versions using _version_. For example `rake _0.7.3_ --version` will run rake version 0.7.3 if a newer version is also installed. Gem Dependency Files ==================== RubyGems can install a consistent set of gems across multiple environments using `gem install -g` when a gem dependencies file (gem.deps.rb, Gemfile or Isolate) is present. If no explicit file is given RubyGems attempts to find one in the current directory. When the RUBYGEMS_GEMDEPS environment variable is set to a gem dependencies file the gems from that file will be activated at startup time. Set it to a specific filename or to "-" to have RubyGems automatically discover the gem dependencies file by walking up from the current directory. NOTE: Enabling automatic discovery on multiuser systems can lead to execution of arbitrary code when used from directories outside your control. Extension Install Failures ========================== If an extension fails to compile during gem installation the gem specification is not written out, but the gem remains unpacked in the repository. You may need to specify the path to the library's headers and libraries to continue. You can do this by adding a -- between RubyGems' options and the extension's build options: $ gem install some_extension_gem [build fails] Gem files will remain installed in \\ /path/to/gems/some_extension_gem-1.0 for inspection. Results logged to /path/to/gems/some_extension_gem-1.0/gem_make.out $ gem install some_extension_gem -- --with-extension-lib=/path/to/lib [build succeeds] $ gem list some_extension_gem *** LOCAL GEMS *** some_extension_gem (1.0) $ If you correct the compilation errors by editing the gem files you will need to write the specification by hand. For example: $ gem install some_extension_gem [build fails] Gem files will remain installed in \\ /path/to/gems/some_extension_gem-1.0 for inspection. Results logged to /path/to/gems/some_extension_gem-1.0/gem_make.out $ [cd /path/to/gems/some_extension_gem-1.0] $ [edit files or what-have-you and run make] $ gem spec ../../cache/some_extension_gem-1.0.gem --ruby > \\ ../../specifications/some_extension_gem-1.0.gemspec $ gem list some_extension_gem *** LOCAL GEMS *** some_extension_gem (1.0) $ Command Alias ========================== You can use `i` command instead of `install`. $ gem i GEMNAME EOF end def usage # :nodoc: "#{program_name} [options] GEMNAME [GEMNAME ...] -- --build-flags" end def check_version # :nodoc: if options[:version] != Gem::Requirement.default && get_all_gem_names.size > 1 alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" terminate_interaction 1 end end def execute if options.include? :gemdeps install_from_gemdeps return # not reached end @installed_specs = [] ENV.delete "GEM_PATH" if options[:install_dir].nil? check_version load_hooks exit_code = install_gems show_installed say update_suggestion if eligible_for_update? terminate_interaction exit_code end def install_from_gemdeps # :nodoc: require_relative "../request_set" rs = Gem::RequestSet.new specs = rs.install_from_gemdeps options do |req, inst| s = req.full_spec if inst say "Installing #{s.name} (#{s.version})" else say "Using #{s.name} (#{s.version})" end end @installed_specs = specs terminate_interaction end def install_gem(name, version) # :nodoc: return if options[:conservative] && !Gem::Dependency.new(name, version).matching_specs.empty? req = Gem::Requirement.create(version) dinst = Gem::DependencyInstaller.new options request_set = dinst.resolve_dependencies name, req if options[:explain] say "Gems to install:" request_set.sorted_requests.each do |activation_request| say " #{activation_request.full_name}" end else @installed_specs.concat request_set.install options end show_install_errors dinst.errors end def install_gems # :nodoc: exit_code = 0 get_all_gem_names_and_versions.each do |gem_name, gem_version| gem_version ||= options[:version] domain = options[:domain] domain = :local unless options[:suggest_alternate] suppress_suggestions = (domain == :local) begin install_gem gem_name, gem_version rescue Gem::InstallError => e alert_error "Error installing #{gem_name}:\n\t#{e.message}" exit_code |= 1 rescue Gem::UnsatisfiableDependencyError => e show_lookup_failure e.name, e.version, e.errors, suppress_suggestions, "'#{gem_name}' (#{gem_version})" exit_code |= 2 end end exit_code end ## # Loads post-install hooks def load_hooks # :nodoc: if options[:install_as_default] require_relative "../install_default_message" else require_relative "../install_message" end require_relative "../rdoc" end def show_install_errors(errors) # :nodoc: return unless errors errors.each do |x| next unless Gem::SourceFetchProblem === x require_relative "../uri" msg = "Unable to pull data from '#{Gem::Uri.redact(x.source.uri)}': #{x.error.message}" alert_warning msg end end def show_installed # :nodoc: return if @installed_specs.empty? gems = @installed_specs.length == 1 ? "gem" : "gems" say "#{@installed_specs.length} #{gems} installed" end end PK!w=FF%rubygems/commands/contents_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" class Gem::Commands::ContentsCommand < Gem::Command include Gem::VersionOption def initialize super "contents", "Display the contents of the installed gems", specdirs: [], lib_only: false, prefix: true, show_install_dir: false add_version_option add_option("--all", "Contents for all gems") do |all, options| options[:all] = all end add_option("-s", "--spec-dir a,b,c", Array, "Search for gems under specific paths") do |spec_dirs, options| options[:specdirs] = spec_dirs end add_option("-l", "--[no-]lib-only", "Only return files in the Gem's lib_dirs") do |lib_only, options| options[:lib_only] = lib_only end add_option("--[no-]prefix", "Don't include installed path prefix") do |prefix, options| options[:prefix] = prefix end add_option("--[no-]show-install-dir", "Show only the gem install dir") do |show, options| options[:show_install_dir] = show end @path_kind = nil @spec_dirs = nil @version = nil end def arguments # :nodoc: "GEMNAME name of gem to list contents for" end def defaults_str # :nodoc: "--no-lib-only --prefix" end def description # :nodoc: <<-EOF The contents command lists the files in an installed gem. The listing can be given as full file names, file names without the installed directory prefix or only the files that are requireable. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def execute @version = options[:version] || Gem::Requirement.default @spec_dirs = specification_directories @path_kind = path_description @spec_dirs names = gem_names names.each do |name| found = if options[:show_install_dir] gem_install_dir name else gem_contents name end terminate_interaction 1 unless found || names.length > 1 end end def files_in(spec) if spec.default_gem? files_in_default_gem spec else files_in_gem spec end end def files_in_gem(spec) gem_path = spec.full_gem_path extra = "/{#{spec.require_paths.join ","}}" if options[:lib_only] glob = "#{gem_path}#{extra}/**/*" prefix_re = %r{#{Regexp.escape(gem_path)}/} Dir[glob].map do |file| [gem_path, file.sub(prefix_re, "")] end end def files_in_default_gem(spec) spec.files.map do |file| if file.start_with?("#{spec.bindir}/") [RbConfig::CONFIG["bindir"], file.delete_prefix("#{spec.bindir}/")] else gem spec.name, spec.version require_path = spec.require_paths.find do |path| file.start_with?("#{path}/") end requirable_part = file.delete_prefix("#{require_path}/") resolve = $LOAD_PATH.resolve_feature_path(requirable_part)&.last next unless resolve [resolve.delete_suffix(requirable_part), requirable_part] end end.compact end def gem_contents(name) spec = spec_for name return false unless spec files = files_in spec show_files files true end def gem_install_dir(name) spec = spec_for name return false unless spec say spec.gem_dir true end def gem_names # :nodoc: if options[:all] Gem::Specification.map(&:name) else get_all_gem_names end end def path_description(spec_dirs) # :nodoc: if spec_dirs.empty? "default gem paths" else "specified path" end end def show_files(files) files.sort.each do |prefix, basename| absolute_path = File.join(prefix, basename) next if File.directory? absolute_path if options[:prefix] say absolute_path else say basename end end end def spec_for(name) spec = Gem::Specification.find_all_by_name(name, @version).first return spec if spec say "Unable to find gem '#{name}' in #{@path_kind}" if Gem.configuration.verbose say "\nDirectories searched:" @spec_dirs.sort.each {|dir| say dir } end nil end def specification_directories # :nodoc: options[:specdirs].map do |i| [i, File.join(i, "specifications")] end.flatten end end PK!}$rubygems/commands/cleanup_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../dependency_list" require_relative "../uninstaller" class Gem::Commands::CleanupCommand < Gem::Command def initialize super "cleanup", "Clean up old versions of installed gems", force: false, install_dir: Gem.dir, check_dev: true add_option("-n", "-d", "--dry-run", "Do not uninstall gems") do |_value, options| options[:dryrun] = true end add_option(:Deprecated, "--dryrun", "Do not uninstall gems") do |_value, options| options[:dryrun] = true end deprecate_option("--dryrun", extra_msg: "Use --dry-run instead") add_option("-D", "--[no-]check-development", "Check development dependencies while uninstalling", "(default: true)") do |value, options| options[:check_dev] = value end add_option("--[no-]user-install", "Cleanup in user's home directory instead", "of GEM_HOME.") do |value, options| options[:user_install] = value end @candidate_gems = nil @default_gems = [] @full = nil @gems_to_cleanup = nil @primary_gems = nil end def arguments # :nodoc: "GEMNAME name of gem to cleanup" end def defaults_str # :nodoc: "--no-dry-run" end def description # :nodoc: <<-EOF The cleanup command removes old versions of gems from GEM_HOME that are not required to meet a dependency. If a gem is installed elsewhere in GEM_PATH the cleanup command won't delete it. If no gems are named all gems in GEM_HOME are cleaned. EOF end def usage # :nodoc: "#{program_name} [GEMNAME ...]" end def execute say "Cleaning up installed gems..." if options[:args].empty? done = false last_set = nil until done do clean_gems this_set = @gems_to_cleanup.map(&:full_name).sort done = this_set.empty? || last_set == this_set last_set = this_set end else clean_gems end say "Clean up complete" verbose do skipped = @default_gems.map(&:full_name) "Skipped default gems: #{skipped.join ", "}" end end def clean_gems get_primary_gems get_candidate_gems get_gems_to_cleanup @full = Gem::DependencyList.from_specs deplist = Gem::DependencyList.new @gems_to_cleanup.each {|spec| deplist.add spec } deps = deplist.strongly_connected_components.flatten deps.reverse_each do |spec| uninstall_dep spec end end def get_candidate_gems @candidate_gems = if options[:args].empty? Gem::Specification.to_a else options[:args].map do |gem_name| Gem::Specification.find_all_by_name gem_name end.flatten end end def get_gems_to_cleanup gems_to_cleanup = @candidate_gems.select do |spec| @primary_gems[spec.name].version != spec.version end default_gems, gems_to_cleanup = gems_to_cleanup.partition(&:default_gem?) uninstall_from = options[:user_install] ? Gem.user_dir : Gem.dir gems_to_cleanup = gems_to_cleanup.select do |spec| spec.base_dir == uninstall_from end @default_gems += default_gems @default_gems.uniq! @gems_to_cleanup = gems_to_cleanup.uniq end def get_primary_gems @primary_gems = {} Gem::Specification.each do |spec| if @primary_gems[spec.name].nil? || @primary_gems[spec.name].version < spec.version @primary_gems[spec.name] = spec end end end def uninstall_dep(spec) return unless @full.ok_to_remove?(spec.full_name, options[:check_dev]) if options[:dryrun] say "Dry Run Mode: Would uninstall #{spec.full_name}" return end say "Attempting to uninstall #{spec.full_name}" uninstall_options = { executables: false, version: "= #{spec.version}", } uninstall_options[:user_install] = Gem.user_dir == spec.base_dir uninstaller = Gem::Uninstaller.new spec.name, uninstall_options begin uninstaller.uninstall rescue Gem::DependencyRemovalException, Gem::InstallError, Gem::GemNotInHomeException, Gem::FilePermissionError => e say "Unable to uninstall #{spec.full_name}:" say "\t#{e.class}: #{e.message}" end end end PK!L$$!rubygems/commands/cert_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../security" class Gem::Commands::CertCommand < Gem::Command def initialize super "cert", "Manage RubyGems certificates and signing settings", add: [], remove: [], list: [], build: [], sign: [] add_option("-a", "--add CERT", "Add a trusted certificate.") do |cert_file, options| options[:add] << open_cert(cert_file) end add_option("-l", "--list [FILTER]", "List trusted certificates where the", "subject contains FILTER") do |filter, options| filter ||= "" options[:list] << filter end add_option("-r", "--remove FILTER", "Remove trusted certificates where the", "subject contains FILTER") do |filter, options| options[:remove] << filter end add_option("-b", "--build EMAIL_ADDR", "Build private key and self-signed", "certificate for EMAIL_ADDR") do |email_address, options| options[:build] << email_address end add_option("-C", "--certificate CERT", "Signing certificate for --sign") do |cert_file, options| options[:issuer_cert] = open_cert(cert_file) options[:issuer_cert_file] = cert_file end add_option("-K", "--private-key KEY", "Key for --sign or --build") do |key_file, options| options[:key] = open_private_key(key_file) end add_option("-A", "--key-algorithm ALGORITHM", "Select which key algorithm to use for --build") do |algorithm, options| options[:key_algorithm] = algorithm end add_option("-s", "--sign CERT", "Signs CERT with the key from -K", "and the certificate from -C") do |cert_file, options| raise Gem::OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless File.file? cert_file options[:sign] << cert_file end add_option("-d", "--days NUMBER_OF_DAYS", "Days before the certificate expires") do |days, options| options[:expiration_length_days] = days.to_i end add_option("-R", "--re-sign", "Re-signs the certificate from -C with the key from -K") do |resign, options| options[:resign] = resign end end def add_certificate(certificate) # :nodoc: Gem::Security.trust_dir.trust_cert certificate say "Added '#{certificate.subject}'" end def check_openssl return if Gem::HAVE_OPENSSL alert_error "OpenSSL library is required for the cert command" terminate_interaction 1 end def open_cert(certificate_file) check_openssl OpenSSL::X509::Certificate.new File.read certificate_file rescue Errno::ENOENT raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: does not exist" rescue OpenSSL::X509::CertificateError raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: invalid X509 certificate" end def open_private_key(key_file) check_openssl passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"] key = OpenSSL::PKey.read File.read(key_file), passphrase raise Gem::OptionParser::InvalidArgument, "#{key_file}: private key not found" unless key.private? key rescue Errno::ENOENT raise Gem::OptionParser::InvalidArgument, "#{key_file}: does not exist" rescue OpenSSL::PKey::PKeyError, ArgumentError raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key" end def execute check_openssl options[:add].each do |certificate| add_certificate certificate end options[:remove].each do |filter| remove_certificates_matching filter end options[:list].each do |filter| list_certificates_matching filter end options[:build].each do |email| build email end if options[:resign] re_sign_cert( options[:issuer_cert], options[:issuer_cert_file], options[:key] ) end sign_certificates unless options[:sign].empty? end def build(email) unless valid_email?(email) raise Gem::CommandLineError, "Invalid email address #{email}" end key, key_path = build_key cert_path = build_cert email, key say "Certificate: #{cert_path}" if key_path say "Private Key: #{key_path}" say "Don't forget to move the key file to somewhere private!" end end def build_cert(email, key) # :nodoc: expiration_length_days = options[:expiration_length_days] || Gem.configuration.cert_expiration_length_days cert = Gem::Security.create_cert_email( email, key, (Gem::Security::ONE_DAY * expiration_length_days) ) Gem::Security.write cert, "gem-public_cert.pem" end def build_key # :nodoc: return options[:key] if options[:key] passphrase = ask_for_password "Passphrase for your Private Key:" say "\n" passphrase_confirmation = ask_for_password "Please repeat the passphrase for your Private Key:" say "\n" raise Gem::CommandLineError, "Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM key = Gem::Security.create_key(algorithm) key_path = Gem::Security.write key, "gem-private_key.pem", 0o600, passphrase [key, key_path] end def certificates_matching(filter) return enum_for __method__, filter unless block_given? Gem::Security.trusted_certificates.select do |certificate, _| subject = certificate.subject.to_s subject.downcase.index filter end.sort_by do |certificate, _| certificate.subject.to_a.map {|name, data,| [name, data] } end.each do |certificate, path| yield certificate, path end end def description # :nodoc: <<-EOF The cert command manages signing keys and certificates for creating signed gems. Your signing certificate and private key are typically stored in ~/.gem/gem-public_cert.pem and ~/.gem/gem-private_key.pem respectively. To build a certificate for signing gems: gem cert --build you@example If you already have an RSA key, or are creating a new certificate for an existing key: gem cert --build you@example --private-key /path/to/key.pem If you wish to trust a certificate you can add it to the trust list with: gem cert --add /path/to/cert.pem You can list trusted certificates with: gem cert --list or: gem cert --list cert_subject_substring If you wish to remove a previously trusted certificate: gem cert --remove cert_subject_substring To sign another gem author's certificate: gem cert --sign /path/to/other_cert.pem For further reading on signing gems see `ri Gem::Security`. EOF end def list_certificates_matching(filter) # :nodoc: certificates_matching filter do |certificate, _| # this could probably be formatted more gracefully say certificate.subject.to_s end end def load_default_cert cert_file = File.join Gem.default_cert_path cert = File.read cert_file options[:issuer_cert] = OpenSSL::X509::Certificate.new cert rescue Errno::ENOENT alert_error \ "--certificate not specified and ~/.gem/gem-public_cert.pem does not exist" terminate_interaction 1 rescue OpenSSL::X509::CertificateError alert_error \ "--certificate not specified and ~/.gem/gem-public_cert.pem is not valid" terminate_interaction 1 end def load_default_key key_file = File.join Gem.default_key_path key = File.read key_file passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"] options[:key] = OpenSSL::PKey.read key, passphrase rescue Errno::ENOENT alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem does not exist" terminate_interaction 1 rescue OpenSSL::PKey::PKeyError alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem is not valid" terminate_interaction 1 end def load_defaults # :nodoc: load_default_cert unless options[:issuer_cert] load_default_key unless options[:key] end def remove_certificates_matching(filter) # :nodoc: certificates_matching filter do |certificate, path| FileUtils.rm path say "Removed '#{certificate.subject}'" end end def sign(cert_file) cert = File.read cert_file cert = OpenSSL::X509::Certificate.new cert permissions = File.stat(cert_file).mode & 0o777 issuer_cert = options[:issuer_cert] issuer_key = options[:key] cert = Gem::Security.sign cert, issuer_key, issuer_cert Gem::Security.write cert, cert_file, permissions end def sign_certificates # :nodoc: load_defaults unless options[:sign].empty? options[:sign].each do |cert_file| sign cert_file end end def re_sign_cert(cert, cert_path, private_key) Gem::Security::Signer.re_sign_cert(cert, cert_path, private_key) do |expired_cert_path, new_expired_cert_path| alert("Your certificate #{expired_cert_path} has been re-signed") alert("Your expired certificate will be located at: #{new_expired_cert_path}") end end private def valid_email?(email) # It's simple, but is all we need email =~ /\A.+@.+\z/ end end PK!v((!rubygems/commands/help_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::HelpCommand < Gem::Command # :stopdoc: EXAMPLES = <<-EOF Some examples of 'gem' usage. * Install 'rake', either from local directory or remote server: gem install rake * Install 'rake', only from remote server: gem install rake --remote * Install 'rake', but only version 0.3.1, even if dependencies are not met, and into a user-specific directory: gem install rake --version 0.3.1 --force --user-install * List local gems whose name begins with 'D': gem list D * List local and remote gems whose name contains 'log': gem search log --both * List only remote gems whose name contains 'log': gem search log --remote * Uninstall 'rake': gem uninstall rake * Create a gem: See https://guides.rubygems.org/make-your-own-gem/ * See information about RubyGems: gem environment * Update all gems on your system: gem update * Update your local version of RubyGems gem update --system EOF GEM_DEPENDENCIES = <<-EOF A gem dependencies file allows installation of a consistent set of gems across multiple environments. The RubyGems implementation is designed to be compatible with Bundler's Gemfile format. You can see additional documentation on the format at: https://bundler.io RubyGems automatically looks for these gem dependencies files: * gem.deps.rb * Gemfile * Isolate These files are looked up automatically using `gem install -g`, or you can specify a custom file. When the RUBYGEMS_GEMDEPS environment variable is set to a gem dependencies file the gems from that file will be activated at startup time. Set it to a specific filename or to "-" to have RubyGems automatically discover the gem dependencies file by walking up from the current directory. You can also activate gem dependencies at program startup using Gem.use_gemdeps. NOTE: Enabling automatic discovery on multiuser systems can lead to execution of arbitrary code when used from directories outside your control. Gem Dependencies ================ Use #gem to declare which gems you directly depend upon: gem 'rake' To depend on a specific set of versions: gem 'rake', '~> 10.3', '>= 10.3.2' RubyGems will require the gem name when activating the gem using the RUBYGEMS_GEMDEPS environment variable or Gem::use_gemdeps. Use the require: option to override this behavior if the gem does not have a file of that name or you don't want to require those files: gem 'my_gem', require: 'other_file' To prevent RubyGems from requiring any files use: gem 'my_gem', require: false To load dependencies from a .gemspec file: gemspec RubyGems looks for the first .gemspec file in the current directory. To override this use the name: option: gemspec name: 'specific_gem' To look in a different directory use the path: option: gemspec name: 'specific_gem', path: 'gemspecs' To depend on a gem unpacked into a local directory: gem 'modified_gem', path: 'vendor/modified_gem' To depend on a gem from git: gem 'private_gem', git: 'git@my.company.example:private_gem.git' To depend on a gem from github: gem 'private_gem', github: 'my_company/private_gem' To depend on a gem from a github gist: gem 'bang', gist: '1232884' Git, github and gist support the ref:, branch: and tag: options to specify a commit reference or hash, branch or tag respectively to use for the gem. Setting the submodules: option to true for git, github and gist dependencies causes fetching of submodules when fetching the repository. You can depend on multiple gems from a single repository with the git method: git 'https://github.com/rails/rails.git' do gem 'activesupport' gem 'activerecord' end Gem Sources =========== RubyGems uses the default sources for regular `gem install` for gem dependencies files. Unlike bundler, you do need to specify a source. You can override the sources used for downloading gems with: source 'https://gem_server.example' You may specify multiple sources. Unlike bundler the prepend: option is not supported. Sources are used in-order, to prepend a source place it at the front of the list. Gem Platform ============ You can restrict gem dependencies to specific platforms with the #platform and #platforms methods: platform :ruby_21 do gem 'debugger' end See the bundler Gemfile manual page for a list of platforms supported in a gem dependencies file.: https://bundler.io/v2.5/man/gemfile.5.html Ruby Version and Engine Dependency ================================== You can specify the version, engine and engine version of ruby to use with your gem dependencies file. If you are not running the specified version RubyGems will raise an exception. To depend on a specific version of ruby: ruby '2.1.2' To depend on a specific ruby engine: ruby '1.9.3', engine: 'jruby' To depend on a specific ruby engine version: ruby '1.9.3', engine: 'jruby', engine_version: '1.7.11' Grouping Dependencies ===================== Gem dependencies may be placed in groups that can be excluded from install. Dependencies required for development or testing of your code may be excluded when installed in a production environment. A #gem dependency may be placed in a group using the group: option: gem 'minitest', group: :test To install dependencies from a gemfile without specific groups use the `--without` option for `gem install -g`: $ gem install -g --without test The group: option also accepts multiple groups if the gem fits in multiple categories. Multiple groups may be excluded during install by comma-separating the groups for `--without` or by specifying `--without` multiple times. The #group method can also be used to place gems in groups: group :test do gem 'minitest' gem 'minitest-emoji' end The #group method allows multiple groups. The #gemspec development dependencies are placed in the :development group by default. This may be overridden with the :development_group option: gemspec development_group: :other EOF PLATFORMS = <<-'EOF' RubyGems platforms are composed of three parts, a CPU, an OS, and a version. These values are taken from values in rbconfig.rb. You can view your current platform by running `gem environment`. RubyGems matches platforms as follows: * The CPU must match exactly unless one of the platforms has "universal" as the CPU or the local CPU starts with "arm" and the gem's CPU is exactly "arm" (for gems that support generic ARM architecture). * The OS must match exactly. * The versions must match exactly unless one of the versions is nil. For commands that install, uninstall and list gems, you can override what RubyGems thinks your platform is with the --platform option. The platform you pass must match "#{cpu}-#{os}" or "#{cpu}-#{os}-#{version}". On mswin platforms, the version is the compiler version, not the OS version. (Ruby compiled with VC6 uses "60" as the compiler version, VC8 uses "80".) For the ARM architecture, gems with a platform of "arm-linux" should run on a reasonable set of ARM CPUs and not depend on instructions present on a limited subset of the architecture. For example, the binary should run on platforms armv5, armv6hf, armv6l, armv7, etc. If you use the "arm-linux" platform please test your gem on a variety of ARM hardware before release to ensure it functions correctly. Example platforms: x86-freebsd # Any FreeBSD version on an x86 CPU universal-darwin-8 # Darwin 8 only gems that run on any CPU x86-mswin32-80 # Windows gems compiled with VC8 armv7-linux # Gem complied for an ARMv7 CPU running linux arm-linux # Gem compiled for any ARM CPU running linux When building platform gems, set the platform in the gem specification to Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's platform. EOF # NOTE: when updating also update Gem::Command::HELP SUBCOMMANDS = [ ["commands", :show_commands], ["options", Gem::Command::HELP], ["examples", EXAMPLES], ["gem_dependencies", GEM_DEPENDENCIES], ["platforms", PLATFORMS], ].freeze # :startdoc: def initialize super "help", "Provide help on the 'gem' command" @command_manager = Gem::CommandManager.instance end def usage # :nodoc: "#{program_name} ARGUMENT" end def execute arg = options[:args][0] _, help = SUBCOMMANDS.find do |command,| begins? command, arg end if help if Symbol === help send help else say help end return end if options[:help] show_help elsif arg show_command_help arg else say Gem::Command::HELP end end def show_commands # :nodoc: out = [] out << "GEM commands are:" out << nil margin_width = 4 desc_width = @command_manager.command_names.map(&:size).max + 4 summary_width = 80 - margin_width - desc_width wrap_indent = " " * (margin_width + desc_width) format = "#{" " * margin_width}%-#{desc_width}s%s" @command_manager.command_names.each do |cmd_name| command = @command_manager[cmd_name] next if command&.deprecated? summary = if command command.summary else "[No command found for #{cmd_name}]" end summary = wrap(summary, summary_width).split "\n" out << format(format, cmd_name, summary.shift) until summary.empty? do out << "#{wrap_indent}#{summary.shift}" end end out << nil out << "For help on a particular command, use 'gem help COMMAND'." out << nil out << "Commands may be abbreviated, so long as they are unambiguous." out << "e.g. 'gem i rake' is short for 'gem install rake'." say out.join("\n") end def show_command_help(command_name) # :nodoc: command_name = command_name.downcase possibilities = @command_manager.find_command_possibilities command_name if possibilities.size == 1 command = @command_manager[possibilities.first] command.invoke("--help") elsif possibilities.size > 1 alert_warning "Ambiguous command #{command_name} (#{possibilities.join(", ")})" else alert_warning "Unknown command #{command_name}. Try: gem help commands" end end end PK! 6}VV'rubygems/commands/dependency_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" class Gem::Commands::DependencyCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize super "dependency", "Show the dependencies of an installed gem", version: Gem::Requirement.default, domain: :local add_version_option add_platform_option add_prerelease_option add_option("-R", "--[no-]reverse-dependencies", "Include reverse dependencies in the output") do |value, options| options[:reverse_dependencies] = value end add_option("-p", "--pipe", "Pipe Format (name --version ver)") do |value, options| options[:pipe_format] = value end add_local_remote_options end def arguments # :nodoc: "REGEXP show dependencies for gems whose names start with REGEXP" end def defaults_str # :nodoc: "--local --version '#{Gem::Requirement.default}' --no-reverse-dependencies" end def description # :nodoc: <<-EOF The dependency commands lists which other gems a given gem depends on. For local gems only the reverse dependencies can be shown (which gems depend on the named gem). The dependency list can be displayed in a format suitable for piping for use with other commands. EOF end def usage # :nodoc: "#{program_name} REGEXP" end def fetch_remote_specs(name, requirement, prerelease) # :nodoc: fetcher = Gem::SpecFetcher.fetcher specs_type = prerelease ? :complete : :released ss = if name.nil? fetcher.detect(specs_type) { true } else fetcher.detect(specs_type) do |name_tuple| name === name_tuple.name && requirement.satisfied_by?(name_tuple.version) end end ss.map {|tuple, source| source.fetch_spec(tuple) } end def fetch_specs(name_pattern, requirement, prerelease) # :nodoc: specs = [] if local? specs.concat Gem::Specification.stubs.find_all {|spec| name_matches = name_pattern ? name_pattern =~ spec.name : true version_matches = requirement.satisfied_by?(spec.version) name_matches && version_matches }.map(&:to_spec) end specs.concat fetch_remote_specs name_pattern, requirement, prerelease if remote? ensure_specs specs specs.uniq.sort end def display_pipe(specs) # :nodoc: specs.each do |spec| next if spec.dependencies.empty? spec.dependencies.sort_by(&:name).each do |dep| say "#{dep.name} --version '#{dep.requirement}'" end end end def display_readable(specs, reverse) # :nodoc: response = String.new specs.each do |spec| response << print_dependencies(spec) unless reverse[spec.full_name].empty? response << " Used by\n" reverse[spec.full_name].each do |sp, dep| response << " #{sp} (#{dep})\n" end end response << "\n" end say response end def execute ensure_local_only_reverse_dependencies pattern = name_pattern options[:args] requirement = Gem::Requirement.new options[:version] specs = fetch_specs pattern, requirement, options[:prerelease] reverse = reverse_dependencies specs if options[:pipe_format] display_pipe specs else display_readable specs, reverse end end def ensure_local_only_reverse_dependencies # :nodoc: if options[:reverse_dependencies] && remote? && !local? alert_error "Only reverse dependencies for local gems are supported." terminate_interaction 1 end end def ensure_specs(specs) # :nodoc: return unless specs.empty? patterns = options[:args].join "," say "No gems found matching #{patterns} (#{options[:version]})" if Gem.configuration.verbose terminate_interaction 1 end def print_dependencies(spec, level = 0) # :nodoc: response = String.new response << " " * level + "Gem #{spec.full_name}\n" unless spec.dependencies.empty? spec.dependencies.sort_by(&:name).each do |dep| response << " " * level + " #{dep}\n" end end response end def reverse_dependencies(specs) # :nodoc: reverse = Hash.new {|h, k| h[k] = [] } return reverse unless options[:reverse_dependencies] specs.each do |spec| reverse[spec.full_name] = find_reverse_dependencies spec end reverse end ## # Returns an Array of [specification, dep] that are satisfied by +spec+. def find_reverse_dependencies(spec) # :nodoc: result = [] Gem::Specification.each do |sp| sp.dependencies.each do |dep| dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep if spec.name == dep.name && dep.requirement.satisfied_by?(spec.version) result << [sp.full_name, dep] end end end result end private def name_pattern(args) return if args.empty? if args.length == 1 && args.first =~ /\A(.*)(i)?\z/m flags = $2 ? Regexp::IGNORECASE : nil Regexp.new $1, flags else /\A#{Regexp.union(*args)}/ end end end PK!*d%rubygems/commands/pristine_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../package" require_relative "../installer" require_relative "../version_option" class Gem::Commands::PristineCommand < Gem::Command include Gem::VersionOption def initialize super "pristine", "Restores installed gems to pristine condition from files located in the gem cache", version: Gem::Requirement.default, extensions: true, extensions_set: false, all: false add_option("--all", "Restore all installed gems to pristine", "condition") do |value, options| options[:all] = value end add_option("--skip=gem_name", "used on --all, skip if name == gem_name") do |value, options| options[:skip] ||= [] options[:skip] << value end add_option("--[no-]extensions", "Restore gems with extensions", "in addition to regular gems") do |value, options| options[:extensions_set] = true options[:extensions] = value end add_option("--only-missing-extensions", "Only restore gems with missing extensions") do |value, options| options[:only_missing_extensions] = value end add_option("--only-executables", "Only restore executables") do |value, options| options[:only_executables] = value end add_option("--only-plugins", "Only restore plugins") do |value, options| options[:only_plugins] = value end add_option("-E", "--[no-]env-shebang", "Rewrite executables with a shebang", "of /usr/bin/env") do |value, options| options[:env_shebang] = value end add_option("-i", "--install-dir DIR", "Gem repository to get gems restored") do |value, options| options[:install_dir] = File.expand_path(value) end add_option("-n", "--bindir DIR", "Directory where executables are", "located") do |value, options| options[:bin_dir] = File.expand_path(value) end add_version_option("restore to", "pristine condition") end def arguments # :nodoc: "GEMNAME gem to restore to pristine condition (unless --all)" end def defaults_str # :nodoc: "--extensions" end def description # :nodoc: <<-EOF The pristine command compares an installed gem with the contents of its cached .gem file and restores any files that don't match the cached .gem's copy. If you have made modifications to an installed gem, the pristine command will revert them. All extensions are rebuilt and all bin stubs for the gem are regenerated after checking for modifications. If the cached gem cannot be found it will be downloaded. If --no-extensions is provided pristine will not attempt to restore a gem with an extension. If --extensions is given (but not --all or gem names) only gems with extensions will be restored. EOF end def usage # :nodoc: "#{program_name} [GEMNAME ...]" end def execute install_dir = options[:install_dir] specification_record = install_dir ? Gem::SpecificationRecord.from_path(install_dir) : Gem::Specification.specification_record specs = if options[:all] specification_record.map # `--extensions` must be explicitly given to pristine only gems # with extensions. elsif options[:extensions_set] && options[:extensions] && options[:args].empty? specification_record.select do |spec| spec.extensions && !spec.extensions.empty? end elsif options[:only_missing_extensions] specification_record.select(&:missing_extensions?) else get_all_gem_names.sort.map do |gem_name| specification_record.find_all_by_name(gem_name, options[:version]).reverse end.flatten end specs = specs.select {|spec| spec.platform == RUBY_ENGINE || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY } if specs.to_a.empty? raise Gem::Exception, "Failed to find gems #{options[:args]} #{options[:version]}" end say "Restoring gems to pristine condition..." specs.group_by(&:full_name_with_location).values.each do |grouped_specs| spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first unless only_executables_or_plugins? # Default gemspecs include changes provided by ruby-core installer that # can't currently be pristined (inclusion of compiled extension targets in # the file list). So stick to resetting executables if it's a default gem. options[:only_executables] = true if spec.default_gem? end if options.key? :skip if options[:skip].include? spec.name say "Skipped #{spec.full_name}, it was given through options" next end end unless spec.extensions.empty? || options[:extensions] || only_executables_or_plugins? say "Skipped #{spec.full_name_with_location}, it needs to compile an extension" next end gem = spec.cache_file unless File.exist?(gem) || only_executables_or_plugins? require_relative "../remote_fetcher" say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..." dep = Gem::Dependency.new spec.name, spec.version found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep if found.empty? say "Skipped #{spec.full_name}, it was not found from cache and remote sources" next end spec_candidate, source = found.first Gem::RemoteFetcher.fetcher.download spec_candidate, source.uri.to_s, spec.base_dir end env_shebang = if options.include? :env_shebang options[:env_shebang] else install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS["install"] install_defaults.to_s["--env-shebang"] end bin_dir = options[:bin_dir] if options[:bin_dir] installer_options = { wrappers: true, force: true, install_dir: install_dir || spec.base_dir, env_shebang: env_shebang, build_args: spec.build_args, bin_dir: bin_dir, } if options[:only_executables] installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin elsif options[:only_plugins] installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_plugins else installer = Gem::Installer.at(gem, installer_options) installer.install end say "Restored #{spec.full_name_with_location}" end end private def only_executables_or_plugins? options[:only_executables] || options[:only_plugins] end end PK!V[(rubygems/commands/environment_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::EnvironmentCommand < Gem::Command def initialize super "environment", "Display information about the RubyGems environment" end def arguments # :nodoc: args = <<-EOF home display the path where gems are installed. Aliases: gemhome, gemdir, GEM_HOME path display path used to search for gems. Aliases: gempath, GEM_PATH user_gemhome display the path where gems are installed when `--user-install` is given. Aliases: user_gemdir version display the gem format version remotesources display the remote gem servers platform display the supported gem platforms display everything EOF args.gsub(/^\s+/, "") end def description # :nodoc: <<-EOF The environment command lets you query rubygems for its configuration for use in shell scripts or as a debugging aid. The RubyGems environment can be controlled through command line arguments, gemrc files, environment variables and built-in defaults. Command line argument defaults and some RubyGems defaults can be set in a ~/.gemrc file for individual users and a gemrc in the SYSTEM CONFIGURATION DIRECTORY for all users. These files are YAML files with the following YAML keys: :sources: A YAML array of remote gem repositories to install gems from :verbose: Verbosity of the gem command. false, true, and :really are the levels :update_sources: Enable/disable automatic updating of repository metadata :backtrace: Print backtrace when RubyGems encounters an error :gempath: The paths in which to look for gems :disable_default_gem_server: Force specification of gem server host on push : A string containing arguments for the specified gem command Example: :verbose: false install: --no-wrappers update: --no-wrappers :disable_default_gem_server: true RubyGems' default local repository can be overridden with the GEM_PATH and GEM_HOME environment variables. GEM_HOME sets the default repository to install into. GEM_PATH allows multiple local repositories to be searched for gems. If you are behind a proxy server, RubyGems uses the HTTP_PROXY, HTTP_PROXY_USER and HTTP_PROXY_PASS environment variables to discover the proxy server. If you would like to push gems to a private gem server the RUBYGEMS_HOST environment variable can be set to the URI for that server. If you are packaging RubyGems all of RubyGems' defaults are in lib/rubygems/defaults.rb. You may override these in lib/rubygems/defaults/operating_system.rb EOF end def usage # :nodoc: "#{program_name} [arg]" end def execute out = String.new arg = options[:args][0] out << case arg when /^version/ then Gem::VERSION when /^gemdir/, /^gemhome/, /^home/, /^GEM_HOME/ then Gem.dir when /^gempath/, /^path/, /^GEM_PATH/ then Gem.path.join(File::PATH_SEPARATOR) when /^user_gemdir/, /^user_gemhome/ then Gem.user_dir when /^remotesources/ then Gem.sources.to_a.join("\n") when /^platform/ then Gem.platforms.join(File::PATH_SEPARATOR) when nil then show_environment else raise Gem::CommandLineError, "Unknown environment option [#{arg}]" end say out true end def add_path(out, path) path.each do |component| out << " - #{component}\n" end end def show_environment # :nodoc: out = "RubyGems Environment:\n".dup out << " - RUBYGEMS VERSION: #{Gem::VERSION}\n" out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]\n" out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n" out << " - USER INSTALLATION DIRECTORY: #{Gem.user_dir}\n" out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil? out << " - RUBY EXECUTABLE: #{Gem.ruby}\n" out << " - GIT EXECUTABLE: #{git_path}\n" out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n" out << " - SPEC CACHE DIRECTORY: #{Gem.spec_cache_dir}\n" out << " - SYSTEM CONFIGURATION DIRECTORY: #{Gem::ConfigFile::SYSTEM_CONFIG_PATH}\n" out << " - RUBYGEMS PLATFORMS:\n" Gem.platforms.each do |platform| out << " - #{platform}\n" end out << " - GEM PATHS:\n" out << " - #{Gem.dir}\n" gem_path = Gem.path.dup gem_path.delete Gem.dir add_path out, gem_path out << " - GEM CONFIGURATION:\n" Gem.configuration.each do |name, value| value = value.gsub(/./, "*") if name == "gemcutter_key" out << " - #{name.inspect} => #{value.inspect}\n" end out << " - REMOTE SOURCES:\n" Gem.sources.each do |s| out << " - #{s}\n" end out << " - SHELL PATH:\n" shell_path = ENV["PATH"].split(File::PATH_SEPARATOR) add_path out, shell_path out end private ## # Git binary path def git_path exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""] ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| exts.each do |ext| exe = File.join(path, "git#{ext}") return exe if File.executable?(exe) && !File.directory?(exe) end end nil end end PK!LTb#rubygems/commands/signin_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../gemcutter_utilities" class Gem::Commands::SigninCommand < Gem::Command include Gem::GemcutterUtilities def initialize super "signin", "Sign in to any gemcutter-compatible host. "\ "It defaults to https://rubygems.org" add_option("--host HOST", "Push to another gemcutter-compatible host") do |value, options| options[:host] = value end add_otp_option end def description # :nodoc: "The signin command executes host sign in for a push server (the default is"\ " https://rubygems.org). The host can be provided with the host flag or can"\ " be inferred from the provided gem. Host resolution matches the resolution"\ " strategy for the push command." end def usage # :nodoc: program_name end def execute sign_in options[:host] end end PK!9:4"rubygems/commands/query_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../query_utils" require_relative "../deprecate" class Gem::Commands::QueryCommand < Gem::Command extend Gem::Deprecate rubygems_deprecate_command include Gem::QueryUtils alias_method :warning_without_suggested_alternatives, :deprecation_warning def deprecation_warning warning_without_suggested_alternatives message = "It is recommended that you use `gem search` or `gem list` instead.\n" alert_warning message unless Gem::Deprecate.skip end def initialize(name = "query", summary = "Query gem information in local or remote repositories") super name, summary, domain: :local, details: false, versions: true, installed: nil, version: Gem::Requirement.default add_option("-n", "--name-matches REGEXP", "Name of gem(s) to query on matches the", "provided REGEXP") do |value, options| options[:name] = /#{value}/i end add_query_options end def description # :nodoc: <<-EOF The query command is the basis for the list and search commands. You should really use the list and search commands instead. This command is too hard to use. EOF end end PK!gpY *rubygems/commands/specification_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" require_relative "../package" class Gem::Commands::SpecificationCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize Gem.load_yaml super "specification", "Display gem specification (in yaml)", domain: :local, version: Gem::Requirement.default, format: :yaml add_version_option("examine") add_platform_option add_prerelease_option add_option("--all", "Output specifications for all versions of", "the gem") do |_value, options| options[:all] = true end add_option("--ruby", "Output ruby format") do |_value, options| options[:format] = :ruby end add_option("--yaml", "Output YAML format") do |_value, options| options[:format] = :yaml end add_option("--marshal", "Output Marshal format") do |_value, options| options[:format] = :marshal end add_local_remote_options end def arguments # :nodoc: <<-ARGS GEMFILE name of gem to show the gemspec for FIELD name of gemspec field to show ARGS end def defaults_str # :nodoc: "--local --version '#{Gem::Requirement.default}' --yaml" end def description # :nodoc: <<-EOF The specification command allows you to extract the specification from a gem for examination. The specification can be output in YAML, ruby or Marshal formats. Specific fields in the specification can be extracted in YAML format: $ gem spec rake summary --- Ruby based make-like utility. ... EOF end def usage # :nodoc: "#{program_name} [GEMFILE] [FIELD]" end def execute specs = [] gem = options[:args].shift unless gem raise Gem::CommandLineError, "Please specify a gem name or file on the command line" end case v = options[:version] when String req = Gem::Requirement.create v when Gem::Requirement req = v else raise Gem::CommandLineError, "Unsupported version type: '#{v}'" end if !req.none? && options[:all] alert_error "Specify --all or -v, not both" terminate_interaction 1 end if options[:all] dep = Gem::Dependency.new gem else dep = Gem::Dependency.new gem, req end field = get_one_optional_argument raise Gem::CommandLineError, "--ruby and FIELD are mutually exclusive" if field && options[:format] == :ruby if local? if File.exist? gem begin specs << Gem::Package.new(gem).spec rescue StandardError nil end end if specs.empty? specs.push(*dep.matching_specs) end end if remote? dep.prerelease = options[:prerelease] found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep specs.push(*found.map {|spec,| spec }) end if specs.empty? alert_error "No gem matching '#{dep}' found" terminate_interaction 1 end platform = get_platform_from_requirements(options) if platform specs = specs.select {|s| s.platform.to_s == platform } end unless options[:all] specs = [specs.max_by(&:version)] end specs.each do |s| s = s.send field if field say case options[:format] when :ruby then s.to_ruby when :marshal then Marshal.dump s else s.to_yaml end say "\n" end end end PK!Dp p "rubygems/commands/owner_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" require_relative "../text" class Gem::Commands::OwnerCommand < Gem::Command include Gem::Text include Gem::LocalRemoteOptions include Gem::GemcutterUtilities def description # :nodoc: <<-EOF The owner command lets you add and remove owners of a gem on a push server (the default is https://rubygems.org). Multiple owners can be added or removed at the same time, if the flag is given multiple times. The supported user identifiers are dependent on the push server. For rubygems.org, both e-mail and handle are supported, even though the user identifier field is called "email". The owner of a gem has the permission to push new versions, yank existing versions or edit the HTML page of the gem. Be careful of who you give push permission to. EOF end def arguments # :nodoc: "GEM gem to manage owners for" end def usage # :nodoc: "#{program_name} GEM" end def initialize super "owner", "Manage gem owners of a gem on the push server" add_proxy_option add_key_option add_otp_option defaults.merge! add: [], remove: [] add_option "-a", "--add NEW_OWNER", "Add an owner by user identifier" do |value, options| options[:add] << value end add_option "-r", "--remove OLD_OWNER", "Remove an owner by user identifier" do |value, options| options[:remove] << value end add_option "-h", "--host HOST", "Use another gemcutter-compatible host", " (e.g. https://rubygems.org)" do |value, options| options[:host] = value end end def execute @host = options[:host] sign_in(scope: get_owner_scope) name = get_one_gem_name add_owners name, options[:add] remove_owners name, options[:remove] show_owners name end def show_owners(name) Gem.load_yaml response = rubygems_api_request :get, "api/v1/gems/#{name}/owners.yaml" do |request| request.add_field "Authorization", api_key end with_response response do |resp| owners = Gem::SafeYAML.load clean_text(resp.body) say "Owners for gem: #{name}" owners.each do |owner| say "- #{owner["email"] || owner["handle"] || owner["id"]}" end end end def add_owners(name, owners) manage_owners :post, name, owners end def remove_owners(name, owners) manage_owners :delete, name, owners end def manage_owners(method, name, owners) owners.each do |owner| response = send_owner_request(method, name, owner) action = method == :delete ? "Removing" : "Adding" with_response response, "#{action} #{owner}" rescue Gem::WebauthnVerificationError => e raise e rescue StandardError # ignore early exits to allow for completing the iteration of all owners end end private def send_owner_request(method, name, owner) rubygems_api_request method, "api/v1/gems/#{name}/owners", scope: get_owner_scope(method: method) do |request| request.set_form_data "email" => owner request.add_field "Authorization", api_key end end def get_owner_scope(method: nil) if method == :post || options.any? && options[:add].any? :add_owner elsif method == :delete || options.any? && options[:remove].any? :remove_owner end end end PK!ZAQ #rubygems/commands/search_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../query_utils" class Gem::Commands::SearchCommand < Gem::Command include Gem::QueryUtils def initialize super "search", "Display remote gems whose name matches REGEXP", domain: :remote, details: false, versions: true, installed: nil, version: Gem::Requirement.default add_query_options end def arguments # :nodoc: "REGEXP regexp to search for in gem name" end def defaults_str # :nodoc: "--remote --no-details" end def description # :nodoc: <<-EOF The search command displays remote gems whose name matches the given regexp. The --details option displays additional details from the gem but will take a little longer to complete as it must download the information individually from the index. To list local gems use the list command. EOF end def usage # :nodoc: "#{program_name} [REGEXP]" end end PK!])[["rubygems/commands/which_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::WhichCommand < Gem::Command def initialize super "which", "Find the location of a library file you can require", search_gems_first: false, show_all: false add_option "-a", "--[no-]all", "show all matching files" do |show_all, options| options[:show_all] = show_all end add_option "-g", "--[no-]gems-first", "search gems before non-gems" do |gems_first, options| options[:search_gems_first] = gems_first end end def arguments # :nodoc: "FILE name of file to find" end def defaults_str # :nodoc: "--no-gems-first --no-all" end def description # :nodoc: <<-EOF The which command is like the shell which command and shows you where the file you wish to require lives. You can use the which command to help determine why you are requiring a version you did not expect or to look at the content of a file you are requiring to see why it does not behave as you expect. EOF end def execute found = true options[:args].each do |arg| arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, "") dirs = $LOAD_PATH spec = Gem::Specification.find_by_path arg if spec if options[:search_gems_first] dirs = spec.full_require_paths + $LOAD_PATH else dirs = $LOAD_PATH + spec.full_require_paths end end paths = find_paths arg, dirs if paths.empty? alert_error "Can't find Ruby library file or shared library #{arg}" found = false else say paths end end terminate_interaction 1 unless found end def find_paths(package_name, dirs) result = [] dirs.each do |dir| Gem.suffixes.each do |ext| full_path = File.join dir, "#{package_name}#{ext}" if File.exist?(full_path) && !File.directory?(full_path) result << full_path return result unless options[:show_all] end end end result end def usage # :nodoc: "#{program_name} FILE [FILE ...]" end end PK!ؿd d !rubygems/commands/yank_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" require_relative "../gemcutter_utilities" class Gem::Commands::YankCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption include Gem::GemcutterUtilities def description # :nodoc: <<-EOF The yank command permanently removes a gem you pushed to a server. Once you have pushed a gem several downloads will happen automatically via the webhooks. If you accidentally pushed passwords or other sensitive data you will need to change them immediately and yank your gem. EOF end def arguments # :nodoc: "GEM name of gem" end def usage # :nodoc: "#{program_name} -v VERSION [-p PLATFORM] [--key KEY_NAME] [--host HOST] GEM" end def initialize super "yank", "Remove a pushed gem from the index" add_version_option("remove") add_platform_option("remove") add_otp_option add_option("--host HOST", "Yank from another gemcutter-compatible host", " (e.g. https://rubygems.org)") do |value, options| options[:host] = value end add_key_option @host = nil end def execute @host = options[:host] sign_in @host, scope: get_yank_scope version = get_version_from_requirements(options[:version]) platform = get_platform_from_requirements(options) if version yank_gem(version, platform) else say "A version argument is required: #{usage}" terminate_interaction end end def yank_gem(version, platform) say "Yanking gem from #{host}..." args = [:delete, version, platform, "api/v1/gems/yank"] response = yank_api_request(*args) say response.body end private def yank_api_request(method, version, platform, api) name = get_one_gem_name response = rubygems_api_request(method, api, host, scope: get_yank_scope) do |request| request.add_field("Authorization", api_key) data = { "gem_name" => name, "version" => version, } data["platform"] = platform if platform request.set_form_data data end response end def get_version_from_requirements(requirements) requirements.requirements.first[1].version rescue StandardError nil end def get_yank_scope :yank_rubygem end end PK!Q BB!rubygems/commands/info_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../query_utils" class Gem::Commands::InfoCommand < Gem::Command include Gem::QueryUtils def initialize super "info", "Show information for the given gem", name: //, domain: :local, details: false, versions: true, installed: nil, version: Gem::Requirement.default add_query_options remove_option("-d") defaults[:details] = true defaults[:exact] = true end def description # :nodoc: "Info prints information about the gem such as name,"\ " description, website, license and installed paths" end def usage # :nodoc: "#{program_name} GEMNAME" end def arguments # :nodoc: "GEMNAME name of the gem to print information about" end def defaults_str "--local" end end PK!A !rubygems/commands/lock_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::LockCommand < Gem::Command def initialize super "lock", "Generate a lockdown list of gems", strict: false add_option "-s", "--[no-]strict", "fail if unable to satisfy a dependency" do |strict, options| options[:strict] = strict end end def arguments # :nodoc: "GEMNAME name of gem to lock\nVERSION version of gem to lock" end def defaults_str # :nodoc: "--no-strict" end def description # :nodoc: <<-EOF The lock command will generate a list of +gem+ statements that will lock down the versions for the gem given in the command line. It will specify exact versions in the requirements list to ensure that the gems loaded will always be consistent. A full recursive search of all effected gems will be generated. Example: gem lock rails-1.0.0 > lockdown.rb will produce in lockdown.rb: require "rubygems" gem 'rails', '= 1.0.0' gem 'rake', '= 0.7.0.1' gem 'activesupport', '= 1.2.5' gem 'activerecord', '= 1.13.2' gem 'actionpack', '= 1.11.2' gem 'actionmailer', '= 1.1.5' gem 'actionwebservice', '= 1.0.0' Just load lockdown.rb from your application to ensure that the current versions are loaded. Make sure that lockdown.rb is loaded *before* any other require statements. Notice that rails 1.0.0 only requires that rake 0.6.2 or better be used. Rake-0.7.0.1 is the most recent version installed that satisfies that, so we lock it down to the exact version. EOF end def usage # :nodoc: "#{program_name} GEMNAME-VERSION [GEMNAME-VERSION ...]" end def complain(message) if options[:strict] raise Gem::Exception, message else say "# #{message}" end end def execute say "require 'rubygems'" locked = {} pending = options[:args] until pending.empty? do full_name = pending.shift spec = Gem::Specification.load spec_path(full_name) if spec.nil? complain "Could not find gem #{full_name}, try using the full name" next end say "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name] locked[spec.name] = true spec.runtime_dependencies.each do |dep| next if locked[dep.name] candidates = dep.matching_specs if candidates.empty? complain "Unable to satisfy '#{dep}' from currently installed gems" else pending << candidates.last.full_name end end end end def spec_path(gem_full_name) gemspecs = Gem.path.map do |path| File.join path, "specifications", "#{gem_full_name}.gemspec" end gemspecs.find {|path| File.exist? path } end end PK! U1$rubygems/commands/signout_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::SignoutCommand < Gem::Command def initialize super "signout", "Sign out from all the current sessions." end def description # :nodoc: "The `signout` command is used to sign out from all current sessions,"\ " allowing you to sign in using a different set of credentials." end def usage # :nodoc: program_name end def execute credentials_path = Gem.configuration.credentials_path if !File.exist?(credentials_path) alert_error "You are not currently signed in." elsif !File.writable?(credentials_path) alert_error "File '#{Gem.configuration.credentials_path}' is read-only."\ " Please make sure it is writable." else Gem.configuration.unset_api_key! say "You have successfully signed out from all sessions." end end end PK!L!rubygems/commands/list_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../query_utils" ## # Searches for gems starting with the supplied argument. class Gem::Commands::ListCommand < Gem::Command include Gem::QueryUtils def initialize super "list", "Display local gems whose name matches REGEXP", domain: :local, details: false, versions: true, installed: nil, version: Gem::Requirement.default add_query_options end def arguments # :nodoc: "REGEXP regexp to look for in gem name" end def defaults_str # :nodoc: "--local --no-details" end def description # :nodoc: <<-EOF The list command is used to view the gems you have installed locally. The --details option displays additional details including the summary, the homepage, the author, the locations of different versions of the gem. To search for remote gems use the search command. EOF end def usage # :nodoc: "#{program_name} [REGEXP ...]" end end PK!d+rubygems/commands/generate_index_command.rbnu[# frozen_string_literal: true require_relative "../command" unless defined? Gem::Commands::GenerateIndexCommand class Gem::Commands::GenerateIndexCommand < Gem::Command module RubygemsTrampoline def description # :nodoc: <<~EOF The generate_index command has been moved to the rubygems-generate_index gem. EOF end def execute alert_error "Install the rubygems-generate_index gem for the generate_index command" end def invoke_with_build_args(args, build_args) name = "rubygems-generate_index" spec = begin Gem::Specification.find_by_name(name) rescue Gem::LoadError require "rubygems/dependency_installer" Gem.install(name, Gem::Requirement.default, Gem::DependencyInstaller::DEFAULT_OPTIONS).find {|s| s.name == name } end # remove the methods defined in this file so that the methods defined in the gem are used instead, # and without a method redefinition warning %w[description execute invoke_with_build_args].each do |method| RubygemsTrampoline.remove_method(method) end self.class.singleton_class.remove_method(:new) spec.activate Gem.load_plugin_files spec.matches_for_glob("rubygems_plugin#{Gem.suffix_pattern}") self.class.new.invoke_with_build_args(args, build_args) end end private_constant :RubygemsTrampoline # remove_method(:initialize) warns, but removing new does not warn def self.new command = allocate command.send(:initialize, "generate_index", "Generates the index files for a gem server directory (requires rubygems-generate_index)") command end prepend(RubygemsTrampoline) end end PK!ss#rubygems/commands/mirror_command.rbnu[# frozen_string_literal: true require_relative "../command" unless defined? Gem::Commands::MirrorCommand class Gem::Commands::MirrorCommand < Gem::Command def initialize super("mirror", "Mirror all gem files (requires rubygems-mirror)") begin Gem::Specification.find_by_name("rubygems-mirror").activate rescue Gem::LoadError # no-op end end def description # :nodoc: <<-EOF The mirror command has been moved to the rubygems-mirror gem. EOF end def execute alert_error "Install the rubygems-mirror gem for the mirror command" end end end PK!V- - "rubygems/commands/check_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../validator" require_relative "../doctor" class Gem::Commands::CheckCommand < Gem::Command include Gem::VersionOption def initialize super "check", "Check a gem repository for added or missing files", alien: true, doctor: false, dry_run: false, gems: true add_option("-a", "--[no-]alien", 'Report "unmanaged" or rogue files in the', "gem repository") do |value, options| options[:alien] = value end add_option("--[no-]doctor", "Clean up uninstalled gems and broken", "specifications") do |value, options| options[:doctor] = value end add_option("--[no-]dry-run", "Do not remove files, only report what", "would be removed") do |value, options| options[:dry_run] = value end add_option("--[no-]gems", "Check installed gems for problems") do |value, options| options[:gems] = value end add_version_option "check" end def check_gems say "Checking gems..." say gems = begin get_all_gem_names rescue StandardError [] end Gem::Validator.new.alien(gems).sort.each do |key, val| if val.empty? say "#{key} is error-free" if Gem.configuration.verbose else say "#{key} has #{val.size} problems" val.each do |error_entry| say " #{error_entry.path}:" say " #{error_entry.problem}" end end say end end def doctor say "Checking for files from uninstalled gems..." say Gem.path.each do |gem_repo| doctor = Gem::Doctor.new gem_repo, options[:dry_run] doctor.doctor end end def execute check_gems if options[:gems] doctor if options[:doctor] end def arguments # :nodoc: "GEMNAME name of gem to check" end def defaults_str # :nodoc: "--gems --alien" end def description # :nodoc: <<-EOF The check command can list and repair problems with installed gems and specifications and will clean up gems that have been partially uninstalled. EOF end def usage # :nodoc: "#{program_name} [OPTIONS] [GEMNAME ...]" end end PK!4``%rubygems/commands/outdated_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../spec_fetcher" require_relative "../version_option" class Gem::Commands::OutdatedCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize super "outdated", "Display all gems that need updates" add_local_remote_options add_platform_option end def description # :nodoc: <<-EOF The outdated command lists gems you may wish to upgrade to a newer version. You can check for dependency mismatches using the dependency command and update the gems with the update or install commands. EOF end def execute Gem::Specification.outdated_and_latest_version.each do |spec, remote_version| say "#{spec.name} (#{spec.version} < #{remote_version})" end end end PK!ʉ instead of the current working directory." do |value, options| options[:build_path] = value end end def arguments # :nodoc: "GEM_NAME gem name on gem server\n" \ "GEM_VERSION gem version you are attempting to rebuild" end def description # :nodoc: <<-EOF The rebuild command allows you to (attempt to) reproduce a build of a gem from a ruby gemspec. This command assumes the gemspec can be built with the `gem build` command. If you use any of `gem build`, `rake build`, or`rake release` in the build/release process for a gem, it is a potential candidate. You will need to match the RubyGems version used, since this is included in the Gem metadata. If the gem includes lockfiles (e.g. Gemfile.lock) and similar, it will require more effort to reproduce a build. For example, it might require more precisely matched versions of Ruby and/or Bundler to be used. EOF end def usage # :nodoc: "#{program_name} GEM_NAME GEM_VERSION" end def execute gem_name, gem_version = get_gem_name_and_version old_dir, new_dir = prep_dirs gem_filename = "#{gem_name}-#{gem_version}.gem" old_file = File.join(old_dir, gem_filename) new_file = File.join(new_dir, gem_filename) if options[:original_gem_file] FileUtils.copy_file(options[:original_gem_file], old_file) else download_gem(gem_name, gem_version, old_file) end rg_version = rubygems_version(old_file) unless rg_version == Gem::VERSION alert_error <<-EOF You need to use the same RubyGems version #{gem_name} v#{gem_version} was built with. #{gem_name} v#{gem_version} was built using RubyGems v#{rg_version}. Gem files include the version of RubyGems used to build them. This means in order to reproduce #{gem_filename}, you must also use RubyGems v#{rg_version}. You're using RubyGems v#{Gem::VERSION}. Please install RubyGems v#{rg_version} and try again. EOF terminate_interaction 1 end source_date_epoch = get_timestamp(old_file).to_s if build_path = options[:build_path] Dir.chdir(build_path) { build_gem(gem_name, source_date_epoch, new_file) } else build_gem(gem_name, source_date_epoch, new_file) end compare(source_date_epoch, old_file, new_file) end private def sha256(file) Digest::SHA256.hexdigest(Gem.read_binary(file)) end def get_timestamp(file) mtime = nil File.open(file, Gem.binary_mode) do |f| Gem::Package::TarReader.new(f) do |tar| mtime = tar.seek("metadata.gz") {|tf| tf.header.mtime } end end mtime end def compare(source_date_epoch, old_file, new_file) date = Time.at(source_date_epoch.to_i).strftime("%F %T %Z") old_hash = sha256(old_file) new_hash = sha256(new_file) say say "Built at: #{date} (#{source_date_epoch})" say "Original build saved to: #{old_file}" say "Reproduced build saved to: #{new_file}" say "Working directory: #{options[:build_path] || Dir.pwd}" say say "Hash comparison:" say " #{old_hash}\t#{old_file}" say " #{new_hash}\t#{new_file}" say if old_hash == new_hash say "SUCCESS - original and rebuild hashes matched" else say "FAILURE - original and rebuild hashes did not match" say if options[:diff] if system("diffoscope", old_file, new_file).nil? alert_error "error: could not find `diffoscope` executable" end else say "Pass --diff for more details (requires diffoscope to be installed)." end terminate_interaction 1 end end def prep_dirs rebuild_dir = Dir.mktmpdir("gem_rebuild") old_dir = File.join(rebuild_dir, "old") new_dir = File.join(rebuild_dir, "new") FileUtils.mkdir_p(old_dir) FileUtils.mkdir_p(new_dir) [old_dir, new_dir] end def get_gem_name_and_version args = options[:args] || [] if args.length == 2 gem_name, gem_version = args elsif args.length > 2 raise Gem::CommandLineError, "Too many arguments" else raise Gem::CommandLineError, "Expected GEM_NAME and GEM_VERSION arguments (gem rebuild GEM_NAME GEM_VERSION)" end [gem_name, gem_version] end def build_gem(gem_name, source_date_epoch, output_file) gemspec = options[:gemspec_file] || find_gemspec("#{gem_name}.gemspec") if gemspec build_package(gemspec, source_date_epoch, output_file) else alert_error error_message(gem_name) terminate_interaction(1) end end def build_package(gemspec, source_date_epoch, output_file) with_source_date_epoch(source_date_epoch) do spec = Gem::Specification.load(gemspec) if spec Gem::Package.build( spec, options[:force], options[:strict], output_file ) else alert_error "Error loading gemspec. Aborting." terminate_interaction 1 end end end def with_source_date_epoch(source_date_epoch) old_sde = ENV["SOURCE_DATE_EPOCH"] ENV["SOURCE_DATE_EPOCH"] = source_date_epoch.to_s yield ensure ENV["SOURCE_DATE_EPOCH"] = old_sde end def error_message(gem_name) if gem_name "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}" else "Couldn't find a gemspec file in #{Dir.pwd}" end end def download_gem(gem_name, gem_version, old_file) # This code was based loosely off the `gem fetch` command. version = "= #{gem_version}" dep = Gem::Dependency.new gem_name, version specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep # There should never be more than one item in specs_and_sources, # since we search for an exact version. spec, source = specs_and_sources[0] if spec.nil? show_lookup_failure gem_name, version, errors, options[:domain] terminate_interaction 1 end download_path = source.download spec FileUtils.move(download_path, old_file) say "Downloaded #{gem_name} version #{gem_version} as #{old_file}." end def rubygems_version(gem_file) Gem::Package.new(gem_file).spec.rubygems_version end end PK!i99!rubygems/commands/exec_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../dependency_installer" require_relative "../gem_runner" require_relative "../package" require_relative "../version_option" class Gem::Commands::ExecCommand < Gem::Command include Gem::VersionOption def initialize super "exec", "Run a command from a gem", { version: Gem::Requirement.default, } add_version_option add_prerelease_option "to be installed" add_option "-g", "--gem GEM", "run the executable from the given gem" do |value, options| options[:gem_name] = value end add_option(:"Install/Update", "--conservative", "Prefer the most recent installed version, ", "rather than the latest version overall") do |_value, options| options[:conservative] = true end end def arguments # :nodoc: "COMMAND the executable command to run" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}'" end def description # :nodoc: <<-EOF The exec command handles installing (if necessary) and running an executable from a gem, regardless of whether that gem is currently installed. The exec command can be thought of as a shortcut to running `gem install` and then the executable from the installed gem. For example, `gem exec rails new .` will run `rails new .` in the current directory, without having to manually run `gem install rails`. Additionally, the exec command ensures the most recent version of the gem is used (unless run with `--conservative`), and that the gem is not installed to the same gem path as user-installed gems. EOF end def usage # :nodoc: "#{program_name} [options --] COMMAND [args]" end def execute check_executable print_command if options[:gem_name] == "gem" && options[:executable] == "gem" set_gem_exec_install_paths Gem::GemRunner.new.run options[:args] return elsif options[:conservative] install_if_needed else install activate! end load! end private def handle_options(args) args = add_extra_args(args) check_deprecated_options(args) @options = Marshal.load Marshal.dump @defaults # deep copy parser.order!(args) do |v| # put the non-option back at the front of the list of arguments args.unshift(v) # stop parsing once we hit the first non-option, # so you can call `gem exec rails --version` and it prints the rails # version rather than rubygem's break end @options[:args] = args options[:executable], gem_version = extract_gem_name_and_version(options[:args].shift) options[:gem_name] ||= options[:executable] if gem_version if options[:version].none? options[:version] = Gem::Requirement.new(gem_version) else options[:version].concat [gem_version] end end if options[:prerelease] && !options[:version].prerelease? if options[:version].none? options[:version] = Gem::Requirement.default_prerelease else options[:version].concat [Gem::Requirement.default_prerelease] end end end def check_executable if options[:executable].nil? raise Gem::CommandLineError, "Please specify an executable to run (e.g. #{program_name} COMMAND)" end end def print_command verbose "running #{program_name} with:\n" opts = options.reject {|_, v| v.nil? || Array(v).empty? } max_length = opts.map {|k, _| k.size }.max opts.each do |k, v| next if v.nil? verbose "\t#{k.to_s.rjust(max_length)}: #{v}" end verbose "" end def install_if_needed activate! rescue Gem::MissingSpecError verbose "#{Gem::Dependency.new(options[:gem_name], options[:version])} not available locally, installing from remote" install activate! end def set_gem_exec_install_paths home = Gem.dir ENV["GEM_PATH"] = ([home] + Gem.path).join(File::PATH_SEPARATOR) ENV["GEM_HOME"] = home Gem.clear_paths end def install set_gem_exec_install_paths gem_name = options[:gem_name] gem_version = options[:version] install_options = options.merge( minimal_deps: false, wrappers: true ) suppress_always_install do dep_installer = Gem::DependencyInstaller.new install_options request_set = dep_installer.resolve_dependencies gem_name, gem_version verbose "Gems to install:" request_set.sorted_requests.each do |activation_request| verbose "\t#{activation_request.full_name}" end request_set.install install_options end Gem::Specification.reset rescue Gem::InstallError => e alert_error "Error installing #{gem_name}:\n\t#{e.message}" terminate_interaction 1 rescue Gem::GemNotFoundException => e show_lookup_failure e.name, e.version, e.errors, false terminate_interaction 2 rescue Gem::UnsatisfiableDependencyError => e show_lookup_failure e.name, e.version, e.errors, false, "'#{gem_name}' (#{gem_version})" terminate_interaction 2 end def activate! gem(options[:gem_name], options[:version]) Gem.finish_resolve verbose "activated #{options[:gem_name]} (#{Gem.loaded_specs[options[:gem_name]].version})" end def load! argv = ARGV.clone ARGV.replace options[:args] exe = executable = options[:executable] contains_executable = Gem.loaded_specs.values.select do |spec| spec.executables.include?(executable) end if contains_executable.any? {|s| s.name == executable } contains_executable.select! {|s| s.name == executable } end if contains_executable.empty? if (spec = Gem.loaded_specs[executable]) && (exe = spec.executable) contains_executable << spec else alert_error "Failed to load executable `#{executable}`," \ " are you sure the gem `#{options[:gem_name]}` contains it?" terminate_interaction 1 end end if contains_executable.size > 1 alert_error "Ambiguous which gem `#{executable}` should come from: " \ "the options are #{contains_executable.map(&:name)}, " \ "specify one via `-g`" terminate_interaction 1 end load Gem.activate_bin_path(contains_executable.first.name, exe, ">= 0.a") ensure ARGV.replace argv end def suppress_always_install name = :always_install cls = ::Gem::Resolver::InstallerSet method = cls.instance_method(name) cls.remove_method(name) cls.define_method(name) { [] } begin yield ensure cls.remove_method(name) cls.define_method(name, method) end end end PK!::JGG"rubygems/commands/setup_command.rbnu[# frozen_string_literal: true require_relative "../command" ## # Installs RubyGems itself. This command is ordinarily only available from a # RubyGems checkout or tarball. class Gem::Commands::SetupCommand < Gem::Command HISTORY_HEADER = %r{^#\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} VERSION_MATCHER = %r{^#\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} ENV_PATHS = %w[/usr/bin/env /bin/env].freeze def initialize super "setup", "Install RubyGems", format_executable: false, document: %w[ri], force: true, site_or_vendor: "sitelibdir", destdir: "", prefix: "", previous_version: "", regenerate_binstubs: true, regenerate_plugins: true add_option "--previous-version=VERSION", "Previous version of RubyGems", "Used for changelog processing" do |version, options| options[:previous_version] = version end add_option "--prefix=PREFIX", "Prefix path for installing RubyGems", "Will not affect gem repository location" do |prefix, options| options[:prefix] = File.expand_path prefix end add_option "--destdir=DESTDIR", "Root directory to install RubyGems into", "Mainly used for packaging RubyGems" do |destdir, options| options[:destdir] = File.expand_path destdir end add_option "--[no-]vendor", "Install into vendorlibdir not sitelibdir" do |vendor, options| options[:site_or_vendor] = vendor ? "vendorlibdir" : "sitelibdir" end add_option "--[no-]format-executable", "Makes `gem` match ruby", "If Ruby is ruby18, gem will be gem18" do |value, options| options[:format_executable] = value end add_option "--[no-]document [TYPES]", Array, "Generate documentation for RubyGems", "List the documentation types you wish to", "generate. For example: rdoc,ri" do |value, options| options[:document] = case value when nil then %w[rdoc ri] when false then [] else value end end add_option "--[no-]rdoc", "Generate RDoc documentation for RubyGems" do |value, options| if value options[:document] << "rdoc" else options[:document].delete "rdoc" end options[:document].uniq! end add_option "--[no-]ri", "Generate RI documentation for RubyGems" do |value, options| if value options[:document] << "ri" else options[:document].delete "ri" end options[:document].uniq! end add_option "--[no-]regenerate-binstubs", "Regenerate gem binstubs" do |value, options| options[:regenerate_binstubs] = value end add_option "--[no-]regenerate-plugins", "Regenerate gem plugins" do |value, options| options[:regenerate_plugins] = value end add_option "-f", "--[no-]force", "Forcefully overwrite binstubs" do |value, options| options[:force] = value end add_option("-E", "--[no-]env-shebang", "Rewrite executables with a shebang", "of /usr/bin/env") do |value, options| options[:env_shebang] = value end @verbose = nil end def check_ruby_version required_version = Gem::Requirement.new ">= 2.6.0" unless required_version.satisfied_by? Gem.ruby_version alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" terminate_interaction 1 end end def defaults_str # :nodoc: "--format-executable --document ri --regenerate-binstubs" end def description # :nodoc: <<-EOF Installs RubyGems itself. RubyGems installs RDoc for itself in GEM_HOME. By default this is: #{Gem.dir} If you prefer a different directory, set the GEM_HOME environment variable. RubyGems will install the gem command with a name matching ruby's prefix and suffix. If ruby was installed as `ruby18`, gem will be installed as `gem18`. By default, this RubyGems will install gem as: #{Gem.default_exec_format % "gem"} EOF end module MakeDirs def mkdir_p(path, **opts) super (@mkdirs ||= []) << path end end def execute @verbose = Gem.configuration.really_verbose check_ruby_version require "fileutils" if Gem.configuration.really_verbose extend FileUtils::Verbose else extend FileUtils end extend MakeDirs lib_dir, bin_dir = make_destination_dirs man_dir = generate_default_man_dir install_lib lib_dir install_executables bin_dir remove_old_bin_files bin_dir remove_old_lib_files lib_dir # Can be removed one we drop support for bundler 2.2.3 (the last version installing man files to man_dir) remove_old_man_files man_dir if man_dir && File.exist?(man_dir) install_default_bundler_gem bin_dir if mode = options[:dir_mode] @mkdirs.uniq! File.chmod(mode, @mkdirs) end say "RubyGems #{Gem::VERSION} installed" regenerate_binstubs(bin_dir) if options[:regenerate_binstubs] regenerate_plugins(bin_dir) if options[:regenerate_plugins] uninstall_old_gemcutter documentation_success = install_rdoc say if @verbose say "-" * 78 say end if options[:previous_version].empty? options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, "0") end options[:previous_version] = Gem::Version.new(options[:previous_version]) show_release_notes say say "-" * 78 say say "RubyGems installed the following executables:" say bin_file_names.map {|name| "\t#{name}\n" } say unless bin_file_names.grep(/#{File::SEPARATOR}gem$/) say "If `gem` was installed by a previous RubyGems installation, you may need" say "to remove it by hand." say end if documentation_success if options[:document].include? "rdoc" say "Rdoc documentation was installed. You may now invoke:" say " gem server" say "and then peruse beautifully formatted documentation for your gems" say "with your web browser." say "If you do not wish to install this documentation in the future, use the" say "--no-document flag, or set it as the default in your ~/.gemrc file. See" say "'gem help env' for details." say end if options[:document].include? "ri" say "Ruby Interactive (ri) documentation was installed. ri is kind of like man " say "pages for Ruby libraries. You may access it like this:" say " ri Classname" say " ri Classname.class_method" say " ri Classname#instance_method" say "If you do not wish to install this documentation in the future, use the" say "--no-document flag, or set it as the default in your ~/.gemrc file. See" say "'gem help env' for details." say end end end def install_executables(bin_dir) prog_mode = options[:prog_mode] || 0o755 executables = { "gem" => "exe" } executables.each do |tool, path| say "Installing #{tool} executable" if @verbose Dir.chdir path do bin_file = "gem" require "tmpdir" dest_file = target_bin_path(bin_dir, bin_file) bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" begin bin = File.readlines bin_file bin[0] = shebang File.open bin_tmp_file, "w" do |fp| fp.puts bin.join end install bin_tmp_file, dest_file, mode: prog_mode bin_file_names << dest_file ensure rm bin_tmp_file end next unless Gem.win_platform? begin bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat" File.open bin_cmd_file, "w" do |file| file.puts <<-TEXT @ECHO OFF IF NOT "%~f0" == "~f0" GOTO :WinNT @"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9 GOTO :EOF :WinNT @"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %* TEXT end install bin_cmd_file, "#{dest_file}.bat", mode: prog_mode ensure rm bin_cmd_file end end end end def shebang if options[:env_shebang] ruby_name = RbConfig::CONFIG["ruby_install_name"] @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } "#!#{@env_path} #{ruby_name}\n" else "#!#{Gem.ruby}\n" end end def install_lib(lib_dir) libs = { "RubyGems" => "lib" } libs["Bundler"] = "bundler/lib" libs.each do |tool, path| say "Installing #{tool}" if @verbose lib_files = files_in path Dir.chdir path do install_file_list(lib_files, lib_dir) end end end def install_rdoc gem_doc_dir = File.join Gem.dir, "doc" rubygems_name = "rubygems-#{Gem::VERSION}" rubygems_doc_dir = File.join gem_doc_dir, rubygems_name begin Gem.ensure_gem_subdirectories Gem.dir rescue SystemCallError # ignore end if File.writable?(gem_doc_dir) && (!File.exist?(rubygems_doc_dir) || File.writable?(rubygems_doc_dir)) say "Removing old RubyGems RDoc and ri" if @verbose Dir[File.join(Gem.dir, "doc", "rubygems-[0-9]*")].each do |dir| rm_rf dir end require_relative "../rdoc" return false unless defined?(Gem::RDoc) fake_spec = Gem::Specification.new "rubygems", Gem::VERSION def fake_spec.full_gem_path File.expand_path "../../..", __dir__ end generate_ri = options[:document].include? "ri" generate_rdoc = options[:document].include? "rdoc" rdoc = Gem::RDoc.new fake_spec, generate_rdoc, generate_ri rdoc.generate return true elsif @verbose say "Skipping RDoc generation, #{gem_doc_dir} not writable" say "Set the GEM_HOME environment variable if you want RDoc generated" end false end def install_default_bundler_gem(bin_dir) current_default_spec = Gem::Specification.default_stubs.find {|s| s.name == "bundler" } specs_dir = if current_default_spec && default_dir == Gem.default_dir Gem::Specification.remove_spec current_default_spec loaded_from = current_default_spec.loaded_from File.delete(loaded_from) File.dirname(loaded_from) else target_specs_dir = File.join(default_dir, "specifications", "default") mkdir_p target_specs_dir, mode: 0o755 target_specs_dir end new_bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") } full_name = new_bundler_spec.full_name gemspec_path = "#{full_name}.gemspec" default_spec_path = File.join(specs_dir, gemspec_path) Gem.write_binary(default_spec_path, new_bundler_spec.to_ruby) bundler_spec = Gem::Specification.load(default_spec_path) # Remove gemspec that was same version of vendored bundler. normal_gemspec = File.join(default_dir, "specifications", gemspec_path) if File.file? normal_gemspec File.delete normal_gemspec end # Remove gem files that were same version of vendored bundler. if File.directory? bundler_spec.gems_dir Dir.entries(bundler_spec.gems_dir). select {|default_gem| File.basename(default_gem) == full_name }. each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) } end require_relative "../installer" Dir.chdir("bundler") do built_gem = Gem::Package.build(new_bundler_spec) begin Gem::Installer.at( built_gem, env_shebang: options[:env_shebang], format_executable: options[:format_executable], force: options[:force], install_as_default: true, bin_dir: bin_dir, install_dir: default_dir, wrappers: true ).install ensure FileUtils.rm_f built_gem end end new_bundler_spec.executables.each {|executable| bin_file_names << target_bin_path(bin_dir, executable) } say "Bundler #{new_bundler_spec.version} installed" end def make_destination_dirs lib_dir, bin_dir = Gem.default_rubygems_dirs unless lib_dir lib_dir, bin_dir = generate_default_dirs end mkdir_p lib_dir, mode: 0o755 mkdir_p bin_dir, mode: 0o755 [lib_dir, bin_dir] end def generate_default_man_dir prefix = options[:prefix] if prefix.empty? man_dir = RbConfig::CONFIG["mandir"] return unless man_dir else man_dir = File.join prefix, "man" end prepend_destdir_if_present(man_dir) end def generate_default_dirs prefix = options[:prefix] site_or_vendor = options[:site_or_vendor] if prefix.empty? lib_dir = RbConfig::CONFIG[site_or_vendor] bin_dir = RbConfig::CONFIG["bindir"] else lib_dir = File.join prefix, "lib" bin_dir = File.join prefix, "bin" end [prepend_destdir_if_present(lib_dir), prepend_destdir_if_present(bin_dir)] end def files_in(dir) Dir.chdir dir do Dir.glob(File.join("**", "*"), File::FNM_DOTMATCH). select {|f| !File.directory?(f) } end end def remove_old_bin_files(bin_dir) old_bin_files = { "gem_mirror" => "gem mirror", "gem_server" => "gem server", "gemlock" => "gem lock", "gemri" => "ri", "gemwhich" => "gem which", "index_gem_repository.rb" => "gem generate_index", } old_bin_files.each do |old_bin_file, new_name| old_bin_path = File.join bin_dir, old_bin_file next unless File.exist? old_bin_path deprecation_message = "`#{old_bin_file}` has been deprecated. Use `#{new_name}` instead." File.open old_bin_path, "w" do |fp| fp.write <<-EOF #!#{Gem.ruby} abort "#{deprecation_message}" EOF end next unless Gem.win_platform? File.open "#{old_bin_path}.bat", "w" do |fp| fp.puts %(@ECHO.#{deprecation_message}) end end end def remove_old_lib_files(lib_dir) lib_dirs = { File.join(lib_dir, "rubygems") => "lib/rubygems" } lib_dirs[File.join(lib_dir, "bundler")] = "bundler/lib/bundler" lib_dirs.each do |old_lib_dir, new_lib_dir| lib_files = files_in(new_lib_dir) old_lib_files = files_in(old_lib_dir) to_remove = old_lib_files - lib_files gauntlet_rubygems = File.join(lib_dir, "gauntlet_rubygems.rb") to_remove << gauntlet_rubygems if File.exist? gauntlet_rubygems to_remove.delete_if do |file| file.start_with? "defaults" end remove_file_list(to_remove, old_lib_dir) end end def remove_old_man_files(old_man_dir) old_man1_dir = "#{old_man_dir}/man1" if File.exist?(old_man1_dir) man1_to_remove = Dir.chdir(old_man1_dir) { Dir["bundle*.1{,.txt,.ronn}"] } remove_file_list(man1_to_remove, old_man1_dir) end old_man5_dir = "#{old_man_dir}/man5" if File.exist?(old_man5_dir) man5_to_remove = Dir.chdir(old_man5_dir) { Dir["gemfile.5{,.txt,.ronn}"] } remove_file_list(man5_to_remove, old_man5_dir) end end def show_release_notes release_notes = File.join Dir.pwd, "CHANGELOG.md" release_notes = if File.exist? release_notes history = File.read release_notes history.force_encoding Encoding::UTF_8 text = history.split(HISTORY_HEADER) text.shift # correct an off-by-one generated by split version_lines = history.scan(HISTORY_HEADER) versions = history.scan(VERSION_MATCHER).flatten.map do |x| Gem::Version.new(x) end history_string = "" until versions.length == 0 || versions.shift <= options[:previous_version] do history_string += version_lines.shift + text.shift end history_string else "Oh-no! Unable to find release notes!" end say release_notes end def uninstall_old_gemcutter require_relative "../uninstaller" ui = Gem::Uninstaller.new("gemcutter", all: true, ignore: true, version: "< 0.4") ui.uninstall rescue Gem::InstallError end def regenerate_binstubs(bindir) require_relative "pristine_command" say "Regenerating binstubs" args = %w[--all --only-executables --silent] args << "--bindir=#{bindir}" args << "--install-dir=#{default_dir}" if options[:env_shebang] args << "--env-shebang" end command = Gem::Commands::PristineCommand.new command.invoke(*args) end def regenerate_plugins(bindir) require_relative "pristine_command" say "Regenerating plugins" args = %w[--all --only-plugins --silent] args << "--bindir=#{bindir}" args << "--install-dir=#{default_dir}" command = Gem::Commands::PristineCommand.new command.invoke(*args) end private def default_dir prefix = options[:prefix] if prefix.empty? dir = Gem.default_dir else dir = prefix end prepend_destdir_if_present(dir) end def prepend_destdir_if_present(path) destdir = options[:destdir] return path if destdir.empty? File.join(options[:destdir], path.gsub(/^[a-zA-Z]:/, "")) end def install_file_list(files, dest_dir) files.each do |file| install_file file, dest_dir end end def install_file(file, dest_dir) dest_file = File.join dest_dir, file dest_dir = File.dirname dest_file unless File.directory? dest_dir mkdir_p dest_dir, mode: 0o755 end install file, dest_file, mode: options[:data_mode] || 0o644 end def remove_file_list(files, dir) Dir.chdir dir do files.each do |file| FileUtils.rm_f file warn "unable to remove old file #{file} please remove it by hand" if File.exist? file end end end def target_bin_path(bin_dir, bin_file) bin_file_formatted = if options[:format_executable] Gem.default_exec_format % bin_file else bin_file end File.join bin_dir, bin_file_formatted end def bin_file_names @bin_file_names ||= [] end end PK!#rubygems/commands/server_command.rbnu[# frozen_string_literal: true require_relative "../command" unless defined? Gem::Commands::ServerCommand class Gem::Commands::ServerCommand < Gem::Command def initialize super("server", "Starts up a web server that hosts the RDoc (requires rubygems-server)") begin Gem::Specification.find_by_name("rubygems-server").activate rescue Gem::LoadError # no-op end end def description # :nodoc: <<-EOF The server command has been moved to the rubygems-server gem. EOF end def execute alert_error "Install the rubygems-server gem for the server command" end end end PK!TA"A"#rubygems/commands/update_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../command_manager" require_relative "../dependency_installer" require_relative "../install_update_options" require_relative "../local_remote_options" require_relative "../spec_fetcher" require_relative "../version_option" require_relative "../install_message" # must come before rdoc for messaging require_relative "../rdoc" class Gem::Commands::UpdateCommand < Gem::Command include Gem::InstallUpdateOptions include Gem::LocalRemoteOptions include Gem::VersionOption attr_reader :installer # :nodoc: attr_reader :updated # :nodoc: def initialize options = { force: false, } options.merge!(install_update_options) super "update", "Update installed gems to the latest version", options add_install_update_options Gem::OptionParser.accept Gem::Version do |value| Gem::Version.new value value end add_option("--system [VERSION]", Gem::Version, "Update the RubyGems system software") do |value, opts| value ||= true opts[:system] = value end add_local_remote_options add_platform_option add_prerelease_option "as update targets" @updated = [] @installer = nil end def arguments # :nodoc: "GEMNAME name of gem to update" end def defaults_str # :nodoc: "--no-force --install-dir #{Gem.dir}\n" + install_update_defaults_str end def description # :nodoc: <<-EOF The update command will update your gems to the latest version. The update command does not remove the previous version. Use the cleanup command to remove old versions. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def check_latest_rubygems(version) # :nodoc: if Gem.rubygems_version == version say "Latest version already installed. Done." terminate_interaction end end def check_oldest_rubygems(version) # :nodoc: if oldest_supported_version > version alert_error "rubygems #{version} is not supported on #{RUBY_VERSION}. The oldest version supported by this ruby is #{oldest_supported_version}" terminate_interaction 1 end end def check_update_arguments # :nodoc: unless options[:args].empty? alert_error "Gem names are not allowed with the --system option" terminate_interaction 1 end end def execute if options[:system] update_rubygems return end gems_to_update = which_to_update( highest_installed_gems, options[:args].uniq ) if options[:explain] say "Gems to update:" gems_to_update.each do |name_tuple| say " #{name_tuple.full_name}" end return end say "Updating installed gems" updated = update_gems gems_to_update installed_names = highest_installed_gems.keys updated_names = updated.map(&:name) not_updated_names = options[:args].uniq - updated_names not_installed_names = not_updated_names - installed_names up_to_date_names = not_updated_names - not_installed_names if updated.empty? say "Nothing to update" else say "Gems updated: #{updated_names.join(" ")}" end say "Gems already up-to-date: #{up_to_date_names.join(" ")}" unless up_to_date_names.empty? say "Gems not currently installed: #{not_installed_names.join(" ")}" unless not_installed_names.empty? end def fetch_remote_gems(spec) # :nodoc: dependency = Gem::Dependency.new spec.name, "> #{spec.version}" dependency.prerelease = options[:prerelease] fetcher = Gem::SpecFetcher.fetcher spec_tuples, errors = fetcher.search_for_dependency dependency error = errors.find {|e| e.respond_to? :exception } raise error if error spec_tuples end def highest_installed_gems # :nodoc: hig = {} # highest installed gems # Get only gem specifications installed as --user-install Gem::Specification.dirs = Gem.user_dir if options[:user_install] Gem::Specification.each do |spec| if hig[spec.name].nil? || hig[spec.name].version < spec.version hig[spec.name] = spec end end hig end def highest_remote_name_tuple(spec) # :nodoc: spec_tuples = fetch_remote_gems spec highest_remote_gem = spec_tuples.max return unless highest_remote_gem highest_remote_gem.first end def install_rubygems(spec) # :nodoc: args = update_rubygems_arguments version = spec.version update_dir = File.join spec.base_dir, "gems", "rubygems-update-#{version}" Dir.chdir update_dir do say "Installing RubyGems #{version}" unless options[:silent] installed = preparing_gem_layout_for(version) do system Gem.ruby, "--disable-gems", "setup.rb", *args end unless options[:silent] say "RubyGems system software updated" if installed end end end def preparing_gem_layout_for(version) if Gem::Version.new(version) >= Gem::Version.new("3.2.a") yield else require "tmpdir" Dir.mktmpdir("gem_update") do |tmpdir| FileUtils.mv Gem.plugindir, tmpdir status = yield unless status FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir end status end end end def rubygems_target_version version = options[:system] update_latest = version == true unless update_latest version = Gem::Version.new version requirement = Gem::Requirement.new version return version, requirement end version = Gem::Version.new Gem::VERSION requirement = Gem::Requirement.new ">= #{Gem::VERSION}" rubygems_update = Gem::Specification.new rubygems_update.name = "rubygems-update" rubygems_update.version = version highest_remote_tup = highest_remote_name_tuple(rubygems_update) target = highest_remote_tup ? highest_remote_tup.version : version [target, requirement] end def update_gem(name, version = Gem::Requirement.default) return if @updated.any? {|spec| spec.name == name } update_options = options.dup update_options[:prerelease] = version.prerelease? @installer = Gem::DependencyInstaller.new update_options say "Updating #{name}" unless options[:system] begin @installer.install name, Gem::Requirement.new(version) rescue Gem::InstallError, Gem::DependencyError => e alert_error "Error installing #{name}:\n\t#{e.message}" end @installer.installed_gems.each do |spec| @updated << spec end end def update_gems(gems_to_update) gems_to_update.uniq.sort.each do |name_tuple| update_gem name_tuple.name, name_tuple.version end @updated end ## # Update RubyGems software to the latest version. def update_rubygems if Gem.disable_system_update_message alert_error Gem.disable_system_update_message terminate_interaction 1 end check_update_arguments version, requirement = rubygems_target_version check_latest_rubygems version check_oldest_rubygems version installed_gems = Gem::Specification.find_all_by_name "rubygems-update", requirement installed_gems = update_gem("rubygems-update", requirement) if installed_gems.empty? || installed_gems.first.version != version return if installed_gems.empty? install_rubygems installed_gems.first end def update_rubygems_arguments # :nodoc: args = [] args << "--silent" if options[:silent] args << "--prefix" << Gem.prefix if Gem.prefix args << "--no-document" unless options[:document].include?("rdoc") || options[:document].include?("ri") args << "--no-format-executable" if options[:no_format_executable] args << "--previous-version" << Gem::VERSION args end def which_to_update(highest_installed_gems, gem_names) result = [] highest_installed_gems.each do |_l_name, l_spec| next if !gem_names.empty? && gem_names.none? {|name| name == l_spec.name } highest_remote_tup = highest_remote_name_tuple l_spec next unless highest_remote_tup result << highest_remote_tup end result end private # # Oldest version we support downgrading to. This is the version that # originally ships with the first patch version of each ruby, because we never # test each ruby against older rubygems, so we can't really guarantee it # works. Version list can be checked here: https://stdgems.org/rubygems # def oldest_supported_version @oldest_supported_version ||= if Gem.ruby_version > Gem::Version.new("3.1.a") Gem::Version.new("3.3.3") else Gem::Version.new("3.2.3") end end end PK!8 8 !rubygems/commands/push_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" require_relative "../package" class Gem::Commands::PushCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::GemcutterUtilities def description # :nodoc: <<-EOF The push command uploads a gem to the push server (the default is https://rubygems.org) and adds it to the index. The gem can be removed from the index and deleted from the server using the yank command. For further discussion see the help for the yank command. The push command will use ~/.gem/credentials to authenticate to a server, but you can use the RubyGems environment variable GEM_HOST_API_KEY to set the api key to authenticate. EOF end def arguments # :nodoc: "GEM built gem to push up" end def usage # :nodoc: "#{program_name} GEM" end def initialize super "push", "Push a gem up to the gem server", host: host @user_defined_host = false add_proxy_option add_key_option add_otp_option add_option("--host HOST", "Push to another gemcutter-compatible host", " (e.g. https://rubygems.org)") do |value, options| options[:host] = value @user_defined_host = true end @host = nil end def execute gem_name = get_one_gem_name default_gem_server, push_host = get_hosts_for(gem_name) @host = if @user_defined_host options[:host] elsif default_gem_server default_gem_server elsif push_host push_host else options[:host] end sign_in @host, scope: get_push_scope send_gem(gem_name) end def send_gem(name) args = [:post, "api/v1/gems"] _, push_host = get_hosts_for(name) @host ||= push_host # Always include @host, even if it's nil args += [@host, push_host] say "Pushing gem to #{@host || Gem.host}..." response = send_push_request(name, args) with_response response end private def send_push_request(name, args) rubygems_api_request(*args, scope: get_push_scope) do |request| request.body = Gem.read_binary name request.add_field "Content-Length", request.body.size request.add_field "Content-Type", "application/octet-stream" request.add_field "Authorization", api_key end end def get_hosts_for(name) gem_metadata = Gem::Package.new(name).spec.metadata [ gem_metadata["default_gem_server"], gem_metadata["allowed_push_host"], ] end def get_push_scope :push_rubygem end end PK!bF葝!rubygems/commands/open_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" class Gem::Commands::OpenCommand < Gem::Command include Gem::VersionOption def initialize super "open", "Open gem sources in editor" add_option("-e", "--editor COMMAND", String, "Prepends COMMAND to gem path. Could be used to specify editor.") do |command, options| options[:editor] = command || get_env_editor end add_option("-v", "--version VERSION", String, "Opens specific gem version") do |version| options[:version] = version end end def arguments # :nodoc: "GEMNAME name of gem to open in editor" end def defaults_str # :nodoc: "-e #{get_env_editor}" end def description # :nodoc: <<-EOF The open command opens gem in editor and changes current path to gem's source directory. Editor command can be specified with -e option, otherwise rubygems will look for editor in $EDITOR, $VISUAL and $GEM_EDITOR variables. EOF end def usage # :nodoc: "#{program_name} [-e COMMAND] GEMNAME" end def get_env_editor ENV["GEM_EDITOR"] || ENV["VISUAL"] || ENV["EDITOR"] || "vi" end def execute @version = options[:version] || Gem::Requirement.default @editor = options[:editor] || get_env_editor found = open_gem(get_one_gem_name) terminate_interaction 1 unless found end def open_gem(name) spec = spec_for name return false unless spec if spec.default_gem? say "'#{name}' is a default gem and can't be opened." return false end open_editor(spec.full_gem_path) end def open_editor(path) system(*@editor.split(/\s+/) + [path], { chdir: path }) end def spec_for(name) spec = Gem::Specification.find_all_by_name(name, @version).first return spec if spec say "Unable to find gem '#{name}'" end end PK! $rubygems/commands/sources_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../remote_fetcher" require_relative "../spec_fetcher" require_relative "../local_remote_options" class Gem::Commands::SourcesCommand < Gem::Command include Gem::LocalRemoteOptions def initialize require "fileutils" super "sources", "Manage the sources and cache file RubyGems uses to search for gems" add_option "-a", "--add SOURCE_URI", "Add source" do |value, options| options[:add] = value end add_option "-l", "--list", "List sources" do |value, options| options[:list] = value end add_option "-r", "--remove SOURCE_URI", "Remove source" do |value, options| options[:remove] = value end add_option "-c", "--clear-all", "Remove all sources (clear the cache)" do |value, options| options[:clear_all] = value end add_option "-u", "--update", "Update source cache" do |value, options| options[:update] = value end add_option "-f", "--[no-]force", "Do not show any confirmation prompts and behave as if 'yes' was always answered" do |value, options| options[:force] = value end add_proxy_option end def add_source(source_uri) # :nodoc: check_rubygems_https source_uri source = Gem::Source.new source_uri check_typo_squatting(source) begin if Gem.sources.include? source say "source #{source_uri} already present in the cache" else source.load_specs :released Gem.sources << source Gem.configuration.write say "#{source_uri} added to sources" end rescue Gem::URI::Error, ArgumentError say "#{source_uri} is not a URI" terminate_interaction 1 rescue Gem::RemoteFetcher::FetchError => e say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" terminate_interaction 1 end end def check_typo_squatting(source) if source.typo_squatting?("rubygems.org") question = <<-QUESTION.chomp #{source.uri} is too similar to https://rubygems.org Do you want to add this source? QUESTION terminate_interaction 1 unless options[:force] || ask_yes_no(question) end end def check_rubygems_https(source_uri) # :nodoc: uri = Gem::URI source_uri if uri.scheme && uri.scheme.casecmp("http").zero? && uri.host.casecmp("rubygems.org").zero? question = <<-QUESTION.chomp https://rubygems.org is recommended for security over #{uri} Do you want to add this insecure source? QUESTION terminate_interaction 1 unless options[:force] || ask_yes_no(question) end end def clear_all # :nodoc: path = Gem.spec_cache_dir FileUtils.rm_rf path if File.exist? path if File.writable? path say "*** Unable to remove source cache ***" else say "*** Unable to remove source cache (write protected) ***" end terminate_interaction 1 else say "*** Removed specs cache ***" end end def defaults_str # :nodoc: "--list" end def description # :nodoc: <<-EOF RubyGems fetches gems from the sources you have configured (stored in your ~/.gemrc). The default source is https://rubygems.org, but you may have other sources configured. This guide will help you update your sources or configure yourself to use your own gem server. Without any arguments the sources lists your currently configured sources: $ gem sources *** CURRENT SOURCES *** https://rubygems.org This may list multiple sources or non-rubygems sources. You probably configured them before or have an old `~/.gemrc`. If you have sources you do not recognize you should remove them. RubyGems has been configured to serve gems via the following URLs through its history: * http://gems.rubyforge.org (RubyGems 1.3.5 and earlier) * http://rubygems.org (RubyGems 1.3.6 through 1.8.30, and 2.0.0) * https://rubygems.org (RubyGems 2.0.1 and newer) Since all of these sources point to the same set of gems you only need one of them in your list. https://rubygems.org is recommended as it brings the protections of an SSL connection to gem downloads. To add a source use the --add argument: $ gem sources --add https://rubygems.org https://rubygems.org added to sources RubyGems will check to see if gems can be installed from the source given before it is added. To remove a source use the --remove argument: $ gem sources --remove https://rubygems.org/ https://rubygems.org/ removed from sources EOF end def list # :nodoc: say "*** CURRENT SOURCES ***" say Gem.sources.each do |src| say src end end def list? # :nodoc: !(options[:add] || options[:clear_all] || options[:remove] || options[:update]) end def execute clear_all if options[:clear_all] source_uri = options[:add] add_source source_uri if source_uri source_uri = options[:remove] remove_source source_uri if source_uri update if options[:update] list if list? end def remove_source(source_uri) # :nodoc: if Gem.sources.include? source_uri Gem.sources.delete source_uri Gem.configuration.write say "#{source_uri} removed from sources" else say "source #{source_uri} not present in cache" end end def update # :nodoc: Gem.sources.each_source do |src| src.load_specs :released src.load_specs :latest end say "source cache successfully updated" end def remove_cache_file(desc, path) # :nodoc: FileUtils.rm_rf path if !File.exist?(path) say "*** Removed #{desc} source cache ***" elsif !File.writable?(path) say "*** Unable to remove #{desc} source cache (write protected) ***" else say "*** Unable to remove #{desc} source cache ***" end end end PK!Ok rubygems/uri.rbnu[# frozen_string_literal: true ## # The Uri handles rubygems source URIs. # class Gem::Uri ## # Parses and redacts uri def self.redact(uri) new(uri).redacted end ## # Parses uri, raising if it's invalid def self.parse!(uri) require_relative "vendor/uri/lib/uri" raise Gem::URI::InvalidURIError unless uri return uri unless uri.is_a?(String) # Always escape URI's to deal with potential spaces and such # It should also be considered that source_uri may already be # a valid URI with escaped characters. e.g. "{DESede}" is encoded # as "%7BDESede%7D". If this is escaped again the percentage # symbols will be escaped. begin Gem::URI.parse(uri) rescue Gem::URI::InvalidURIError Gem::URI.parse(Gem::URI::DEFAULT_PARSER.escape(uri)) end end ## # Parses uri, returning the original uri if it's invalid def self.parse(uri) parse!(uri) rescue Gem::URI::InvalidURIError uri end def initialize(source_uri) @parsed_uri = parse(source_uri) end def redacted return self unless valid_uri? if token? || oauth_basic? with_redacted_user elsif password? with_redacted_password else self end end def to_s @parsed_uri.to_s end def redact_credentials_from(text) return text unless valid_uri? && password? && text.include?(to_s) text.sub(password, "REDACTED") end def method_missing(method_name, *args, &blk) if @parsed_uri.respond_to?(method_name) @parsed_uri.send(method_name, *args, &blk) else super end end def respond_to_missing?(method_name, include_private = false) @parsed_uri.respond_to?(method_name, include_private) || super end protected # Add a protected reader for the cloned instance to access the original object's parsed uri attr_reader :parsed_uri private def parse!(uri) self.class.parse!(uri) end def parse(uri) self.class.parse(uri) end def with_redacted_user clone.tap {|uri| uri.user = "REDACTED" } end def with_redacted_password clone.tap {|uri| uri.password = "REDACTED" } end def valid_uri? !@parsed_uri.is_a?(String) end def password? !!password end def oauth_basic? password == "x-oauth-basic" end def token? !user.nil? && password.nil? end def initialize_copy(original) @parsed_uri = original.parsed_uri.clone end end PK!zʄ rubygems/doctor.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "user_interaction" ## # Cleans up after a partially-failed uninstall or for an invalid # Gem::Specification. # # If a specification was removed by hand this will remove any remaining files. # # If a corrupt specification was installed this will clean up warnings by # removing the bogus specification. class Gem::Doctor include Gem::UserInteraction ## # Maps a gem subdirectory to the files that are expected to exist in the # subdirectory. REPOSITORY_EXTENSION_MAP = [ # :nodoc: ["specifications", ".gemspec"], ["build_info", ".info"], ["cache", ".gem"], ["doc", ""], ["extensions", ""], ["gems", ""], ["plugins", ""], ].freeze missing = Gem::REPOSITORY_SUBDIRECTORIES.sort - REPOSITORY_EXTENSION_MAP.map {|(k,_)| k }.sort raise "Update REPOSITORY_EXTENSION_MAP, missing: #{missing.join ", "}" unless missing.empty? ## # Creates a new Gem::Doctor that will clean up +gem_repository+. Only one # gem repository may be cleaned at a time. # # If +dry_run+ is true no files or directories will be removed. def initialize(gem_repository, dry_run = false) @gem_repository = gem_repository @dry_run = dry_run @installed_specs = nil end ## # Specs installed in this gem repository def installed_specs # :nodoc: @installed_specs ||= Gem::Specification.map(&:full_name) end ## # Are we doctoring a gem repository? def gem_repository? !installed_specs.empty? end ## # Cleans up uninstalled files and invalid gem specifications def doctor @orig_home = Gem.dir @orig_path = Gem.path say "Checking #{@gem_repository}" Gem.use_paths @gem_repository.to_s unless gem_repository? say "This directory does not appear to be a RubyGems repository, " \ "skipping" say return end doctor_children say ensure Gem.use_paths @orig_home, *@orig_path end ## # Cleans up children of this gem repository def doctor_children # :nodoc: REPOSITORY_EXTENSION_MAP.each do |sub_directory, extension| doctor_child sub_directory, extension end end ## # Removes files in +sub_directory+ with +extension+ def doctor_child(sub_directory, extension) # :nodoc: directory = File.join(@gem_repository, sub_directory) Dir.entries(directory).sort.each do |ent| next if [".", ".."].include?(ent) child = File.join(directory, ent) next unless File.exist?(child) basename = File.basename(child, extension) next if installed_specs.include? basename next if /^rubygems-\d/.match?(basename) next if sub_directory == "specifications" && basename == "default" next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ (basename) type = File.directory?(child) ? "directory" : "file" action = if @dry_run "Extra" else FileUtils.rm_r(child) "Removed" end say "#{action} #{type} #{sub_directory}/#{File.basename(child)}" end rescue Errno::ENOENT # ignore end end PK!;}}rubygems/openssl.rbnu[# frozen_string_literal: true autoload :OpenSSL, "openssl" module Gem HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc: end PK!9BBrubygems/shellwords.rbnu[# frozen_string_literal: true autoload :Shellwords, "shellwords" PK!Fj''rubygems/package_task.rbnu[# frozen_string_literal: true # Copyright (c) 2003, 2004 Jim Weirich, 2009 Eric Hodel # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require_relative "../rubygems" require_relative "package" require "rake/packagetask" ## # Create a package based upon a Gem::Specification. Gem packages, as well as # zip files and tar/gzipped packages can be produced by this task. # # In addition to the Rake targets generated by Rake::PackageTask, a # Gem::PackageTask will also generate the following tasks: # # ["package_dir/name-version.gem"] # Create a RubyGems package with the given name and version. # # Example using a Gem::Specification: # # require 'rubygems' # require 'rubygems/package_task' # # spec = Gem::Specification.new do |s| # s.summary = "Ruby based make-like utility." # s.name = 'rake' # s.version = PKG_VERSION # s.requirements << 'none' # s.files = PKG_FILES # s.description = <<-EOF # Rake is a Make-like program implemented in Ruby. Tasks # and dependencies are specified in standard Ruby syntax. # EOF # end # # Gem::PackageTask.new(spec) do |pkg| # pkg.need_zip = true # pkg.need_tar = true # end class Gem::PackageTask < Rake::PackageTask ## # Ruby Gem::Specification containing the metadata for this package. The # name, version and package_files are automatically determined from the # gemspec and don't need to be explicitly provided. attr_accessor :gem_spec ## # Create a Gem Package task library. Automatically define the gem if a # block is given. If no block is supplied, then #define needs to be called # to define the task. def initialize(gem_spec) init gem_spec yield self if block_given? define if block_given? end ## # Initialization tasks without the "yield self" or define operations. def init(gem) super gem.full_name, :noversion @gem_spec = gem @package_files += gem_spec.files if gem_spec.files @fileutils_output = $stdout end ## # Create the Rake tasks and actions specified by this Gem::PackageTask. # (+define+ is automatically called if a block is given to +new+). def define super gem_file = File.basename gem_spec.cache_file gem_path = File.join package_dir, gem_file gem_dir = File.join package_dir, gem_spec.full_name task package: [:gem] directory package_dir directory gem_dir desc "Build the gem file #{gem_file}" task gem: [gem_path] trace = Rake.application.options.trace Gem.configuration.verbose = trace file gem_path => [package_dir, gem_dir] + @gem_spec.files do chdir(gem_dir) do when_writing "Creating #{gem_spec.file_name}" do Gem::Package.build gem_spec verbose trace do mv gem_file, ".." end end end end end end PK!7XOOrubygems/vendored_optparse.rbnu[# frozen_string_literal: true require_relative "vendor/optparse/lib/optparse" PK![!lkkrubygems/update_suggestion.rbnu[# frozen_string_literal: true ## # Mixin methods for Gem::Command to promote available RubyGems update module Gem::UpdateSuggestion ONE_WEEK = 7 * 24 * 60 * 60 ## # Message to promote available RubyGems update with related gem update command. def update_suggestion <<-MESSAGE A new release of RubyGems is available: #{Gem.rubygems_version} → #{Gem.latest_rubygems_version}! Run `gem update --system #{Gem.latest_rubygems_version}` to update your installation. MESSAGE end ## # Determines if current environment is eligible for update suggestion. def eligible_for_update? # explicit opt-out return false if Gem.configuration[:prevent_update_suggestion] return false if ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] # focus only on human usage of final RubyGems releases return false unless Gem.ui.tty? return false if Gem.rubygems_version.prerelease? return false if Gem.disable_system_update_message return false if Gem::CIDetector.ci? # check makes sense only when we can store timestamp of last try # otherwise we will not be able to prevent "annoying" update message # on each command call return unless Gem.configuration.state_file_writable? # load time of last check, ensure the difference is enough to repeat the suggestion check_time = Time.now.to_i last_update_check = Gem.configuration.last_update_check return false if (check_time - last_update_check) < ONE_WEEK # compare current and latest version, this is the part where # latest rubygems spec is fetched from remote (Gem.rubygems_version < Gem.latest_rubygems_version).tap do |eligible| # store the time of last successful check into state file Gem.configuration.last_update_check = check_time return eligible end rescue StandardError # don't block install command on any problem false end end PK!յ_"rubygems/install_update_options.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" require_relative "security_option" ## # Mixin methods for install and update options for Gem::Commands module Gem::InstallUpdateOptions include Gem::SecurityOption ## # Add the install/update options to the option parser. def add_install_update_options add_option(:"Install/Update", "-i", "--install-dir DIR", "Gem repository directory to get installed", "gems") do |value, options| options[:install_dir] = File.expand_path(value) end add_option(:"Install/Update", "-n", "--bindir DIR", "Directory where executables will be", "placed when the gem is installed") do |value, options| options[:bin_dir] = File.expand_path(value) end add_option(:"Install/Update", "--document [TYPES]", Array, "Generate documentation for installed gems", "List the documentation types you wish to", "generate. For example: rdoc,ri") do |value, options| options[:document] = case value when nil then %w[ri] when false then [] else value end end add_option(:"Install/Update", "--build-root DIR", "Temporary installation root. Useful for building", "packages. Do not use this when installing remote gems.") do |value, options| options[:build_root] = File.expand_path(value) end add_option(:"Install/Update", "--vendor", "Install gem into the vendor directory.", "Only for use by gem repackagers.") do |_value, options| unless Gem.vendor_dir raise Gem::OptionParser::InvalidOption.new "your platform is not supported" end options[:vendor] = true options[:install_dir] = Gem.vendor_dir end add_option(:"Install/Update", "-N", "--no-document", "Disable documentation generation") do |_value, options| options[:document] = [] end add_option(:"Install/Update", "-E", "--[no-]env-shebang", "Rewrite the shebang line on installed", "scripts to use /usr/bin/env") do |value, options| options[:env_shebang] = value end add_option(:"Install/Update", "-f", "--[no-]force", "Force gem to install, bypassing dependency", "checks") do |value, options| options[:force] = value end add_option(:"Install/Update", "-w", "--[no-]wrappers", "Use bin wrappers for executables", "Not available on dosish platforms") do |value, options| options[:wrappers] = value end add_security_option add_option(:"Install/Update", "--ignore-dependencies", "Do not install any required dependent gems") do |value, options| options[:ignore_dependencies] = value end add_option(:"Install/Update", "--[no-]format-executable", "Make installed executable names match Ruby.", "If Ruby is ruby18, foo_exec will be", "foo_exec18") do |value, options| options[:format_executable] = value end add_option(:"Install/Update", "--[no-]user-install", "Install in user's home directory instead", "of GEM_HOME.") do |value, options| options[:user_install] = value end add_option(:"Install/Update", "--development", "Install additional development", "dependencies") do |_value, options| options[:development] = true options[:dev_shallow] = true end add_option(:"Install/Update", "--development-all", "Install development dependencies for all", "gems (including dev deps themselves)") do |_value, options| options[:development] = true options[:dev_shallow] = false end add_option(:"Install/Update", "--conservative", "Don't attempt to upgrade gems already", "meeting version requirement") do |_value, options| options[:conservative] = true options[:minimal_deps] = true end add_option(:"Install/Update", "--[no-]minimal-deps", "Don't upgrade any dependencies that already", "meet version requirements") do |value, options| options[:minimal_deps] = value end add_option(:"Install/Update", "--[no-]post-install-message", "Print post install message") do |value, options| options[:post_install_message] = value end add_option(:"Install/Update", "-g", "--file [FILE]", "Read from a gem dependencies API file and", "install the listed gems") do |v,_o| v ||= Gem::GEM_DEP_FILES.find do |file| File.exist? file end unless v message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ", "})" raise Gem::OptionParser::InvalidArgument, "cannot find gem dependencies file #{message}" end options[:gemdeps] = v end add_option(:"Install/Update", "--without GROUPS", Array, "Omit the named groups (comma separated)", "when installing from a gem dependencies", "file") do |v,_o| options[:without_groups].concat v.map(&:intern) end add_option(:"Install/Update", "--default", "Add the gem's full specification to", "specifications/default and extract only its bin") do |v,_o| options[:install_as_default] = v end add_option(:"Install/Update", "--explain", "Rather than install the gems, indicate which would", "be installed") do |v,_o| options[:explain] = v end add_option(:"Install/Update", "--[no-]lock", "Create a lock file (when used with -g/--file)") do |v,_o| options[:lock] = v end add_option(:"Install/Update", "--[no-]suggestions", "Suggest alternates when gems are not found") do |v,_o| options[:suggest_alternate] = v end end ## # Default options for the gem install and update commands. def install_update_options { document: %w[ri], } end ## # Default description for the gem install and update commands. def install_update_defaults_str "--document=ri" end end PK!{G rubygems/util.rbnu[# frozen_string_literal: true require_relative "deprecate" ## # This module contains various utility methods as module methods. module Gem::Util ## # Zlib::GzipReader wrapper that unzips +data+. def self.gunzip(data) require "zlib" require "stringio" data = StringIO.new(data, "r") gzip_reader = begin Zlib::GzipReader.new(data) rescue Zlib::GzipFile::Error => e raise e.class, e.inspect, e.backtrace end unzipped = gzip_reader.read unzipped.force_encoding Encoding::BINARY unzipped end ## # Zlib::GzipWriter wrapper that zips +data+. def self.gzip(data) require "zlib" require "stringio" zipped = StringIO.new(String.new, "w") zipped.set_encoding Encoding::BINARY Zlib::GzipWriter.wrap zipped do |io| io.write data end zipped.string end ## # A Zlib::Inflate#inflate wrapper def self.inflate(data) require "zlib" Zlib::Inflate.inflate data end ## # This calls IO.popen and reads the result def self.popen(*command) IO.popen command, &:read end ## # Invokes system, but silences all output. def self.silent_system(*command) opt = { out: IO::NULL, err: [:child, :out] } if Hash === command.last opt.update(command.last) cmds = command[0...-1] else cmds = command.dup end system(*(cmds << opt)) end class << self extend Gem::Deprecate rubygems_deprecate :silent_system end ## # Enumerates the parents of +directory+. def self.traverse_parents(directory, &block) return enum_for __method__, directory unless block_given? here = File.expand_path directory loop do begin Dir.chdir here, &block rescue StandardError Errno::EACCES end new_here = File.expand_path("..", here) return if new_here == here # toplevel here = new_here end end ## # Globs for files matching +pattern+ inside of +directory+, # returning absolute paths to the matching files. def self.glob_files_in_dir(glob, base_path) Dir.glob(glob, base: base_path).map! {|f| File.expand_path(f, base_path) } end ## # Corrects +path+ (usually returned by `Gem::URI.parse().path` on Windows), that # comes with a leading slash. def self.correct_for_windows_path(path) if path[0].chr == "/" && path[1].chr.match?(/[a-z]/i) && path[2].chr == ":" path[1..-1] else path end end end PK!FZ c rubygems/basic_specification.rbnu[# frozen_string_literal: true ## # BasicSpecification is an abstract class which implements some common code # used by both Specification and StubSpecification. class Gem::BasicSpecification ## # Allows installation of extensions for git: gems. attr_writer :base_dir # :nodoc: ## # Sets the directory where extensions for this gem will be installed. attr_writer :extension_dir # :nodoc: ## # Is this specification ignored for activation purposes? attr_writer :ignored # :nodoc: ## # The path this gemspec was loaded from. This attribute is not persisted. attr_accessor :loaded_from ## # Allows correct activation of git: and path: gems. attr_writer :full_gem_path # :nodoc: def initialize internal_init end def self.default_specifications_dir Gem.default_specifications_dir end class << self extend Gem::Deprecate rubygems_deprecate :default_specifications_dir, "Gem.default_specifications_dir" end ## # The path to the gem.build_complete file within the extension install # directory. def gem_build_complete_path # :nodoc: File.join extension_dir, "gem.build_complete" end ## # True when the gem has been activated def activated? raise NotImplementedError end ## # Returns the full path to the base gem directory. # # eg: /usr/local/lib/ruby/gems/1.8 def base_dir raise NotImplementedError end ## # Return true if this spec can require +file+. def contains_requirable_file?(file) if ignored? if platform == Gem::Platform::RUBY || Gem::Platform.local === platform warn "Ignoring #{full_name} because its extensions are not built. " \ "Try: gem pristine #{name} --version #{version}" end return false end is_soext = file.end_with?(".so", ".o") if is_soext have_file? file.delete_suffix(File.extname(file)), Gem.dynamic_library_suffixes else have_file? file, Gem.suffixes end end ## # Return true if this spec should be ignored because it's missing extensions. def ignored? return @ignored unless @ignored.nil? @ignored = missing_extensions? end def default_gem? !loaded_from.nil? && File.dirname(loaded_from) == Gem.default_specifications_dir end ## # Regular gems take precedence over default gems def default_gem_priority default_gem? ? 1 : -1 end ## # Gems higher up in +gem_path+ take precedence def base_dir_priority(gem_path) gem_path.index(base_dir) || gem_path.size end ## # Returns full path to the directory where gem's extensions are installed. def extension_dir @extension_dir ||= File.expand_path(File.join(extensions_dir, full_name)) end ## # Returns path to the extensions directory. def extensions_dir Gem.default_ext_dir_for(base_dir) || File.join(base_dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version) end def find_full_gem_path # :nodoc: # TODO: also, shouldn't it default to full_name if it hasn't been written? File.expand_path File.join(gems_dir, full_name) end private :find_full_gem_path ## # The full path to the gem (install path + full name). def full_gem_path # TODO: This is a heavily used method by gems, so we'll need # to aleast just alias it to #gem_dir rather than remove it. @full_gem_path ||= find_full_gem_path end ## # Returns the full name (name-version) of this Gem. Platform information # is included (name-version-platform) if it is specified and not the # default Ruby platform. def full_name if platform == Gem::Platform::RUBY || platform.nil? "#{name}-#{version}" else "#{name}-#{version}-#{platform}" end end ## # Returns the full name of this Gem (see `Gem::BasicSpecification#full_name`). # Information about where the gem is installed is also included if not # installed in the default GEM_HOME. def full_name_with_location if base_dir != Gem.dir "#{full_name} in #{base_dir}" else full_name end end ## # Full paths in the gem to add to $LOAD_PATH when this gem is # activated. def full_require_paths @full_require_paths ||= begin full_paths = raw_require_paths.map do |path| File.join full_gem_path, path end full_paths << extension_dir if have_extensions? full_paths end end ## # The path to the data directory for this gem. def datadir # TODO: drop the extra ", gem_name" which is uselessly redundant File.expand_path(File.join(gems_dir, full_name, "data", name)) end ## # Full path of the target library file. # If the file is not in this gem, return nil. def to_fullpath(path) if activated? @paths_map ||= {} Gem.suffixes.each do |suf| full_require_paths.each do |dir| fullpath = "#{dir}/#{path}#{suf}" next unless File.file?(fullpath) @paths_map[path] ||= fullpath end end @paths_map[path] end end ## # Returns the full path to this spec's gem directory. # eg: /usr/local/lib/ruby/1.8/gems/mygem-1.0 def gem_dir @gem_dir ||= File.expand_path File.join(gems_dir, full_name) end ## # Returns the full path to the gems directory containing this spec's # gem directory. eg: /usr/local/lib/ruby/1.8/gems def gems_dir raise NotImplementedError end def internal_init # :nodoc: @extension_dir = nil @full_gem_path = nil @gem_dir = nil @ignored = nil end ## # Name of the gem def name raise NotImplementedError end ## # Platform of the gem def platform raise NotImplementedError end def raw_require_paths # :nodoc: raise NotImplementedError end ## # Paths in the gem to add to $LOAD_PATH when this gem is # activated. # # See also #require_paths= # # If you have an extension you do not need to add "ext" to the # require path, the extension build process will copy the extension files # into "lib" for you. # # The default value is "lib" # # Usage: # # # If all library files are in the root directory... # spec.require_path = '.' def require_paths return raw_require_paths unless have_extensions? [extension_dir].concat raw_require_paths end ## # Returns the paths to the source files for use with analysis and # documentation tools. These paths are relative to full_gem_path. def source_paths paths = raw_require_paths.dup if have_extensions? ext_dirs = extensions.map do |extension| extension.split(File::SEPARATOR, 2).first end.uniq paths.concat ext_dirs end paths.uniq end ## # Return all files in this gem that match for +glob+. def matches_for_glob(glob) # TODO: rename? glob = File.join(lib_dirs_glob, glob) Dir[glob] end ## # Returns the list of plugins in this spec. def plugins matches_for_glob("rubygems#{Gem.plugin_suffix_pattern}") end ## # Returns a string usable in Dir.glob to match all requirable paths # for this spec. def lib_dirs_glob dirs = if raw_require_paths if raw_require_paths.size > 1 "{#{raw_require_paths.join(",")}}" else raw_require_paths.first end else "lib" # default value for require_paths for bundler/inline end "#{full_gem_path}/#{dirs}" end ## # Return a Gem::Specification from this gem def to_spec raise NotImplementedError end ## # Version of the gem def version raise NotImplementedError end ## # Whether this specification is stubbed - i.e. we have information # about the gem from a stub line, without having to evaluate the # entire gemspec file. def stubbed? raise NotImplementedError end def this self end private def have_extensions? !extensions.empty? end def have_file?(file, suffixes) return true if raw_require_paths.any? do |path| base = File.join(gems_dir, full_name, path, file) suffixes.any? {|suf| File.file? base + suf } end if have_extensions? base = File.join extension_dir, file suffixes.any? {|suf| File.file? base + suf } else false end end end PK!frubygems/util/list.rbnu[# frozen_string_literal: true module Gem # The Gem::List class is currently unused and will be removed in the next major rubygems version class List # :nodoc: include Enumerable attr_accessor :value, :tail def initialize(value = nil, tail = nil) @value = value @tail = tail end def each n = self while n yield n.value n = n.tail end end def to_a super.reverse end def prepend(value) List.new value, self end def pretty_print(q) # :nodoc: q.pp to_a end def self.prepend(list, value) return List.new(value) unless list List.new value, list end end deprecate_constant :List end PK!Cv66rubygems/util/licenses.rbnu[# frozen_string_literal: true # This is generated by generate_spdx_license_list.rb, any edits to this # file will be discarded. require_relative "../text" class Gem::Licenses extend Gem::Text NONSTANDARD = "Nonstandard" LICENSE_REF = "LicenseRef-.+" # Software Package Data Exchange (SPDX) standard open-source software # license identifiers LICENSE_IDENTIFIERS = %w[ 0BSD 3D-Slicer-1.0 AAL ADSL AFL-1.1 AFL-1.2 AFL-2.0 AFL-2.1 AFL-3.0 AGPL-1.0-only AGPL-1.0-or-later AGPL-3.0-only AGPL-3.0-or-later AMD-newlib AMDPLPA AML AML-glslang AMPAS ANTLR-PD ANTLR-PD-fallback APAFML APL-1.0 APSL-1.0 APSL-1.1 APSL-1.2 APSL-2.0 ASWF-Digital-Assets-1.0 ASWF-Digital-Assets-1.1 Abstyles AdaCore-doc Adobe-2006 Adobe-Display-PostScript Adobe-Glyph Adobe-Utopia Afmparse Aladdin Apache-1.0 Apache-1.1 Apache-2.0 App-s2p Arphic-1999 Artistic-1.0 Artistic-1.0-Perl Artistic-1.0-cl8 Artistic-2.0 BSD-1-Clause BSD-2-Clause BSD-2-Clause-Darwin BSD-2-Clause-Patent BSD-2-Clause-Views BSD-2-Clause-first-lines BSD-3-Clause BSD-3-Clause-Attribution BSD-3-Clause-Clear BSD-3-Clause-HP BSD-3-Clause-LBNL BSD-3-Clause-Modification BSD-3-Clause-No-Military-License BSD-3-Clause-No-Nuclear-License BSD-3-Clause-No-Nuclear-License-2014 BSD-3-Clause-No-Nuclear-Warranty BSD-3-Clause-Open-MPI BSD-3-Clause-Sun BSD-3-Clause-acpica BSD-3-Clause-flex BSD-4-Clause BSD-4-Clause-Shortened BSD-4-Clause-UC BSD-4.3RENO BSD-4.3TAHOE BSD-Advertising-Acknowledgement BSD-Attribution-HPND-disclaimer BSD-Inferno-Nettverk BSD-Protection BSD-Source-Code BSD-Source-beginning-file BSD-Systemics BSD-Systemics-W3Works BSL-1.0 BUSL-1.1 Baekmuk Bahyph Barr Beerware BitTorrent-1.0 BitTorrent-1.1 Bitstream-Charter Bitstream-Vera BlueOak-1.0.0 Boehm-GC Borceux Brian-Gladman-2-Clause Brian-Gladman-3-Clause C-UDA-1.0 CAL-1.0 CAL-1.0-Combined-Work-Exception CATOSL-1.1 CC-BY-1.0 CC-BY-2.0 CC-BY-2.5 CC-BY-2.5-AU CC-BY-3.0 CC-BY-3.0-AT CC-BY-3.0-AU CC-BY-3.0-DE CC-BY-3.0-IGO CC-BY-3.0-NL CC-BY-3.0-US CC-BY-4.0 CC-BY-NC-1.0 CC-BY-NC-2.0 CC-BY-NC-2.5 CC-BY-NC-3.0 CC-BY-NC-3.0-DE CC-BY-NC-4.0 CC-BY-NC-ND-1.0 CC-BY-NC-ND-2.0 CC-BY-NC-ND-2.5 CC-BY-NC-ND-3.0 CC-BY-NC-ND-3.0-DE CC-BY-NC-ND-3.0-IGO CC-BY-NC-ND-4.0 CC-BY-NC-SA-1.0 CC-BY-NC-SA-2.0 CC-BY-NC-SA-2.0-DE CC-BY-NC-SA-2.0-FR CC-BY-NC-SA-2.0-UK CC-BY-NC-SA-2.5 CC-BY-NC-SA-3.0 CC-BY-NC-SA-3.0-DE CC-BY-NC-SA-3.0-IGO CC-BY-NC-SA-4.0 CC-BY-ND-1.0 CC-BY-ND-2.0 CC-BY-ND-2.5 CC-BY-ND-3.0 CC-BY-ND-3.0-DE CC-BY-ND-4.0 CC-BY-SA-1.0 CC-BY-SA-2.0 CC-BY-SA-2.0-UK CC-BY-SA-2.1-JP CC-BY-SA-2.5 CC-BY-SA-3.0 CC-BY-SA-3.0-AT CC-BY-SA-3.0-DE CC-BY-SA-3.0-IGO CC-BY-SA-4.0 CC-PDDC CC0-1.0 CDDL-1.0 CDDL-1.1 CDL-1.0 CDLA-Permissive-1.0 CDLA-Permissive-2.0 CDLA-Sharing-1.0 CECILL-1.0 CECILL-1.1 CECILL-2.0 CECILL-2.1 CECILL-B CECILL-C CERN-OHL-1.1 CERN-OHL-1.2 CERN-OHL-P-2.0 CERN-OHL-S-2.0 CERN-OHL-W-2.0 CFITSIO CMU-Mach CMU-Mach-nodoc CNRI-Jython CNRI-Python CNRI-Python-GPL-Compatible COIL-1.0 CPAL-1.0 CPL-1.0 CPOL-1.02 CUA-OPL-1.0 Caldera Caldera-no-preamble Catharon ClArtistic Clips Community-Spec-1.0 Condor-1.1 Cornell-Lossless-JPEG Cronyx Crossword CrystalStacker Cube D-FSL-1.0 DEC-3-Clause DL-DE-BY-2.0 DL-DE-ZERO-2.0 DOC DRL-1.0 DRL-1.1 DSDP Dotseqn ECL-1.0 ECL-2.0 EFL-1.0 EFL-2.0 EPICS EPL-1.0 EPL-2.0 EUDatagrid EUPL-1.0 EUPL-1.1 EUPL-1.2 Elastic-2.0 Entessa ErlPL-1.1 Eurosym FBM FDK-AAC FSFAP FSFAP-no-warranty-disclaimer FSFUL FSFULLR FSFULLRWD FTL Fair Ferguson-Twofish Frameworx-1.0 FreeBSD-DOC FreeImage Furuseth GCR-docs GD GFDL-1.1-invariants-only GFDL-1.1-invariants-or-later GFDL-1.1-no-invariants-only GFDL-1.1-no-invariants-or-later GFDL-1.1-only GFDL-1.1-or-later GFDL-1.2-invariants-only GFDL-1.2-invariants-or-later GFDL-1.2-no-invariants-only GFDL-1.2-no-invariants-or-later GFDL-1.2-only GFDL-1.2-or-later GFDL-1.3-invariants-only GFDL-1.3-invariants-or-later GFDL-1.3-no-invariants-only GFDL-1.3-no-invariants-or-later GFDL-1.3-only GFDL-1.3-or-later GL2PS GLWTPL GPL-1.0-only GPL-1.0-or-later GPL-2.0-only GPL-2.0-or-later GPL-3.0-only GPL-3.0-or-later Giftware Glide Glulxe Graphics-Gems Gutmann HP-1986 HP-1989 HPND HPND-DEC HPND-Fenneberg-Livingston HPND-INRIA-IMAG HPND-Intel HPND-Kevlin-Henney HPND-MIT-disclaimer HPND-Markus-Kuhn HPND-Pbmplus HPND-UC HPND-UC-export-US HPND-doc HPND-doc-sell HPND-export-US HPND-export-US-acknowledgement HPND-export-US-modify HPND-export2-US HPND-merchantability-variant HPND-sell-MIT-disclaimer-xserver HPND-sell-regexpr HPND-sell-variant HPND-sell-variant-MIT-disclaimer HPND-sell-variant-MIT-disclaimer-rev HTMLTIDY HaskellReport Hippocratic-2.1 IBM-pibs ICU IEC-Code-Components-EULA IJG IJG-short IPA IPL-1.0 ISC ISC-Veillard ImageMagick Imlib2 Info-ZIP Inner-Net-2.0 Intel Intel-ACPI Interbase-1.0 JPL-image JPNIC JSON Jam JasPer-2.0 Kastrup Kazlib Knuth-CTAN LAL-1.2 LAL-1.3 LGPL-2.0-only LGPL-2.0-or-later LGPL-2.1-only LGPL-2.1-or-later LGPL-3.0-only LGPL-3.0-or-later LGPLLR LOOP LPD-document LPL-1.0 LPL-1.02 LPPL-1.0 LPPL-1.1 LPPL-1.2 LPPL-1.3a LPPL-1.3c LZMA-SDK-9.11-to-9.20 LZMA-SDK-9.22 Latex2e Latex2e-translated-notice Leptonica LiLiQ-P-1.1 LiLiQ-R-1.1 LiLiQ-Rplus-1.1 Libpng Linux-OpenIB Linux-man-pages-1-para Linux-man-pages-copyleft Linux-man-pages-copyleft-2-para Linux-man-pages-copyleft-var Lucida-Bitmap-Fonts MIT MIT-0 MIT-CMU MIT-Festival MIT-Khronos-old MIT-Modern-Variant MIT-Wu MIT-advertising MIT-enna MIT-feh MIT-open-group MIT-testregex MITNFA MMIXware MPEG-SSG MPL-1.0 MPL-1.1 MPL-2.0 MPL-2.0-no-copyleft-exception MS-LPL MS-PL MS-RL MTLL Mackerras-3-Clause Mackerras-3-Clause-acknowledgment MakeIndex Martin-Birgmeier McPhee-slideshow Minpack MirOS Motosoto MulanPSL-1.0 MulanPSL-2.0 Multics Mup NAIST-2003 NASA-1.3 NBPL-1.0 NCBI-PD NCGL-UK-2.0 NCL NCSA NGPL NICTA-1.0 NIST-PD NIST-PD-fallback NIST-Software NLOD-1.0 NLOD-2.0 NLPL NOSL NPL-1.0 NPL-1.1 NPOSL-3.0 NRL NTP NTP-0 Naumen Net-SNMP NetCDF Newsletr Nokia Noweb O-UDA-1.0 OAR OCCT-PL OCLC-2.0 ODC-By-1.0 ODbL-1.0 OFFIS OFL-1.0 OFL-1.0-RFN OFL-1.0-no-RFN OFL-1.1 OFL-1.1-RFN OFL-1.1-no-RFN OGC-1.0 OGDL-Taiwan-1.0 OGL-Canada-2.0 OGL-UK-1.0 OGL-UK-2.0 OGL-UK-3.0 OGTSL OLDAP-1.1 OLDAP-1.2 OLDAP-1.3 OLDAP-1.4 OLDAP-2.0 OLDAP-2.0.1 OLDAP-2.1 OLDAP-2.2 OLDAP-2.2.1 OLDAP-2.2.2 OLDAP-2.3 OLDAP-2.4 OLDAP-2.5 OLDAP-2.6 OLDAP-2.7 OLDAP-2.8 OLFL-1.3 OML OPL-1.0 OPL-UK-3.0 OPUBL-1.0 OSET-PL-2.1 OSL-1.0 OSL-1.1 OSL-2.0 OSL-2.1 OSL-3.0 OpenPBS-2.3 OpenSSL OpenSSL-standalone OpenVision PADL PDDL-1.0 PHP-3.0 PHP-3.01 PPL PSF-2.0 Parity-6.0.0 Parity-7.0.0 Pixar Plexus PolyForm-Noncommercial-1.0.0 PolyForm-Small-Business-1.0.0 PostgreSQL Python-2.0 Python-2.0.1 QPL-1.0 QPL-1.0-INRIA-2004 Qhull RHeCos-1.1 RPL-1.1 RPL-1.5 RPSL-1.0 RSA-MD RSCPL Rdisc Ruby SAX-PD SAX-PD-2.0 SCEA SGI-B-1.0 SGI-B-1.1 SGI-B-2.0 SGI-OpenGL SGP4 SHL-0.5 SHL-0.51 SISSL SISSL-1.2 SL SMLNJ SMPPL SNIA SPL-1.0 SSH-OpenSSH SSH-short SSLeay-standalone SSPL-1.0 SWL Saxpath SchemeReport Sendmail Sendmail-8.23 SimPL-2.0 Sleepycat Soundex Spencer-86 Spencer-94 Spencer-99 SugarCRM-1.1.3 Sun-PPP Sun-PPP-2000 SunPro Symlinks TAPR-OHL-1.0 TCL TCP-wrappers TGPPL-1.0 TMate TORQUE-1.1 TOSL TPDL TPL-1.0 TTWL TTYP0 TU-Berlin-1.0 TU-Berlin-2.0 TermReadKey UCAR UCL-1.0 UMich-Merit UPL-1.0 URT-RLE Unicode-3.0 Unicode-DFS-2015 Unicode-DFS-2016 Unicode-TOU UnixCrypt Unlicense VOSTROM VSL-1.0 Vim W3C W3C-19980720 W3C-20150513 WTFPL Watcom-1.0 Widget-Workshop Wsuipa X11 X11-distribute-modifications-variant XFree86-1.1 XSkat Xdebug-1.03 Xerox Xfig Xnet YPL-1.0 YPL-1.1 ZPL-1.1 ZPL-2.0 ZPL-2.1 Zed Zeeff Zend-2.0 Zimbra-1.3 Zimbra-1.4 Zlib any-OSI bcrypt-Solar-Designer blessing bzip2-1.0.6 check-cvs checkmk copyleft-next-0.3.0 copyleft-next-0.3.1 curl cve-tou diffmark dtoa dvipdfm eGenix etalab-2.0 fwlw gSOAP-1.3b gnuplot gtkbook hdparm iMatix libpng-2.0 libselinux-1.0 libtiff libutil-David-Nugent lsof magaz mailprio metamail mpi-permissive mpich2 mplus pkgconf pnmstitch psfrag psutils python-ldap radvd snprintf softSurfer ssh-keyscan swrule threeparttable ulem w3m xinetd xkeyboard-config-Zinoviev xlock xpp xzoom zlib-acknowledgement ].freeze DEPRECATED_LICENSE_IDENTIFIERS = %w[ AGPL-1.0 AGPL-3.0 BSD-2-Clause-FreeBSD BSD-2-Clause-NetBSD GFDL-1.1 GFDL-1.2 GFDL-1.3 GPL-1.0 GPL-1.0+ GPL-2.0 GPL-2.0+ GPL-2.0-with-GCC-exception GPL-2.0-with-autoconf-exception GPL-2.0-with-bison-exception GPL-2.0-with-classpath-exception GPL-2.0-with-font-exception GPL-3.0 GPL-3.0+ GPL-3.0-with-GCC-exception GPL-3.0-with-autoconf-exception LGPL-2.0 LGPL-2.0+ LGPL-2.1 LGPL-2.1+ LGPL-3.0 LGPL-3.0+ Nunit StandardML-NJ bzip2-1.0.5 eCos-2.0 wxWindows ].freeze # exception identifiers EXCEPTION_IDENTIFIERS = %w[ 389-exception Asterisk-exception Asterisk-linking-protocols-exception Autoconf-exception-2.0 Autoconf-exception-3.0 Autoconf-exception-generic Autoconf-exception-generic-3.0 Autoconf-exception-macro Bison-exception-1.24 Bison-exception-2.2 Bootloader-exception CLISP-exception-2.0 Classpath-exception-2.0 DigiRule-FOSS-exception FLTK-exception Fawkes-Runtime-exception Font-exception-2.0 GCC-exception-2.0 GCC-exception-2.0-note GCC-exception-3.1 GNAT-exception GNOME-examples-exception GNU-compiler-exception GPL-3.0-interface-exception GPL-3.0-linking-exception GPL-3.0-linking-source-exception GPL-CC-1.0 GStreamer-exception-2005 GStreamer-exception-2008 Gmsh-exception KiCad-libraries-exception LGPL-3.0-linking-exception LLGPL LLVM-exception LZMA-exception Libtool-exception Linux-syscall-note OCCT-exception-1.0 OCaml-LGPL-linking-exception OpenJDK-assembly-exception-1.0 PCRE2-exception PS-or-PDF-font-exception-20170817 QPL-1.0-INRIA-2004-exception Qt-GPL-exception-1.0 Qt-LGPL-exception-1.1 Qwt-exception-1.0 RRDtool-FLOSS-exception-2.0 SANE-exception SHL-2.0 SHL-2.1 SWI-exception Swift-exception Texinfo-exception UBDL-exception Universal-FOSS-exception-1.0 WxWindows-exception-3.1 cryptsetup-OpenSSL-exception eCos-exception-2.0 fmt-exception freertos-exception-2.0 gnu-javamail-exception i2p-gpl-java-exception libpri-OpenH323-exception mif-exception openvpn-openssl-exception stunnel-exception u-boot-exception-2.0 vsftpd-openssl-exception x11vnc-openssl-exception ].freeze DEPRECATED_EXCEPTION_IDENTIFIERS = %w[ Nokia-Qt-exception-1.1 ].freeze VALID_REGEXP = / \A (?: #{Regexp.union(LICENSE_IDENTIFIERS)} \+? (?:\s WITH \s #{Regexp.union(EXCEPTION_IDENTIFIERS)})? | #{NONSTANDARD} | #{LICENSE_REF} ) \Z /ox DEPRECATED_LICENSE_REGEXP = / \A #{Regexp.union(DEPRECATED_LICENSE_IDENTIFIERS)} \+? (?:\s WITH \s .+?)? \Z /ox DEPRECATED_EXCEPTION_REGEXP = / \A .+? \+? (?:\s WITH \s #{Regexp.union(DEPRECATED_EXCEPTION_IDENTIFIERS)}) \Z /ox def self.match?(license) VALID_REGEXP.match?(license) end def self.deprecated_license_id?(license) DEPRECATED_LICENSE_REGEXP.match?(license) end def self.deprecated_exception_id?(license) DEPRECATED_EXCEPTION_REGEXP.match?(license) end def self.suggestions(license) by_distance = LICENSE_IDENTIFIERS.group_by do |identifier| levenshtein_distance(identifier, license) end lowest = by_distance.keys.min return unless lowest < license.size by_distance[lowest] end end PK!Xuu rubygems/local_remote_options.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "vendor/uri/lib/uri" require_relative "../rubygems" ## # Mixin methods for local and remote Gem::Command options. module Gem::LocalRemoteOptions ## # Allows Gem::OptionParser to handle HTTP URIs. def accept_uri_http Gem::OptionParser.accept Gem::URI::HTTP do |value| begin uri = Gem::URI.parse value rescue Gem::URI::InvalidURIError raise Gem::OptionParser::InvalidArgument, value end valid_uri_schemes = ["http", "https", "file", "s3"] unless valid_uri_schemes.include?(uri.scheme) msg = "Invalid uri scheme for #{value}\nPreface URLs with one of #{valid_uri_schemes.map {|s| "#{s}://" }}" raise ArgumentError, msg end value end end ## # Add local/remote options to the command line parser. def add_local_remote_options add_option(:"Local/Remote", "-l", "--local", "Restrict operations to the LOCAL domain") do |_value, options| options[:domain] = :local end add_option(:"Local/Remote", "-r", "--remote", "Restrict operations to the REMOTE domain") do |_value, options| options[:domain] = :remote end add_option(:"Local/Remote", "-b", "--both", "Allow LOCAL and REMOTE operations") do |_value, options| options[:domain] = :both end add_bulk_threshold_option add_clear_sources_option add_source_option add_proxy_option add_update_sources_option end ## # Add the --bulk-threshold option def add_bulk_threshold_option add_option(:"Local/Remote", "-B", "--bulk-threshold COUNT", "Threshold for switching to bulk", "synchronization (default #{Gem.configuration.bulk_threshold})") do |value, _options| Gem.configuration.bulk_threshold = value.to_i end end ## # Add the --clear-sources option def add_clear_sources_option add_option(:"Local/Remote", "--clear-sources", "Clear the gem sources") do |_value, options| Gem.sources = nil options[:sources_cleared] = true end end ## # Add the --http-proxy option def add_proxy_option accept_uri_http add_option(:"Local/Remote", "-p", "--[no-]http-proxy [URL]", Gem::URI::HTTP, "Use HTTP proxy for remote operations") do |value, options| options[:http_proxy] = value == false ? :no_proxy : value Gem.configuration[:http_proxy] = options[:http_proxy] end end ## # Add the --source option def add_source_option accept_uri_http add_option(:"Local/Remote", "-s", "--source URL", Gem::URI::HTTP, "Append URL to list of remote gem sources") do |source, options| source << "/" unless source.end_with?("/") if options.delete :sources_cleared Gem.sources = [source] else Gem.sources << source unless Gem.sources.include?(source) end end end ## # Add the --update-sources option def add_update_sources_option add_option(:Deprecated, "-u", "--[no-]update-sources", "Update local source cache") do |value, _options| Gem.configuration.update_sources = value end end ## # Is fetching of local and remote information enabled? def both? options[:domain] == :both end ## # Is local fetching enabled? def local? options[:domain] == :local || options[:domain] == :both end ## # Is remote fetching enabled? def remote? options[:domain] == :remote || options[:domain] == :both end end PK!zNg\>\> rubygems/specification_policy.rbnu[# frozen_string_literal: true require_relative "user_interaction" class Gem::SpecificationPolicy include Gem::UserInteraction VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc: SPECIAL_CHARACTERS = /\A[#{Regexp.escape(".-_")}]+/ # :nodoc: VALID_URI_PATTERN = %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} # :nodoc: METADATA_LINK_KEYS = %w[ homepage_uri changelog_uri source_code_uri documentation_uri wiki_uri mailing_list_uri bug_tracker_uri download_uri funding_uri ].freeze # :nodoc: def initialize(specification) @warnings = 0 @specification = specification end ## # If set to true, run packaging-specific checks, as well. attr_accessor :packaging ## # Does a sanity check on the specification. # # Raises InvalidSpecificationException if the spec does not pass the # checks. # # It also performs some validations that do not raise but print warning # messages instead. def validate(strict = false) validate_required! validate_required_metadata! validate_optional(strict) if packaging || strict true end ## # Does a sanity check on the specification. # # Raises InvalidSpecificationException if the spec does not pass the # checks. # # Only runs checks that are considered necessary for the specification to be # functional. def validate_required! validate_nil_attributes validate_rubygems_version validate_required_attributes validate_name validate_require_paths @specification.keep_only_files_and_directories validate_non_files validate_self_inclusion_in_files_list validate_specification_version validate_platform validate_array_attributes validate_authors_field validate_licenses_length validate_duplicate_dependencies end def validate_required_metadata! validate_metadata validate_lazy_metadata end def validate_optional(strict) validate_licenses validate_permissions validate_values validate_dependencies validate_required_ruby_version validate_extensions validate_removed_attributes validate_unique_links if @warnings > 0 if strict error "specification has warnings" else alert_warning help_text end end end ## # Implementation for Specification#validate_for_resolution def validate_for_resolution validate_required! end ## # Implementation for Specification#validate_metadata def validate_metadata metadata = @specification.metadata unless Hash === metadata error "metadata must be a hash" end metadata.each do |key, value| entry = "metadata['#{key}']" unless key.is_a?(String) error "metadata keys must be a String" end if key.size > 128 error "metadata key is too large (#{key.size} > 128)" end unless value.is_a?(String) error "#{entry} value must be a String" end if value.size > 1024 error "#{entry} value is too large (#{value.size} > 1024)" end next unless METADATA_LINK_KEYS.include? key unless VALID_URI_PATTERN.match?(value) error "#{entry} has invalid link: #{value.inspect}" end end end ## # Checks that no duplicate dependencies are specified. def validate_duplicate_dependencies # :nodoc: # NOTE: see REFACTOR note in Gem::Dependency about types - this might be brittle seen = Gem::Dependency::TYPES.inject({}) {|types, type| types.merge({ type => {} }) } error_messages = [] @specification.dependencies.each do |dep| if prev = seen[dep.type][dep.name] error_messages << <<-MESSAGE duplicate dependency on #{dep}, (#{prev.requirement}) use: add_#{dep.type}_dependency \"#{dep.name}\", \"#{dep.requirement}\", \"#{prev.requirement}\" MESSAGE end seen[dep.type][dep.name] = dep end if error_messages.any? error error_messages.join end end ## # Checks that the gem does not depend on itself. # Checks that dependencies use requirements as we recommend. Warnings are # issued when dependencies are open-ended or overly strict for semantic # versioning. def validate_dependencies # :nodoc: warning_messages = [] @specification.dependencies.each do |dep| if dep.name == @specification.name # warn on self reference warning_messages << "Self referencing dependency is unnecessary and strongly discouraged." end prerelease_dep = dep.requirements_list.any? do |req| Gem::Requirement.new(req).prerelease? end warning_messages << "prerelease dependency on #{dep} is not recommended" if prerelease_dep && !@specification.version.prerelease? open_ended = dep.requirement.requirements.all? do |op, version| !version.prerelease? && [">", ">="].include?(op) end next unless open_ended op, dep_version = dep.requirement.requirements.first segments = dep_version.segments base = segments.first 2 recommendation = if [">", ">="].include?(op) && segments == [0] " use a bounded requirement, such as \"~> x.y\"" else bugfix = if op == ">" ", \"> #{dep_version}\"" elsif op == ">=" && base != segments ", \">= #{dep_version}\"" end " if #{dep.name} is semantically versioned, use:\n" \ " add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join "."}\"#{bugfix}" end warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n" end if warning_messages.any? warning_messages.each {|warning_message| warning warning_message } end end def validate_required_ruby_version if @specification.required_ruby_version.requirements == [Gem::Requirement::DefaultRequirement] warning "make sure you specify the oldest ruby version constraint (like \">= 3.0\") that you want your gem to support by setting the `required_ruby_version` gemspec attribute" end end ## # Issues a warning for each file to be packaged which is world-readable. # # Implementation for Specification#validate_permissions def validate_permissions return if Gem.win_platform? @specification.files.each do |file| next unless File.file?(file) next if File.stat(file).mode & 0o444 == 0o444 warning "#{file} is not world-readable" end @specification.executables.each do |name| exec = File.join @specification.bindir, name next unless File.file?(exec) next if File.stat(exec).executable? warning "#{exec} is not executable" end end private def validate_nil_attributes nil_attributes = Gem::Specification.non_nil_attributes.select do |attrname| @specification.instance_variable_get("@#{attrname}").nil? end return if nil_attributes.empty? error "#{nil_attributes.join ", "} must not be nil" end def validate_rubygems_version return unless packaging rubygems_version = @specification.rubygems_version return if rubygems_version == Gem::VERSION warning "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" @specification.rubygems_version = Gem::VERSION end def validate_required_attributes Gem::Specification.required_attributes.each do |symbol| unless @specification.send symbol error "missing value for attribute #{symbol}" end end end def validate_name name = @specification.name if !name.is_a?(String) error "invalid value for attribute name: \"#{name.inspect}\" must be a string" elsif !/[a-zA-Z]/.match?(name) error "invalid value for attribute name: #{name.dump} must include at least one letter" elsif !VALID_NAME_PATTERN.match?(name) error "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores" elsif SPECIAL_CHARACTERS.match?(name) error "invalid value for attribute name: #{name.dump} cannot begin with a period, dash, or underscore" end end def validate_require_paths return unless @specification.raw_require_paths.empty? error "specification must have at least one require_path" end def validate_non_files return unless packaging non_files = @specification.files.reject {|x| File.file?(x) || File.symlink?(x) } unless non_files.empty? error "[\"#{non_files.join "\", \""}\"] are not files" end end def validate_self_inclusion_in_files_list file_name = @specification.file_name return unless @specification.files.include?(file_name) error "#{@specification.full_name} contains itself (#{file_name}), check your files list" end def validate_specification_version return if @specification.specification_version.is_a?(Integer) error "specification_version must be an Integer (did you mean version?)" end def validate_platform platform = @specification.platform case platform when Gem::Platform, Gem::Platform::RUBY # ok else error "invalid platform #{platform.inspect}, see Gem::Platform" end end def validate_array_attributes Gem::Specification.array_attributes.each do |field| validate_array_attribute(field) end end def validate_array_attribute(field) val = @specification.send(field) klass = case field when :dependencies then Gem::Dependency else String end unless Array === val && val.all? {|x| x.is_a?(klass) || (field == :licenses && x.nil?) } error "#{field} must be an Array of #{klass}" end end def validate_authors_field return unless @specification.authors.empty? error "authors may not be empty" end def validate_licenses_length licenses = @specification.licenses licenses.each do |license| next if license.nil? if license.length > 64 error "each license must be 64 characters or less" end end end def validate_licenses licenses = @specification.licenses licenses.each do |license| next if Gem::Licenses.match?(license) || license.nil? license_id_deprecated = Gem::Licenses.deprecated_license_id?(license) exception_id_deprecated = Gem::Licenses.deprecated_exception_id?(license) suggestions = Gem::Licenses.suggestions(license) if license_id_deprecated main_message = "License identifier '#{license}' is deprecated" elsif exception_id_deprecated main_message = "Exception identifier at '#{license}' is deprecated" else main_message = "License identifier '#{license}' is invalid" end message = <<-WARNING #{main_message}. Use an identifier from https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license, or set it to nil if you don't want to specify a license. WARNING message += "Did you mean #{suggestions.map {|s| "'#{s}'" }.join(", ")}?\n" unless suggestions.nil? warning(message) end warning <<-WARNING if licenses.empty? licenses is empty, but is recommended. Use an license identifier from https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license, or set it to nil if you don't want to specify a license. WARNING end LAZY = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, "") LAZY_PATTERN = /\AFI XME|\ATO DO/x HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i def validate_lazy_metadata unless @specification.authors.grep(LAZY_PATTERN).empty? error "#{LAZY} is not an author" end unless Array(@specification.email).grep(LAZY_PATTERN).empty? error "#{LAZY} is not an email" end if LAZY_PATTERN.match?(@specification.description) error "#{LAZY} is not a description" end if LAZY_PATTERN.match?(@specification.summary) error "#{LAZY} is not a summary" end homepage = @specification.homepage # Make sure a homepage is valid HTTP/HTTPS URI if homepage && !homepage.empty? require_relative "vendor/uri/lib/uri" begin homepage_uri = Gem::URI.parse(homepage) unless [Gem::URI::HTTP, Gem::URI::HTTPS].member? homepage_uri.class error "\"#{homepage}\" is not a valid HTTP URI" end rescue Gem::URI::InvalidURIError error "\"#{homepage}\" is not a valid HTTP URI" end end end def validate_values %w[author homepage summary files].each do |attribute| validate_attribute_present(attribute) end if @specification.description == @specification.summary warning "description and summary are identical" end # TODO: raise at some given date warning "deprecated autorequire specified" if @specification.autorequire @specification.executables.each do |executable| validate_shebang_line_in(executable) end @specification.files.select {|f| File.symlink?(f) }.each do |file| warning "#{file} is a symlink, which is not supported on all platforms" end end def validate_attribute_present(attribute) value = @specification.send attribute warning("no #{attribute} specified") if value.nil? || value.empty? end def validate_shebang_line_in(executable) executable_path = File.join(@specification.bindir, executable) return if File.read(executable_path, 2) == "#!" warning "#{executable_path} is missing #! line" end def validate_removed_attributes # :nodoc: @specification.removed_method_calls.each do |attr| warning("#{attr} is deprecated and ignored. Please remove this from your gemspec to ensure that your gem continues to build in the future.") end end def validate_extensions # :nodoc: require_relative "ext" builder = Gem::Ext::Builder.new(@specification) validate_rake_extensions(builder) validate_rust_extensions(builder) end def validate_rust_extensions(builder) # :nodoc: rust_extension = @specification.extensions.any? {|s| builder.builder_for(s).is_a? Gem::Ext::CargoBuilder } missing_cargo_lock = !@specification.files.any? {|f| f.end_with?("Cargo.lock") } error <<-ERROR if rust_extension && missing_cargo_lock You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec. ERROR end def validate_rake_extensions(builder) # :nodoc: rake_extension = @specification.extensions.any? {|s| builder.builder_for(s) == Gem::Ext::RakeBuilder } rake_dependency = @specification.dependencies.any? {|d| d.name == "rake" && d.type == :runtime } warning <<-WARNING if rake_extension && !rake_dependency You have specified rake based extension, but rake is not added as runtime dependency. It is recommended to add rake as a runtime dependency in gemspec since there's no guarantee rake will be already installed. WARNING end def validate_unique_links links = @specification.metadata.slice(*METADATA_LINK_KEYS) grouped = links.group_by {|_key, uri| uri } grouped.each do |uri, copies| next unless copies.length > 1 keys = copies.map(&:first).join("\n ") warning <<~WARNING You have specified the uri: #{uri} for all of the following keys: #{keys} Only the first one will be shown on rubygems.org WARNING end end def warning(statement) # :nodoc: @warnings += 1 alert_warning statement end def error(statement) # :nodoc: raise Gem::InvalidSpecificationException, statement ensure alert_warning help_text end def help_text # :nodoc: "See https://guides.rubygems.org/specification-reference/ for help" end end PK!X00)rubygems/safe_marshal/visitors/visitor.rbnu[# frozen_string_literal: true module Gem::SafeMarshal::Visitors class Visitor def visit(target) send DISPATCH.fetch(target.class), target end private DISPATCH = Gem::SafeMarshal::Elements.constants.each_with_object({}) do |c, h| next if c == :Element klass = Gem::SafeMarshal::Elements.const_get(c) h[klass] = :"visit_#{klass.name.gsub("::", "_")}" h.default = :visit_unknown_element end.compare_by_identity.freeze private_constant :DISPATCH def visit_unknown_element(e) raise ArgumentError, "Attempting to visit unknown element #{e.inspect}" end def visit_Gem_SafeMarshal_Elements_Array(target) target.elements.each {|e| visit(e) } end def visit_Gem_SafeMarshal_Elements_Bignum(target); end def visit_Gem_SafeMarshal_Elements_False(target); end def visit_Gem_SafeMarshal_Elements_Float(target); end def visit_Gem_SafeMarshal_Elements_Hash(target) target.pairs.each do |k, v| visit(k) visit(v) end end def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(target) visit_Gem_SafeMarshal_Elements_Hash(target) visit(target.default) end def visit_Gem_SafeMarshal_Elements_Integer(target); end def visit_Gem_SafeMarshal_Elements_Nil(target); end def visit_Gem_SafeMarshal_Elements_Object(target) visit(target.name) end def visit_Gem_SafeMarshal_Elements_ObjectLink(target); end def visit_Gem_SafeMarshal_Elements_String(target); end def visit_Gem_SafeMarshal_Elements_Symbol(target); end def visit_Gem_SafeMarshal_Elements_SymbolLink(target); end def visit_Gem_SafeMarshal_Elements_True(target); end def visit_Gem_SafeMarshal_Elements_UserDefined(target) visit(target.name) end def visit_Gem_SafeMarshal_Elements_UserMarshal(target) visit(target.name) visit(target.data) end def visit_Gem_SafeMarshal_Elements_WithIvars(target) visit(target.object) target.ivars.each do |k, v| visit(k) visit(v) end end end end PK!Im0rubygems/safe_marshal/visitors/stream_printer.rbnu[# frozen_string_literal: true require_relative "visitor" module Gem::SafeMarshal module Visitors class StreamPrinter < Visitor def initialize(io, indent: "") @io = io @indent = indent @level = 0 end def visit(target) @io.write("#{@indent * @level}#{target.class}") target.instance_variables.each do |ivar| value = target.instance_variable_get(ivar) next if Elements::Element === value || Array === value @io.write(" #{ivar}=#{value.inspect}") end @io.write("\n") begin @level += 1 super ensure @level -= 1 end end end end end PK!G#,#,)rubygems/safe_marshal/visitors/to_ruby.rbnu[# frozen_string_literal: true require_relative "visitor" module Gem::SafeMarshal module Visitors class ToRuby < Visitor def initialize(permitted_classes:, permitted_symbols:, permitted_ivars:) @permitted_classes = permitted_classes @permitted_symbols = ["E"].concat(permitted_symbols).concat(permitted_classes) @permitted_ivars = permitted_ivars @objects = [] @symbols = [] @class_cache = {} @stack = ["root"] @stack_idx = 1 end def inspect # :nodoc: format("#<%s permitted_classes: %p permitted_symbols: %p permitted_ivars: %p>", self.class, @permitted_classes, @permitted_symbols, @permitted_ivars) end def visit(target) stack_idx = @stack_idx super ensure @stack_idx = stack_idx - 1 end private def push_stack(element) @stack[@stack_idx] = element @stack_idx += 1 end def visit_Gem_SafeMarshal_Elements_Array(a) array = register_object([]) elements = a.elements size = elements.size idx = 0 # not idiomatic, but there's a huge number of IMEMOs allocated here, so we avoid the block # because this is such a hot path when doing a bundle install with the full index until idx == size push_stack idx array << visit(elements[idx]) idx += 1 end array end def visit_Gem_SafeMarshal_Elements_Symbol(s) name = s.name raise UnpermittedSymbolError.new(symbol: name, stack: formatted_stack) unless @permitted_symbols.include?(name) visit_symbol_type(s) end def map_ivars(klass, ivars) stack_idx = @stack_idx ivars.map.with_index do |(k, v), i| @stack_idx = stack_idx push_stack "ivar_" push_stack i k = resolve_ivar(klass, k) @stack_idx = stack_idx push_stack k next k, visit(v) end end def visit_Gem_SafeMarshal_Elements_WithIvars(e) object_offset = @objects.size push_stack "object" object = visit(e.object) ivars = map_ivars(object.class, e.ivars) case e.object when Elements::UserDefined if object.class == ::Time internal = [] ivars.reject! do |k, v| case k when :offset, :zone, :nano_num, :nano_den, :submicro internal << [k, v] true else false end end s = e.object.binary_string marshal_string = "\x04\bIu:\tTime".b marshal_string.concat(s.size + 5) marshal_string << s marshal_string.concat(internal.size + 5) internal.each do |k, v| marshal_string.concat(":") marshal_string.concat(k.size + 5) marshal_string.concat(k.to_s) dumped = Marshal.dump(v) dumped[0, 2] = "" marshal_string.concat(dumped) end object = @objects[object_offset] = Marshal.load(marshal_string) end when Elements::String enc = nil ivars.reject! do |k, v| case k when :E case v when TrueClass enc = "UTF-8" when FalseClass enc = "US-ASCII" else raise FormatError, "Unexpected value for String :E #{v.inspect}" end when :encoding enc = v else next false end true end object.force_encoding(enc) if enc end ivars.each do |k, v| object.instance_variable_set k, v end object end def visit_Gem_SafeMarshal_Elements_Hash(o) hash = register_object({}) o.pairs.each_with_index do |(k, v), i| push_stack i k = visit(k) push_stack k hash[k] = visit(v) end hash end def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(o) hash = visit_Gem_SafeMarshal_Elements_Hash(o) push_stack :default hash.default = visit(o.default) hash end def visit_Gem_SafeMarshal_Elements_Object(o) register_object(resolve_class(o.name).allocate) end def visit_Gem_SafeMarshal_Elements_ObjectLink(o) @objects[o.offset] end def visit_Gem_SafeMarshal_Elements_SymbolLink(o) @symbols[o.offset] end def visit_Gem_SafeMarshal_Elements_UserDefined(o) register_object(call_method(resolve_class(o.name), :_load, o.binary_string)) end def visit_Gem_SafeMarshal_Elements_UserMarshal(o) klass = resolve_class(o.name) compat = COMPAT_CLASSES.fetch(klass, nil) idx = @objects.size object = register_object(call_method(compat || klass, :allocate)) push_stack :data ret = call_method(object, :marshal_load, visit(o.data)) if compat object = @objects[idx] = ret end object end def visit_Gem_SafeMarshal_Elements_Integer(i) i.int end def visit_Gem_SafeMarshal_Elements_Nil(_) nil end def visit_Gem_SafeMarshal_Elements_True(_) true end def visit_Gem_SafeMarshal_Elements_False(_) false end def visit_Gem_SafeMarshal_Elements_String(s) register_object(+s.str) end def visit_Gem_SafeMarshal_Elements_Float(f) case f.string when "inf" ::Float::INFINITY when "-inf" -::Float::INFINITY when "nan" ::Float::NAN else f.string.to_f end end def visit_Gem_SafeMarshal_Elements_Bignum(b) result = 0 b.data.each_byte.with_index do |byte, exp| result += (byte * 2**(exp * 8)) end case b.sign when 43 # ?+ result when 45 # ?- -result else raise FormatError, "Unexpected sign for Bignum #{b.sign.chr.inspect} (#{b.sign})" end end def visit_Gem_SafeMarshal_Elements_UserClass(r) if resolve_class(r.name) == ::Hash && r.wrapped_object.is_a?(Elements::Hash) hash = register_object({}.compare_by_identity) o = r.wrapped_object o.pairs.each_with_index do |(k, v), i| push_stack i k = visit(k) push_stack k hash[k] = visit(v) end if o.is_a?(Elements::HashWithDefaultValue) push_stack :default hash.default = visit(o.default) end hash else raise UnsupportedError.new("Unsupported user class #{resolve_class(r.name)} in marshal stream", stack: formatted_stack) end end def resolve_class(n) @class_cache[n] ||= begin to_s = resolve_symbol_name(n) raise UnpermittedClassError.new(name: to_s, stack: formatted_stack) unless @permitted_classes.include?(to_s) visit_symbol_type(n) begin ::Object.const_get(to_s) rescue NameError raise ArgumentError, "Undefined class #{to_s.inspect}" end end end class RationalCompat def marshal_load(s) num, den = s raise ArgumentError, "Expected 2 ints" unless s.size == 2 && num.is_a?(Integer) && den.is_a?(Integer) Rational(num, den) end end private_constant :RationalCompat COMPAT_CLASSES = {}.tap do |h| h[Rational] = RationalCompat end.compare_by_identity.freeze private_constant :COMPAT_CLASSES def resolve_ivar(klass, name) to_s = resolve_symbol_name(name) raise UnpermittedIvarError.new(symbol: to_s, klass: klass, stack: formatted_stack) unless @permitted_ivars.fetch(klass.name, [].freeze).include?(to_s) visit_symbol_type(name) end def visit_symbol_type(element) case element when Elements::Symbol sym = element.name.to_sym @symbols << sym sym when Elements::SymbolLink visit_Gem_SafeMarshal_Elements_SymbolLink(element) end end # This is a hot method, so avoid respond_to? checks on every invocation if :read.respond_to?(:name) def resolve_symbol_name(element) case element when Elements::Symbol element.name when Elements::SymbolLink visit_Gem_SafeMarshal_Elements_SymbolLink(element).name else raise FormatError, "Expected symbol or symbol link, got #{element.inspect} @ #{formatted_stack.join(".")}" end end else def resolve_symbol_name(element) case element when Elements::Symbol element.name when Elements::SymbolLink visit_Gem_SafeMarshal_Elements_SymbolLink(element).to_s else raise FormatError, "Expected symbol or symbol link, got #{element.inspect} @ #{formatted_stack.join(".")}" end end end def register_object(o) @objects << o o end def call_method(receiver, method, *args) receiver.__send__(method, *args) rescue NoMethodError => e raise unless e.receiver == receiver raise MethodCallError, "Unable to call #{method.inspect} on #{receiver.inspect}, perhaps it is a class using marshal compat, which is not visible in ruby? #{e}" end def formatted_stack formatted = [] @stack[0, @stack_idx].each do |e| if e.is_a?(Integer) if formatted.last == "ivar_" formatted[-1] = "ivar_#{e}" else formatted << "[#{e}]" end else formatted << e end end formatted end class Error < StandardError end class UnpermittedSymbolError < Error def initialize(symbol:, stack:) @symbol = symbol @stack = stack super "Attempting to load unpermitted symbol #{symbol.inspect} @ #{stack.join "."}" end end class UnpermittedIvarError < Error def initialize(symbol:, klass:, stack:) @symbol = symbol @klass = klass @stack = stack super "Attempting to set unpermitted ivar #{symbol.inspect} on object of class #{klass} @ #{stack.join "."}" end end class UnpermittedClassError < Error def initialize(name:, stack:) @name = name @stack = stack super "Attempting to load unpermitted class #{name.inspect} @ #{stack.join "."}" end end class UnsupportedError < Error def initialize(message, stack:) super "#{message} @ #{stack.join "."}" end end class FormatError < Error end class MethodCallError < Error end end end end PK!1/ ""rubygems/safe_marshal/reader.rbnu[# frozen_string_literal: true require_relative "elements" module Gem module SafeMarshal class Reader class Error < StandardError end class UnsupportedVersionError < Error end class UnconsumedBytesError < Error end class NotImplementedError < Error end class EOFError < Error end def initialize(io) @io = io end def read! read_header root = read_element raise UnconsumedBytesError unless @io.eof? root end private MARSHAL_VERSION = [Marshal::MAJOR_VERSION, Marshal::MINOR_VERSION].map(&:chr).join.freeze private_constant :MARSHAL_VERSION def read_header v = @io.read(2) raise UnsupportedVersionError, "Unsupported marshal version #{v.bytes.map(&:ord).join(".")}, expected #{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" unless v == MARSHAL_VERSION end def read_byte @io.getbyte end def read_integer b = read_byte case b when 0x00 0 when 0x01 read_byte when 0x02 read_byte | (read_byte << 8) when 0x03 read_byte | (read_byte << 8) | (read_byte << 16) when 0x04 read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24) when 0xFC read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24) | -0x100000000 when 0xFD read_byte | (read_byte << 8) | (read_byte << 16) | -0x1000000 when 0xFE read_byte | (read_byte << 8) | -0x10000 when 0xFF read_byte | -0x100 when nil raise EOFError, "Unexpected EOF" else signed = (b ^ 128) - 128 if b >= 128 signed + 5 else signed - 5 end end end def read_element type = read_byte case type when 34 then read_string # ?" when 48 then read_nil # ?0 when 58 then read_symbol # ?: when 59 then read_symbol_link # ?; when 64 then read_object_link # ?@ when 70 then read_false # ?F when 73 then read_object_with_ivars # ?I when 84 then read_true # ?T when 85 then read_user_marshal # ?U when 91 then read_array # ?[ when 102 then read_float # ?f when 105 then Elements::Integer.new(read_integer) # ?i when 108 then read_bignum # ?l when 111 then read_object # ?o when 117 then read_user_defined # ?u when 123 then read_hash # ?{ when 125 then read_hash_with_default_value # ?} when 101 then read_extended_object # ?e when 99 then read_class # ?c when 109 then read_module # ?m when 77 then read_class_or_module # ?M when 100 then read_data # ?d when 47 then read_regexp # ?/ when 83 then read_struct # ?S when 67 then read_user_class # ?C when nil raise EOFError, "Unexpected EOF" else raise Error, "Unknown marshal type discriminator #{type.chr.inspect} (#{type})" end end STRING_E_SYMBOL = Elements::Symbol.new("E").freeze private_constant :STRING_E_SYMBOL def read_symbol len = read_integer if len == 1 byte = read_byte if byte == 69 # ?E STRING_E_SYMBOL else Elements::Symbol.new(byte.chr) end else name = -@io.read(len) Elements::Symbol.new(name) end end EMPTY_STRING = Elements::String.new("".b.freeze).freeze private_constant :EMPTY_STRING def read_string length = read_integer return EMPTY_STRING if length == 0 str = @io.read(length) Elements::String.new(str) end def read_true Elements::True::TRUE end def read_false Elements::False::FALSE end def read_user_defined name = read_element binary_string = @io.read(read_integer) Elements::UserDefined.new(name, binary_string) end EMPTY_ARRAY = Elements::Array.new([].freeze).freeze private_constant :EMPTY_ARRAY def read_array length = read_integer return EMPTY_ARRAY if length == 0 elements = Array.new(length) do read_element end Elements::Array.new(elements) end def read_object_with_ivars object = read_element ivars = Array.new(read_integer) do [read_element, read_element] end Elements::WithIvars.new(object, ivars) end def read_symbol_link offset = read_integer Elements::SymbolLink.new(offset) end def read_user_marshal name = read_element data = read_element Elements::UserMarshal.new(name, data) end # profiling bundle install --full-index shows that # offset 6 is by far the most common object link, # so we special case it to avoid allocating a new # object a third of the time. # the following are all the object links that # appear more than 10000 times in my profiling OBJECT_LINKS = { 6 => Elements::ObjectLink.new(6).freeze, 30 => Elements::ObjectLink.new(30).freeze, 81 => Elements::ObjectLink.new(81).freeze, 34 => Elements::ObjectLink.new(34).freeze, 38 => Elements::ObjectLink.new(38).freeze, 50 => Elements::ObjectLink.new(50).freeze, 91 => Elements::ObjectLink.new(91).freeze, 42 => Elements::ObjectLink.new(42).freeze, 46 => Elements::ObjectLink.new(46).freeze, 150 => Elements::ObjectLink.new(150).freeze, 100 => Elements::ObjectLink.new(100).freeze, 104 => Elements::ObjectLink.new(104).freeze, 108 => Elements::ObjectLink.new(108).freeze, 242 => Elements::ObjectLink.new(242).freeze, 246 => Elements::ObjectLink.new(246).freeze, 139 => Elements::ObjectLink.new(139).freeze, 143 => Elements::ObjectLink.new(143).freeze, 114 => Elements::ObjectLink.new(114).freeze, 308 => Elements::ObjectLink.new(308).freeze, 200 => Elements::ObjectLink.new(200).freeze, 54 => Elements::ObjectLink.new(54).freeze, 62 => Elements::ObjectLink.new(62).freeze, 1_286_245 => Elements::ObjectLink.new(1_286_245).freeze, }.freeze private_constant :OBJECT_LINKS def read_object_link offset = read_integer OBJECT_LINKS[offset] || Elements::ObjectLink.new(offset) end EMPTY_HASH = Elements::Hash.new([].freeze).freeze private_constant :EMPTY_HASH def read_hash length = read_integer return EMPTY_HASH if length == 0 pairs = Array.new(length) do [read_element, read_element] end Elements::Hash.new(pairs) end def read_hash_with_default_value pairs = Array.new(read_integer) do [read_element, read_element] end default = read_element Elements::HashWithDefaultValue.new(pairs, default) end def read_object name = read_element object = Elements::Object.new(name) ivars = Array.new(read_integer) do [read_element, read_element] end Elements::WithIvars.new(object, ivars) end def read_nil Elements::Nil::NIL end def read_float string = @io.read(read_integer) Elements::Float.new(string) end def read_bignum sign = read_byte data = @io.read(read_integer * 2) Elements::Bignum.new(sign, data) end def read_extended_object raise NotImplementedError, "Reading Marshal objects of type extended_object is not implemented" end def read_class raise NotImplementedError, "Reading Marshal objects of type class is not implemented" end def read_module raise NotImplementedError, "Reading Marshal objects of type module is not implemented" end def read_class_or_module raise NotImplementedError, "Reading Marshal objects of type class_or_module is not implemented" end def read_data raise NotImplementedError, "Reading Marshal objects of type data is not implemented" end def read_regexp raise NotImplementedError, "Reading Marshal objects of type regexp is not implemented" end def read_struct raise NotImplementedError, "Reading Marshal objects of type struct is not implemented" end def read_user_class name = read_element wrapped_object = read_element Elements::UserClass.new(name, wrapped_object) end end end end PK!HJ8 !rubygems/safe_marshal/elements.rbnu[# frozen_string_literal: true module Gem module SafeMarshal module Elements class Element end class Symbol < Element def initialize(name) @name = name end attr_reader :name end class UserDefined < Element def initialize(name, binary_string) @name = name @binary_string = binary_string end attr_reader :name, :binary_string end class UserMarshal < Element def initialize(name, data) @name = name @data = data end attr_reader :name, :data end class String < Element def initialize(str) @str = str end attr_reader :str end class Hash < Element def initialize(pairs) @pairs = pairs end attr_reader :pairs end class HashWithDefaultValue < Hash def initialize(pairs, default) super(pairs) @default = default end attr_reader :default end class Array < Element def initialize(elements) @elements = elements end attr_reader :elements end class Integer < Element def initialize(int) @int = int end attr_reader :int end class True < Element def initialize end TRUE = new.freeze end class False < Element def initialize end FALSE = new.freeze end class WithIvars < Element def initialize(object, ivars) @object = object @ivars = ivars end attr_reader :object, :ivars end class Object < Element def initialize(name) @name = name end attr_reader :name end class Nil < Element NIL = new.freeze end class ObjectLink < Element def initialize(offset) @offset = offset end attr_reader :offset end class SymbolLink < Element def initialize(offset) @offset = offset end attr_reader :offset end class Float < Element def initialize(string) @string = string end attr_reader :string end class Bignum < Element # rubocop:disable Lint/UnifiedInteger def initialize(sign, data) @sign = sign @data = data end attr_reader :sign, :data end class UserClass < Element def initialize(name, wrapped_object) @name = name @wrapped_object = wrapped_object end attr_reader :name, :wrapped_object end end end end PK!?rubygems/gemspec_helpers.rbnu[# frozen_string_literal: true require_relative "../rubygems" ## # Mixin methods for commands that work with gemspecs. module Gem::GemspecHelpers def find_gemspec(glob = "*.gemspec") gemspecs = Dir.glob(glob).sort if gemspecs.size > 1 alert_error "Multiple gemspecs found: #{gemspecs}, please specify one" terminate_interaction(1) end gemspecs.first end end PK! 7!++rubygems/uninstaller.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require "fileutils" require_relative "../rubygems" require_relative "installer_uninstaller_utils" require_relative "dependency_list" require_relative "rdoc" require_relative "user_interaction" ## # An Uninstaller. # # The uninstaller fires pre and post uninstall hooks. Hooks can be added # either through a rubygems_plugin.rb file in an installed gem or via a # rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb # file. See Gem.pre_uninstall and Gem.post_uninstall for details. class Gem::Uninstaller include Gem::UserInteraction include Gem::InstallerUninstallerUtils ## # The directory a gem's executables will be installed into attr_reader :bin_dir ## # The gem repository the gem will be uninstalled from attr_reader :gem_home ## # The Gem::Specification for the gem being uninstalled, only set during # #uninstall_gem attr_reader :spec ## # Constructs an uninstaller that will uninstall +gem+ def initialize(gem, options = {}) # TODO: document the valid options @gem = gem @version = options[:version] || Gem::Requirement.default @install_dir = options[:install_dir] @gem_home = File.realpath(@install_dir || Gem.dir) @user_dir = File.exist?(Gem.user_dir) ? File.realpath(Gem.user_dir) : Gem.user_dir @force_executables = options[:executables] @force_all = options[:all] @force_ignore = options[:ignore] @bin_dir = options[:bin_dir] @format_executable = options[:format_executable] @abort_on_dependent = options[:abort_on_dependent] # Indicate if development dependencies should be checked when # uninstalling. (default: false) # @check_dev = options[:check_dev] if options[:force] @force_all = true @force_ignore = true end # only add user directory if install_dir is not set @user_install = false @user_install = options[:user_install] unless @install_dir # Optimization: populated during #uninstall @default_specs_matching_uninstall_params = [] end ## # Performs the uninstall of the gem. This removes the spec, the Gem # directory, and the cached .gem file. def uninstall dependency = Gem::Dependency.new @gem, @version list = [] specification_record.stubs.each do |spec| next unless dependency.matches_spec? spec list << spec end if list.empty? raise Gem::InstallError, "gem #{@gem.inspect} is not installed" end default_specs, list = list.partition(&:default_gem?) warn_cannot_uninstall_default_gems(default_specs - list) @default_specs_matching_uninstall_params = default_specs.map(&:to_spec) list, other_repo_specs = list.partition do |spec| @gem_home == spec.base_dir || (@user_install && spec.base_dir == @user_dir) end list.sort! if list.empty? return unless other_repo_specs.any? other_repos = other_repo_specs.map(&:base_dir).uniq message = ["#{@gem} is not installed in GEM_HOME, try:"] message.concat other_repos.map {|repo| "\tgem uninstall -i #{repo} #{@gem}" } raise Gem::InstallError, message.join("\n") elsif @force_all remove_all list elsif list.size > 1 gem_names = list.map(&:full_name_with_location) gem_names << "All versions" say _, index = choose_from_list "Select gem to uninstall:", gem_names if index == list.size remove_all list elsif index && index >= 0 && index < list.size uninstall_gem list[index] else say "Error: must enter a number [1-#{list.size + 1}]" end else uninstall_gem list.first end end ## # Uninstalls gem +spec+ def uninstall_gem(stub) spec = stub.to_spec @spec = spec unless dependencies_ok? spec if abort_on_dependent? || !ask_if_ok(spec) raise Gem::DependencyRemovalException, "Uninstallation aborted due to dependent gem(s)" end end Gem.pre_uninstall_hooks.each do |hook| hook.call self end remove_executables @spec remove_plugins @spec remove @spec specification_record.remove_spec(stub) regenerate_plugins Gem.post_uninstall_hooks.each do |hook| hook.call self end @spec = nil end ## # Removes installed executables and batch files (windows only) for +spec+. def remove_executables(spec) return if spec.executables.empty? || default_spec_matches?(spec) executables = spec.executables.clone # Leave any executables created by other installed versions # of this gem installed. list = Gem::Specification.find_all do |s| s.name == spec.name && s.version != spec.version end list.each do |s| s.executables.each do |exe_name| executables.delete exe_name end end return if executables.empty? executables = executables.map {|exec| formatted_program_filename exec } remove = if @force_executables.nil? ask_yes_no("Remove executables:\n" \ "\t#{executables.join ", "}\n\n" \ "in addition to the gem?", true) else @force_executables end if remove bin_dir = @bin_dir || Gem.bindir(spec.base_dir) raise Gem::FilePermissionError, bin_dir unless File.writable? bin_dir executables.each do |exe_name| say "Removing #{exe_name}" exe_file = File.join bin_dir, exe_name safe_delete { FileUtils.rm exe_file } safe_delete { FileUtils.rm "#{exe_file}.bat" } end else say "Executables and scripts will remain installed." end end ## # Removes all gems in +list+. # # NOTE: removes uninstalled gems from +list+. def remove_all(list) list.each {|spec| uninstall_gem spec } end ## # spec:: the spec of the gem to be uninstalled def remove(spec) unless path_ok?(@gem_home, spec) || (@user_install && path_ok?(@user_dir, spec)) e = Gem::GemNotInHomeException.new \ "Gem '#{spec.full_name}' is not installed in directory #{@gem_home}" e.spec = spec raise e end raise Gem::FilePermissionError, spec.base_dir unless File.writable?(spec.base_dir) full_gem_path = spec.full_gem_path exclusions = [] if default_spec_matches?(spec) && spec.executables.any? exclusions = spec.executables.map {|exe| File.join(spec.bin_dir, exe) } exclusions << File.dirname(exclusions.last) until exclusions.last == full_gem_path end safe_delete { rm_r full_gem_path, exclusions: exclusions } safe_delete { FileUtils.rm_r spec.extension_dir } old_platform_name = spec.original_name gem = spec.cache_file gem = File.join(spec.cache_dir, "#{old_platform_name}.gem") unless File.exist? gem safe_delete { FileUtils.rm_r gem } begin Gem::RDoc.new(spec).remove rescue NameError end gemspec = spec.spec_file unless File.exist? gemspec gemspec = File.join(File.dirname(gemspec), "#{old_platform_name}.gemspec") end safe_delete { FileUtils.rm_r gemspec } announce_deletion_of(spec) end ## # Remove any plugin wrappers for +spec+. def remove_plugins(spec) # :nodoc: return if spec.plugins.empty? remove_plugins_for(spec, plugin_dir_for(spec)) end ## # Regenerates plugin wrappers after removal. def regenerate_plugins latest = specification_record.latest_spec_for(@spec.name) return if latest.nil? regenerate_plugins_for(latest, plugin_dir_for(@spec)) end ## # Is +spec+ in +gem_dir+? def path_ok?(gem_dir, spec) full_path = File.join gem_dir, "gems", spec.full_name original_path = File.join gem_dir, "gems", spec.original_name full_path == spec.full_gem_path || original_path == spec.full_gem_path end ## # Returns true if it is OK to remove +spec+ or this is a forced # uninstallation. def dependencies_ok?(spec) # :nodoc: return true if @force_ignore deplist = Gem::DependencyList.from_specs deplist.ok_to_remove?(spec.full_name, @check_dev) end ## # Should the uninstallation abort if a dependency will go unsatisfied? # # See ::new. def abort_on_dependent? # :nodoc: @abort_on_dependent end ## # Asks if it is OK to remove +spec+. Returns true if it is OK. def ask_if_ok(spec) # :nodoc: msg = [""] msg << "You have requested to uninstall the gem:" msg << "\t#{spec.full_name}" msg << "" siblings = Gem::Specification.select do |s| s.name == spec.name && s.full_name != spec.full_name end spec.dependent_gems(@check_dev).each do |dep_spec, dep, _satlist| unless siblings.any? {|s| s.satisfies_requirement? dep } msg << "#{dep_spec.name}-#{dep_spec.version} depends on #{dep}" end end msg << "If you remove this gem, these dependencies will not be met." msg << "Continue with Uninstall?" ask_yes_no(msg.join("\n"), false) end ## # Returns the formatted version of the executable +filename+ def formatted_program_filename(filename) # :nodoc: # TODO perhaps the installer should leave a small manifest # of what it did for us to find rather than trying to recreate # it again. if @format_executable require_relative "installer" Gem::Installer.exec_format % File.basename(filename) else filename end end def safe_delete(&block) block.call rescue Errno::ENOENT nil rescue Errno::EPERM e = Gem::UninstallError.new e.spec = @spec raise e end private def rm_r(path, exclusions:) FileUtils::Entry_.new(path).postorder_traverse do |ent| ent.remove unless exclusions.include?(ent.path) end end def specification_record @specification_record ||= @install_dir ? Gem::SpecificationRecord.from_path(@install_dir) : Gem::Specification.specification_record end def announce_deletion_of(spec) name = spec.full_name say "Successfully uninstalled #{name}" if default_spec_matches?(spec) say( "There was both a regular copy and a default copy of #{name}. The " \ "regular copy was successfully uninstalled, but the default copy " \ "was left around because default gems can't be removed." ) end end # @return true if the specs of any default gems are `==` to the given `spec`. def default_spec_matches?(spec) !default_specs_that_match(spec).empty? end # @return [Array] specs of default gems that are `==` to the given `spec`. def default_specs_that_match(spec) @default_specs_matching_uninstall_params.select {|default_spec| spec == default_spec } end def warn_cannot_uninstall_default_gems(specs) specs.each do |spec| say "Gem #{spec.full_name} cannot be uninstalled because it is a default gem" end end def plugin_dir_for(spec) Gem.plugindir(spec.base_dir) end end PK!Vrubygems/uri_formatter.rbnu[# frozen_string_literal: true ## # The UriFormatter handles URIs from user-input and escaping. # # uf = Gem::UriFormatter.new 'example.com' # # p uf.normalize #=> 'http://example.com' class Gem::UriFormatter ## # The URI to be formatted. attr_reader :uri ## # Creates a new URI formatter for +uri+. def initialize(uri) require "cgi" @uri = uri end ## # Escapes the #uri for use as a CGI parameter def escape return unless @uri CGI.escape @uri end ## # Normalize the URI by adding "http://" if it is missing. def normalize /^(https?|ftp|file):/i.match?(@uri) ? @uri : "http://#{@uri}" end ## # Unescapes the #uri which came from a CGI parameter def unescape return unless @uri CGI.unescape @uri end end PK!T$rubygems/package/tar_reader/entry.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## # Class for reading entries out of a tar file class Gem::Package::TarReader::Entry ## # Creates a new tar entry for +header+ that will be read from +io+ # If a block is given, the entry is yielded and then closed. def self.open(header, io, &block) entry = new header, io return entry unless block_given? begin yield entry ensure entry.close end end ## # Header for this tar entry attr_reader :header ## # Creates a new tar entry for +header+ that will be read from +io+ def initialize(header, io) @closed = false @header = header @io = io @orig_pos = @io.pos @end_pos = @orig_pos + @header.size @read = 0 end def check_closed # :nodoc: raise IOError, "closed #{self.class}" if closed? end ## # Number of bytes read out of the tar entry def bytes_read @read end ## # Closes the tar entry def close return if closed? # Seek to the end of the entry if it wasn't fully read seek(0, IO::SEEK_END) # discard trailing zeros skip = (512 - (@header.size % 512)) % 512 @io.read(skip) @closed = true nil end ## # Is the tar entry closed? def closed? @closed end ## # Are we at the end of the tar entry? def eof? check_closed @read >= @header.size end ## # Full name of the tar entry def full_name if @header.prefix != "" File.join @header.prefix, @header.name else @header.name end rescue ArgumentError => e raise unless e.message == "string contains null byte" raise Gem::Package::TarInvalidError, "tar is corrupt, name contains null byte" end ## # Read one byte from the tar entry def getc return nil if eof? ret = @io.getc @read += 1 if ret ret end ## # Is this tar entry a directory? def directory? @header.typeflag == "5" end ## # Is this tar entry a file? def file? @header.typeflag == "0" end ## # Is this tar entry a symlink? def symlink? @header.typeflag == "2" end ## # The position in the tar entry def pos check_closed bytes_read end ## # Seek to the position in the tar entry def pos=(new_pos) seek(new_pos, IO::SEEK_SET) end def size @header.size end alias_method :length, :size ## # Reads +maxlen+ bytes from the tar file entry, or the rest of the entry if nil def read(maxlen = nil) if eof? return maxlen.to_i.zero? ? "" : nil end max_read = [maxlen, @header.size - @read].compact.min ret = @io.read max_read if ret.nil? return maxlen ? nil : "" # IO.read returns nil on EOF with len argument end @read += ret.size ret end def readpartial(maxlen, outbuf = "".b) if eof? && maxlen > 0 raise EOFError, "end of file reached" end max_read = [maxlen, @header.size - @read].min @io.readpartial(max_read, outbuf) @read += outbuf.size outbuf end ## # Seeks to +offset+ bytes into the tar file entry # +whence+ can be IO::SEEK_SET, IO::SEEK_CUR, or IO::SEEK_END def seek(offset, whence = IO::SEEK_SET) check_closed new_pos = case whence when IO::SEEK_SET then @orig_pos + offset when IO::SEEK_CUR then @io.pos + offset when IO::SEEK_END then @end_pos + offset else raise ArgumentError, "invalid whence" end if new_pos < @orig_pos new_pos = @orig_pos elsif new_pos > @end_pos new_pos = @end_pos end pending = new_pos - @io.pos return 0 if pending == 0 if @io.respond_to?(:seek) begin # avoid reading if the @io supports seeking @io.seek new_pos, IO::SEEK_SET pending = 0 rescue Errno::EINVAL end end # if seeking isn't supported or failed # negative seek requires that we rewind and read if pending < 0 @io.rewind pending = new_pos end while pending > 0 do size_read = @io.read([pending, 4096].min)&.size raise(EOFError, "end of file reached") if size_read.nil? pending -= size_read end @read = @io.pos - @orig_pos 0 end ## # Rewinds to the beginning of the tar file entry def rewind check_closed seek(0, IO::SEEK_SET) end end PK!|@rubygems/package/tar_reader.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## # TarReader reads tar files and allows iteration over their items class Gem::Package::TarReader include Enumerable ## # Creates a new TarReader on +io+ and yields it to the block, if given. def self.new(io) reader = super return reader unless block_given? begin yield reader ensure reader.close end nil end ## # Creates a new tar file reader on +io+ which needs to respond to #pos, # #eof?, #read, #getc and #pos= def initialize(io) @io = io @init_pos = io.pos end ## # Close the tar file def close end ## # Iterates over files in the tarball yielding each entry def each return enum_for __method__ unless block_given? until @io.eof? do begin header = Gem::Package::TarHeader.from @io rescue ArgumentError => e # Specialize only exceptions from Gem::Package::TarHeader.strict_oct raise e unless e.message.match?(/ is not an octal string$/) raise Gem::Package::TarInvalidError, e.message end return if header.empty? entry = Gem::Package::TarReader::Entry.new header, @io yield entry entry.close end end alias_method :each_entry, :each ## # NOTE: Do not call #rewind during #each def rewind if @init_pos == 0 @io.rewind else @io.pos = @init_pos end end ## # Seeks through the tar file until it finds the +entry+ with +name+ and # yields it. Rewinds the tar file to the beginning when the block # terminates. def seek(name) # :yields: entry found = find do |entry| entry.full_name == name end return unless found yield found ensure rewind end end require_relative "tar_reader/entry" PK!rubygems/package/tar_writer.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## # Allows writing of tar files class Gem::Package::TarWriter class FileOverflow < StandardError; end ## # IO wrapper that allows writing a limited amount of data class BoundedStream ## # Maximum number of bytes that can be written attr_reader :limit ## # Number of bytes written attr_reader :written ## # Wraps +io+ and allows up to +limit+ bytes to be written def initialize(io, limit) @io = io @limit = limit @written = 0 end ## # Writes +data+ onto the IO, raising a FileOverflow exception if the # number of bytes will be more than #limit def write(data) if data.bytesize + @written > @limit raise FileOverflow, "You tried to feed more data than fits in the file." end @io.write data @written += data.bytesize data.bytesize end end ## # IO wrapper that provides only #write class RestrictedStream ## # Creates a new RestrictedStream wrapping +io+ def initialize(io) @io = io end ## # Writes +data+ onto the IO def write(data) @io.write data end end ## # Creates a new TarWriter, yielding it if a block is given def self.new(io) writer = super return writer unless block_given? begin yield writer ensure writer.close end nil end ## # Creates a new TarWriter that will write to +io+ def initialize(io) @io = io @closed = false end ## # Adds file +name+ with permissions +mode+, and yields an IO for writing the # file to def add_file(name, mode) # :yields: io check_closed name, prefix = split_name name init_pos = @io.pos @io.write Gem::Package::TarHeader::EMPTY_HEADER # placeholder for the header yield RestrictedStream.new(@io) if block_given? size = @io.pos - init_pos - 512 remainder = (512 - (size % 512)) % 512 @io.write "\0" * remainder final_pos = @io.pos @io.pos = init_pos header = Gem::Package::TarHeader.new name: name, mode: mode, size: size, prefix: prefix, mtime: Gem.source_date_epoch @io.write header @io.pos = final_pos self end ## # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing # the file. The +digest_algorithm+ is written to a read-only +name+.sum # file following the given file contents containing the digest name and # hexdigest separated by a tab. # # The created digest object is returned. def add_file_digest(name, mode, digest_algorithms) # :yields: io digests = digest_algorithms.map do |digest_algorithm| digest = digest_algorithm.new digest_name = if digest.respond_to? :name digest.name else digest_algorithm.class.name[/::([^:]+)\z/, 1] end [digest_name, digest] end digests = Hash[*digests.flatten] add_file name, mode do |io| Gem::Package::DigestIO.wrap io, digests do |digest_io| yield digest_io end end digests end ## # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing # the file. The +signer+ is used to add a digest file using its # digest_algorithm per add_file_digest and a cryptographic signature in # +name+.sig. If the signer has no key only the checksum file is added. # # Returns the digest. def add_file_signed(name, mode, signer) digest_algorithms = [ signer.digest_algorithm, Gem::Security.create_digest("SHA512"), ].compact.uniq digests = add_file_digest name, mode, digest_algorithms do |io| yield io end signature_digest = digests.values.compact.find do |digest| digest_name = if digest.respond_to? :name digest.name else digest.class.name[/::([^:]+)\z/, 1] end digest_name == signer.digest_name end raise "no #{signer.digest_name} in #{digests.values.compact}" unless signature_digest if signer.key signature = signer.sign signature_digest.digest add_file_simple "#{name}.sig", 0o444, signature.length do |io| io.write signature end end digests end ## # Add file +name+ with permissions +mode+ +size+ bytes long. Yields an IO # to write the file to. def add_file_simple(name, mode, size) # :yields: io check_closed name, prefix = split_name name header = Gem::Package::TarHeader.new(name: name, mode: mode, size: size, prefix: prefix, mtime: Gem.source_date_epoch).to_s @io.write header os = BoundedStream.new @io, size yield os if block_given? min_padding = size - os.written @io.write("\0" * min_padding) remainder = (512 - (size % 512)) % 512 @io.write("\0" * remainder) self end ## # Adds symlink +name+ with permissions +mode+, linking to +target+. def add_symlink(name, target, mode) check_closed name, prefix = split_name name header = Gem::Package::TarHeader.new(name: name, mode: mode, size: 0, typeflag: "2", linkname: target, prefix: prefix, mtime: Gem.source_date_epoch).to_s @io.write header self end ## # Raises IOError if the TarWriter is closed def check_closed raise IOError, "closed #{self.class}" if closed? end ## # Closes the TarWriter def close check_closed @io.write "\0" * 1024 flush @closed = true end ## # Is the TarWriter closed? def closed? @closed end ## # Flushes the TarWriter's IO def flush check_closed @io.flush if @io.respond_to? :flush end ## # Creates a new directory in the tar file +name+ with +mode+ def mkdir(name, mode) check_closed name, prefix = split_name(name) header = Gem::Package::TarHeader.new name: name, mode: mode, typeflag: "5", size: 0, prefix: prefix, mtime: Gem.source_date_epoch @io.write header self end ## # Splits +name+ into a name and prefix that can fit in the TarHeader def split_name(name) # :nodoc: if name.bytesize > 256 raise Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)") end prefix = "" if name.bytesize > 100 parts = name.split("/", -1) # parts are never empty here name = parts.pop # initially empty for names with a trailing slash ("foo/.../bar/") prefix = parts.join("/") # if empty, then it's impossible to split (parts is empty too) while !parts.empty? && (prefix.bytesize > 155 || name.empty?) name = parts.pop + "/" + name prefix = parts.join("/") end if name.bytesize > 100 || prefix.empty? raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)") end if prefix.bytesize > 155 raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)") end end [name, prefix] end end PK!^x{HHrubygems/package/source.rbnu[# frozen_string_literal: true class Gem::Package::Source # :nodoc: end PK!bbrubygems/package/file_source.rbnu[# frozen_string_literal: true ## # The primary source of gems is a file on disk, including all usages # internal to rubygems. # # This is a private class, do not depend on it directly. Instead, pass a path # object to `Gem::Package.new`. class Gem::Package::FileSource < Gem::Package::Source # :nodoc: all attr_reader :path def initialize(path) @path = path end def start @start ||= File.read path, 20 end def present? File.exist? path end def with_write_io(&block) File.open path, "wb", &block end def with_read_io(&block) File.open path, "rb", &block end end PK!*Csrubygems/package/tar_header.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## #-- # struct tarfile_entry_posix { # char name[100]; # ASCII + (Z unless filled) # char mode[8]; # 0 padded, octal, null # char uid[8]; # ditto # char gid[8]; # ditto # char size[12]; # 0 padded, octal, null # char mtime[12]; # 0 padded, octal, null # char checksum[8]; # 0 padded, octal, null, space # char typeflag[1]; # file: "0" dir: "5" # char linkname[100]; # ASCII + (Z unless filled) # char magic[6]; # "ustar\0" # char version[2]; # "00" # char uname[32]; # ASCIIZ # char gname[32]; # ASCIIZ # char devmajor[8]; # 0 padded, octal, null # char devminor[8]; # o padded, octal, null # char prefix[155]; # ASCII + (Z unless filled) # }; #++ # A header for a tar file class Gem::Package::TarHeader ## # Fields in the tar header FIELDS = [ :checksum, :devmajor, :devminor, :gid, :gname, :linkname, :magic, :mode, :mtime, :name, :prefix, :size, :typeflag, :uid, :uname, :version, ].freeze ## # Pack format for a tar header PACK_FORMAT = "a100" + # name "a8" + # mode "a8" + # uid "a8" + # gid "a12" + # size "a12" + # mtime "a7a" + # chksum "a" + # typeflag "a100" + # linkname "a6" + # magic "a2" + # version "a32" + # uname "a32" + # gname "a8" + # devmajor "a8" + # devminor "a155" # prefix ## # Unpack format for a tar header UNPACK_FORMAT = "A100" + # name "A8" + # mode "A8" + # uid "A8" + # gid "A12" + # size "A12" + # mtime "A8" + # checksum "A" + # typeflag "A100" + # linkname "A6" + # magic "A2" + # version "A32" + # uname "A32" + # gname "A8" + # devmajor "A8" + # devminor "A155" # prefix attr_reader(*FIELDS) EMPTY_HEADER = ("\0" * 512).b.freeze # :nodoc: ## # Creates a tar header from IO +stream+ def self.from(stream) header = stream.read 512 return EMPTY if header == EMPTY_HEADER fields = header.unpack UNPACK_FORMAT new name: fields.shift, mode: strict_oct(fields.shift), uid: oct_or_256based(fields.shift), gid: oct_or_256based(fields.shift), size: strict_oct(fields.shift), mtime: strict_oct(fields.shift), checksum: strict_oct(fields.shift), typeflag: fields.shift, linkname: fields.shift, magic: fields.shift, version: strict_oct(fields.shift), uname: fields.shift, gname: fields.shift, devmajor: strict_oct(fields.shift), devminor: strict_oct(fields.shift), prefix: fields.shift, empty: false end def self.strict_oct(str) str.strip! return str.oct if /\A[0-7]*\z/.match?(str) raise ArgumentError, "#{str.inspect} is not an octal string" end def self.oct_or_256based(str) # \x80 flags a positive 256-based number # \ff flags a negative 256-based number # In case we have a match, parse it as a signed binary value # in big-endian order, except that the high-order bit is ignored. return str.unpack1("@4N") if /\A[\x80\xff]/n.match?(str) strict_oct(str) end ## # Creates a new TarHeader using +vals+ def initialize(vals) unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] raise ArgumentError, ":name, :size, :prefix and :mode required" end @checksum = vals[:checksum] || "" @devmajor = vals[:devmajor] || 0 @devminor = vals[:devminor] || 0 @gid = vals[:gid] || 0 @gname = vals[:gname] || "wheel" @linkname = vals[:linkname] @magic = vals[:magic] || "ustar" @mode = vals[:mode] @mtime = vals[:mtime] || 0 @name = vals[:name] @prefix = vals[:prefix] @size = vals[:size] @typeflag = vals[:typeflag] @typeflag = "0" if @typeflag.nil? || @typeflag.empty? @uid = vals[:uid] || 0 @uname = vals[:uname] || "wheel" @version = vals[:version] || "00" @empty = vals[:empty] end EMPTY = new({ # :nodoc: checksum: 0, gname: "", linkname: "", magic: "", mode: 0, name: "", prefix: "", size: 0, uname: "", version: 0, empty: true, }).freeze private_constant :EMPTY ## # Is the tar entry empty? def empty? @empty end def ==(other) # :nodoc: self.class === other && @checksum == other.checksum && @devmajor == other.devmajor && @devminor == other.devminor && @gid == other.gid && @gname == other.gname && @linkname == other.linkname && @magic == other.magic && @mode == other.mode && @mtime == other.mtime && @name == other.name && @prefix == other.prefix && @size == other.size && @typeflag == other.typeflag && @uid == other.uid && @uname == other.uname && @version == other.version end def to_s # :nodoc: update_checksum header end ## # Updates the TarHeader's checksum def update_checksum header = header " " * 8 @checksum = oct calculate_checksum(header), 6 end private def calculate_checksum(header) header.sum(0) end def header(checksum = @checksum) header = [ name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), oct(mtime, 11), checksum, " ", typeflag, linkname, magic, oct(version, 2), uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix, ] header = header.pack PACK_FORMAT header.ljust 512, "\0" end def oct(num, len) format("%0#{len}o", num) end end PK!rubygems/package/old.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ ## # The format class knows the guts of the ancient .gem file format and provides # the capability to read such ancient gems. # # Please pretend this doesn't exist. class Gem::Package::Old < Gem::Package undef_method :spec= ## # Creates a new old-format package reader for +gem+. Old-format packages # cannot be written. def initialize(gem, security_policy) require "fileutils" require "zlib" Gem.load_yaml @contents = nil @gem = gem @security_policy = security_policy @spec = nil end ## # A list of file names contained in this gem def contents verify return @contents if @contents @gem.with_read_io do |io| read_until_dashes io # spec header = file_list io @contents = header.map {|file| file["path"] } end end ## # Extracts the files in this package into +destination_dir+ def extract_files(destination_dir) verify errstr = "Error reading files from gem" @gem.with_read_io do |io| read_until_dashes io # spec header = file_list io raise Gem::Exception, errstr unless header header.each do |entry| full_name = entry["path"] destination = install_location full_name, destination_dir file_data = String.new read_until_dashes io do |line| file_data << line end file_data = file_data.strip.unpack1("m") file_data = Zlib::Inflate.inflate file_data raise Gem::Package::FormatError, "#{full_name} in #{@gem} is corrupt" if file_data.length != entry["size"].to_i FileUtils.rm_rf destination FileUtils.mkdir_p File.dirname(destination), mode: dir_mode && 0o755 File.open destination, "wb", file_mode(entry["mode"]) do |out| out.write file_data end verbose destination end end rescue Zlib::DataError raise Gem::Exception, errstr end ## # Reads the file list section from the old-format gem +io+ def file_list(io) # :nodoc: header = String.new read_until_dashes io do |line| header << line end Gem::SafeYAML.safe_load header end ## # Reads lines until a "---" separator is found def read_until_dashes(io) # :nodoc: while (line = io.gets) && line.chomp.strip != "---" do yield line if block_given? end end ## # Skips the Ruby self-install header in +io+. def skip_ruby(io) # :nodoc: loop do line = io.gets return if line.chomp == "__END__" break unless line end raise Gem::Exception, "Failed to find end of Ruby script while reading gem" end ## # The specification for this gem def spec verify return @spec if @spec yaml = String.new @gem.with_read_io do |io| skip_ruby io read_until_dashes io do |line| yaml << line end end begin @spec = Gem::Specification.from_yaml yaml rescue Psych::SyntaxError raise Gem::Exception, "Failed to parse gem specification out of gem file" end rescue ArgumentError raise Gem::Exception, "Failed to parse gem specification out of gem file" end ## # Raises an exception if a security policy that verifies data is active. # Old format gems cannot be verified as signed. def verify return true unless @security_policy raise Gem::Security::Exception, "old format gems do not contain signatures and cannot be verified" if @security_policy.verify_data true end end PK!䌸..rubygems/package/io_source.rbnu[# frozen_string_literal: true ## # Supports reading and writing gems from/to a generic IO object. This is # useful for other applications built on top of rubygems, such as # rubygems.org. # # This is a private class, do not depend on it directly. Instead, pass an IO # object to `Gem::Package.new`. class Gem::Package::IOSource < Gem::Package::Source # :nodoc: all attr_reader :io def initialize(io) @io = io end def start @start ||= begin if io.pos > 0 raise Gem::Package::Error, "Cannot read start unless IO is at start" end value = io.read 20 io.rewind value end end def present? true end def with_read_io yield io ensure io.rewind end def with_write_io yield io ensure io.rewind end def path end end PK!c_MMrubygems/package/digest_io.rbnu[# frozen_string_literal: true ## # IO wrapper that creates digests of contents written to the IO it wraps. class Gem::Package::DigestIO ## # Collected digests for wrapped writes. # # { # 'SHA1' => #, # 'SHA512' => #, # } attr_reader :digests ## # Wraps +io+ and updates digest for each of the digest algorithms in # the +digests+ Hash. Returns the digests hash. Example: # # io = StringIO.new # digests = { # 'SHA1' => OpenSSL::Digest.new('SHA1'), # 'SHA512' => OpenSSL::Digest.new('SHA512'), # } # # Gem::Package::DigestIO.wrap io, digests do |digest_io| # digest_io.write "hello" # end # # digests['SHA1'].hexdigest #=> "aaf4c61d[...]" # digests['SHA512'].hexdigest #=> "9b71d224[...]" def self.wrap(io, digests) digest_io = new io, digests yield digest_io digests end ## # Creates a new DigestIO instance. Using ::wrap is recommended, see the # ::wrap documentation for documentation of +io+ and +digests+. def initialize(io, digests) @io = io @digests = digests end ## # Writes +data+ to the underlying IO and updates the digests def write(data) result = @io.write data @digests.each do |_, digest| digest << data end result end end PK!I7bbrubygems/defaults.rbnu[# frozen_string_literal: true module Gem DEFAULT_HOST = "https://rubygems.org" @post_install_hooks ||= [] @done_installing_hooks ||= [] @post_uninstall_hooks ||= [] @pre_uninstall_hooks ||= [] @pre_install_hooks ||= [] ## # An Array of the default sources that come with RubyGems def self.default_sources %w[https://rubygems.org/] end ## # Default spec directory path to be used if an alternate value is not # specified in the environment def self.default_spec_cache_dir default_spec_cache_dir = File.join Gem.user_home, ".gem", "specs" unless File.exist?(default_spec_cache_dir) default_spec_cache_dir = File.join Gem.cache_home, "gem", "specs" end default_spec_cache_dir end ## # Default home directory path to be used if an alternate value is not # specified in the environment def self.default_dir @default_dir ||= File.join(RbConfig::CONFIG["rubylibprefix"], "gems", RbConfig::CONFIG["ruby_version_dir_name"] || RbConfig::CONFIG["ruby_version"]) end ## # Returns binary extensions dir for specified RubyGems base dir or nil # if such directory cannot be determined. # # By default, the binary extensions are located side by side with their # Ruby counterparts, therefore nil is returned def self.default_ext_dir_for(base_dir) nil end ## # Paths where RubyGems' .rb files and bin files are installed def self.default_rubygems_dirs nil # default to standard layout end ## # Path to specification files of default gems. def self.default_specifications_dir @default_specifications_dir ||= File.join(Gem.default_dir, "specifications", "default") end ## # Finds the user's home directory. #-- # Some comments from the ruby-talk list regarding finding the home # directory: # # I have HOME, USERPROFILE and HOMEDRIVE + HOMEPATH. Ruby seems # to be depending on HOME in those code samples. I propose that # it should fallback to USERPROFILE and HOMEDRIVE + HOMEPATH (at # least on Win32). #++ #-- # #++ def self.find_home Dir.home.dup rescue StandardError if Gem.win_platform? File.expand_path File.join(ENV["HOMEDRIVE"] || ENV["SystemDrive"], "/") else File.expand_path "/" end end private_class_method :find_home ## # The home directory for the user. def self.user_home @user_home ||= find_home end ## # Path for gems in the user's home directory def self.user_dir gem_dir = File.join(Gem.user_home, ".gem") gem_dir = File.join(Gem.data_home, "gem") unless File.exist?(gem_dir) parts = [gem_dir, ruby_engine] ruby_version_dir_name = RbConfig::CONFIG["ruby_version_dir_name"] || RbConfig::CONFIG["ruby_version"] parts << ruby_version_dir_name unless ruby_version_dir_name.empty? File.join parts end ## # The path to standard location of the user's configuration directory. def self.config_home @config_home ||= ENV["XDG_CONFIG_HOME"] || File.join(Gem.user_home, ".config") end ## # Finds the user's config file def self.find_config_file gemrc = File.join Gem.user_home, ".gemrc" if File.exist? gemrc gemrc else File.join Gem.config_home, "gem", "gemrc" end end ## # The path to standard location of the user's .gemrc file. def self.config_file @config_file ||= find_config_file end ## # The path to standard location of the user's state file. def self.state_file @state_file ||= File.join(Gem.state_home, "gem", "last_update_check") end ## # The path to standard location of the user's cache directory. def self.cache_home @cache_home ||= ENV["XDG_CACHE_HOME"] || File.join(Gem.user_home, ".cache") end ## # The path to standard location of the user's data directory. def self.data_home @data_home ||= ENV["XDG_DATA_HOME"] || File.join(Gem.user_home, ".local", "share") end ## # The path to standard location of the user's state directory. def self.state_home @state_home ||= ENV["XDG_STATE_HOME"] || File.join(Gem.user_home, ".local", "state") end ## # How String Gem paths should be split. Overridable for esoteric platforms. def self.path_separator File::PATH_SEPARATOR end ## # Default gem load path def self.default_path path = [] path << user_dir if user_home && File.exist?(user_home) path << default_dir path << vendor_dir if vendor_dir && File.directory?(vendor_dir) path end ## # Deduce Ruby's --program-prefix and --program-suffix from its install name def self.default_exec_format exec_format = begin RbConfig::CONFIG["ruby_install_name"].sub("ruby", "%s") rescue StandardError "%s" end unless exec_format.include?("%s") raise Gem::Exception, "[BUG] invalid exec_format #{exec_format.inspect}, no %s" end exec_format end ## # The default directory for binaries def self.default_bindir RbConfig::CONFIG["bindir"] end def self.ruby_engine RUBY_ENGINE end ## # The default signing key path def self.default_key_path default_key_path = File.join Gem.user_home, ".gem", "gem-private_key.pem" unless File.exist?(default_key_path) default_key_path = File.join Gem.data_home, "gem", "gem-private_key.pem" end default_key_path end ## # The default signing certificate chain path def self.default_cert_path default_cert_path = File.join Gem.user_home, ".gem", "gem-public_cert.pem" unless File.exist?(default_cert_path) default_cert_path = File.join Gem.data_home, "gem", "gem-public_cert.pem" end default_cert_path end ## # Enables automatic installation into user directory def self.default_user_install # :nodoc: if !ENV.key?("GEM_HOME") && (File.exist?(Gem.dir) && !File.writable?(Gem.dir)) Gem.ui.say "Defaulting to user installation because default installation directory (#{Gem.dir}) is not writable." return true end false end ## # Install extensions into lib as well as into the extension directory. def self.install_extension_in_lib # :nodoc: true end ## # Directory where vendor gems are installed. def self.vendor_dir # :nodoc: if vendor_dir = ENV["GEM_VENDOR"] return vendor_dir.dup end return nil unless RbConfig::CONFIG.key? "vendordir" File.join RbConfig::CONFIG["vendordir"], "gems", RbConfig::CONFIG["ruby_version_dir_name"] || RbConfig::CONFIG["ruby_version"] end ## # Default options for gem commands for Ruby packagers. # # The options here should be structured as an array of string "gem" # command names as keys and a string of the default options as values. # # Example: # # def self.operating_system_defaults # { # 'install' => '--no-rdoc --no-ri --env-shebang', # 'update' => '--no-rdoc --no-ri --env-shebang' # } # end def self.operating_system_defaults {} end ## # Default options for gem commands for Ruby implementers. # # The options here should be structured as an array of string "gem" # command names as keys and a string of the default options as values. # # Example: # # def self.platform_defaults # { # 'install' => '--no-rdoc --no-ri --env-shebang', # 'update' => '--no-rdoc --no-ri --env-shebang' # } # end def self.platform_defaults {} end end PK!$_l  rubygems/request_set/lockfile.rbnu[# frozen_string_literal: true ## # Parses a gem.deps.rb.lock file and constructs a LockSet containing the # dependencies found inside. If the lock file is missing no LockSet is # constructed. class Gem::RequestSet::Lockfile ## # Raised when a lockfile cannot be parsed class ParseError < Gem::Exception ## # The column where the error was encountered attr_reader :column ## # The line where the error was encountered attr_reader :line ## # The location of the lock file attr_reader :path ## # Raises a ParseError with the given +message+ which was encountered at a # +line+ and +column+ while parsing. def initialize(message, column, line, path) @line = line @column = column @path = path super "#{message} (at line #{line} column #{column})" end end ## # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ # location. def self.build(request_set, gem_deps_file, dependencies = nil) request_set.resolve dependencies ||= requests_to_deps request_set.sorted_requests new request_set, gem_deps_file, dependencies end def self.requests_to_deps(requests) # :nodoc: deps = {} requests.each do |request| spec = request.spec name = request.name requirement = request.request.dependency.requirement deps[name] = if [Gem::Resolver::VendorSpecification, Gem::Resolver::GitSpecification].include? spec.class Gem::Requirement.source_set else requirement end end deps end ## # The platforms for this Lockfile attr_reader :platforms def initialize(request_set, gem_deps_file, dependencies) @set = request_set @dependencies = dependencies @gem_deps_file = File.expand_path(gem_deps_file) @gem_deps_dir = File.dirname(@gem_deps_file) @platforms = [] end def add_DEPENDENCIES(out) # :nodoc: out << "DEPENDENCIES" out.concat @dependencies.sort.map {|name, requirement| " #{name}#{requirement.for_lockfile}" } out << nil end def add_GEM(out, spec_groups) # :nodoc: return if spec_groups.empty? source_groups = spec_groups.values.flatten.group_by do |request| request.spec.source.uri end source_groups.sort_by {|group,| group.to_s }.map do |group, requests| out << "GEM" out << " remote: #{group}" out << " specs:" requests.sort_by(&:name).each do |request| next if request.spec.name == "bundler" platform = "-#{request.spec.platform}" unless request.spec.platform == Gem::Platform::RUBY out << " #{request.name} (#{request.version}#{platform})" request.full_spec.dependencies.sort.each do |dependency| next if dependency.type == :development requirement = dependency.requirement out << " #{dependency.name}#{requirement.for_lockfile}" end end out << nil end end def add_GIT(out, git_requests) return if git_requests.empty? by_repository_revision = git_requests.group_by do |request| source = request.spec.source [source.repository, source.rev_parse] end by_repository_revision.each do |(repository, revision), requests| out << "GIT" out << " remote: #{repository}" out << " revision: #{revision}" out << " specs:" requests.sort_by(&:name).each do |request| out << " #{request.name} (#{request.version})" dependencies = request.spec.dependencies.sort_by(&:name) dependencies.each do |dep| out << " #{dep.name}#{dep.requirement.for_lockfile}" end end out << nil end end def relative_path_from(dest, base) # :nodoc: dest = File.expand_path(dest) base = File.expand_path(base) if dest.index(base) == 0 offset = dest[base.size + 1..-1] return "." unless offset offset else dest end end def add_PATH(out, path_requests) # :nodoc: return if path_requests.empty? out << "PATH" path_requests.each do |request| directory = File.expand_path(request.spec.source.uri) out << " remote: #{relative_path_from directory, @gem_deps_dir}" out << " specs:" out << " #{request.name} (#{request.version})" end out << nil end def add_PLATFORMS(out) # :nodoc: out << "PLATFORMS" platforms = requests.map {|request| request.spec.platform }.uniq platforms = platforms.sort_by(&:to_s) platforms.each do |platform| out << " #{platform}" end out << nil end def spec_groups requests.group_by {|request| request.spec.class } end ## # The contents of the lock file. def to_s out = [] groups = spec_groups add_PATH out, groups.delete(Gem::Resolver::VendorSpecification) { [] } add_GIT out, groups.delete(Gem::Resolver::GitSpecification) { [] } add_GEM out, groups add_PLATFORMS out add_DEPENDENCIES out out.join "\n" end ## # Writes the lock file alongside the gem dependencies file def write content = to_s File.open "#{@gem_deps_file}.lock", "w" do |io| io.write content end end private def requests @set.sorted_requests end end require_relative "lockfile/tokenizer" PK!_cVV*rubygems/request_set/gem_dependency_api.rbnu[# frozen_string_literal: true ## # A semi-compatible DSL for the Bundler Gemfile and Isolate gem dependencies # files. # # To work with both the Bundler Gemfile and Isolate formats this # implementation takes some liberties to allow compatibility with each, most # notably in #source. # # A basic gem dependencies file will look like the following: # # source 'https://rubygems.org' # # gem 'rails', '3.2.14a # gem 'devise', '~> 2.1', '>= 2.1.3' # gem 'cancan' # gem 'airbrake' # gem 'pg' # # RubyGems recommends saving this as gem.deps.rb over Gemfile or Isolate. # # To install the gems in this Gemfile use `gem install -g` to install it and # create a lockfile. The lockfile will ensure that when you make changes to # your gem dependencies file a minimum amount of change is made to the # dependencies of your gems. # # RubyGems can activate all the gems in your dependencies file at startup # using the RUBYGEMS_GEMDEPS environment variable or through Gem.use_gemdeps. # See Gem.use_gemdeps for details and warnings. # # See `gem help install` and `gem help gem_dependencies` for further details. class Gem::RequestSet::GemDependencyAPI ENGINE_MAP = { # :nodoc: jruby: %w[jruby], jruby_18: %w[jruby], jruby_19: %w[jruby], maglev: %w[maglev], mri: %w[ruby], mri_18: %w[ruby], mri_19: %w[ruby], mri_20: %w[ruby], mri_21: %w[ruby], rbx: %w[rbx], truffleruby: %w[truffleruby], ruby: %w[ruby rbx maglev truffleruby], ruby_18: %w[ruby rbx maglev truffleruby], ruby_19: %w[ruby rbx maglev truffleruby], ruby_20: %w[ruby rbx maglev truffleruby], ruby_21: %w[ruby rbx maglev truffleruby], }.freeze mswin = Gem::Platform.new "x86-mswin32" mswin64 = Gem::Platform.new "x64-mswin64" x86_mingw = Gem::Platform.new "x86-mingw32" x64_mingw = Gem::Platform.new "x64-mingw32" PLATFORM_MAP = { # :nodoc: jruby: Gem::Platform::RUBY, jruby_18: Gem::Platform::RUBY, jruby_19: Gem::Platform::RUBY, maglev: Gem::Platform::RUBY, mingw: x86_mingw, mingw_18: x86_mingw, mingw_19: x86_mingw, mingw_20: x86_mingw, mingw_21: x86_mingw, mri: Gem::Platform::RUBY, mri_18: Gem::Platform::RUBY, mri_19: Gem::Platform::RUBY, mri_20: Gem::Platform::RUBY, mri_21: Gem::Platform::RUBY, mswin: mswin, mswin_18: mswin, mswin_19: mswin, mswin_20: mswin, mswin_21: mswin, mswin64: mswin64, mswin64_19: mswin64, mswin64_20: mswin64, mswin64_21: mswin64, rbx: Gem::Platform::RUBY, ruby: Gem::Platform::RUBY, ruby_18: Gem::Platform::RUBY, ruby_19: Gem::Platform::RUBY, ruby_20: Gem::Platform::RUBY, ruby_21: Gem::Platform::RUBY, truffleruby: Gem::Platform::RUBY, x64_mingw: x64_mingw, x64_mingw_20: x64_mingw, x64_mingw_21: x64_mingw, }.freeze gt_eq_0 = Gem::Requirement.new ">= 0" tilde_gt_1_8_0 = Gem::Requirement.new "~> 1.8.0" tilde_gt_1_9_0 = Gem::Requirement.new "~> 1.9.0" tilde_gt_2_0_0 = Gem::Requirement.new "~> 2.0.0" tilde_gt_2_1_0 = Gem::Requirement.new "~> 2.1.0" VERSION_MAP = { # :nodoc: jruby: gt_eq_0, jruby_18: tilde_gt_1_8_0, jruby_19: tilde_gt_1_9_0, maglev: gt_eq_0, mingw: gt_eq_0, mingw_18: tilde_gt_1_8_0, mingw_19: tilde_gt_1_9_0, mingw_20: tilde_gt_2_0_0, mingw_21: tilde_gt_2_1_0, mri: gt_eq_0, mri_18: tilde_gt_1_8_0, mri_19: tilde_gt_1_9_0, mri_20: tilde_gt_2_0_0, mri_21: tilde_gt_2_1_0, mswin: gt_eq_0, mswin_18: tilde_gt_1_8_0, mswin_19: tilde_gt_1_9_0, mswin_20: tilde_gt_2_0_0, mswin_21: tilde_gt_2_1_0, mswin64: gt_eq_0, mswin64_19: tilde_gt_1_9_0, mswin64_20: tilde_gt_2_0_0, mswin64_21: tilde_gt_2_1_0, rbx: gt_eq_0, ruby: gt_eq_0, ruby_18: tilde_gt_1_8_0, ruby_19: tilde_gt_1_9_0, ruby_20: tilde_gt_2_0_0, ruby_21: tilde_gt_2_1_0, truffleruby: gt_eq_0, x64_mingw: gt_eq_0, x64_mingw_20: tilde_gt_2_0_0, x64_mingw_21: tilde_gt_2_1_0, }.freeze WINDOWS = { # :nodoc: mingw: :only, mingw_18: :only, mingw_19: :only, mingw_20: :only, mingw_21: :only, mri: :never, mri_18: :never, mri_19: :never, mri_20: :never, mri_21: :never, mswin: :only, mswin_18: :only, mswin_19: :only, mswin_20: :only, mswin_21: :only, mswin64: :only, mswin64_19: :only, mswin64_20: :only, mswin64_21: :only, rbx: :never, ruby: :never, ruby_18: :never, ruby_19: :never, ruby_20: :never, ruby_21: :never, x64_mingw: :only, x64_mingw_20: :only, x64_mingw_21: :only, }.freeze ## # The gems required by #gem statements in the gem.deps.rb file attr_reader :dependencies ## # A set of gems that are loaded via the +:git+ option to #gem attr_reader :git_set # :nodoc: ## # A Hash containing gem names and files to require from those gems. attr_reader :requires ## # A set of gems that are loaded via the +:path+ option to #gem attr_reader :vendor_set # :nodoc: ## # The groups of gems to exclude from installation attr_accessor :without_groups # :nodoc: ## # Creates a new GemDependencyAPI that will add dependencies to the # Gem::RequestSet +set+ based on the dependency API description in +path+. def initialize(set, path) @set = set @path = path @current_groups = nil @current_platforms = nil @current_repository = nil @dependencies = {} @default_sources = true @git_set = @set.git_set @git_sources = {} @installing = false @requires = Hash.new {|h, name| h[name] = [] } @vendor_set = @set.vendor_set @source_set = @set.source_set @gem_sources = {} @without_groups = [] git_source :github do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include? "/" "https://github.com/#{repo_name}.git" end git_source :bitbucket do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include? "/" user, = repo_name.split "/", 2 "https://#{user}@bitbucket.org/#{repo_name}.git" end end ## # Adds +dependencies+ to the request set if any of the +groups+ are allowed. # This is used for gemspec dependencies. def add_dependencies(groups, dependencies) # :nodoc: return unless (groups & @without_groups).empty? dependencies.each do |dep| @set.gem dep.name, *dep.requirement.as_list end end private :add_dependencies ## # Finds a gemspec with the given +name+ that lives at +path+. def find_gemspec(name, path) # :nodoc: glob = File.join path, "#{name}.gemspec" spec_files = Dir[glob] case spec_files.length when 1 then spec_file = spec_files.first spec = Gem::Specification.load spec_file return spec if spec raise ArgumentError, "invalid gemspec #{spec_file}" when 0 then raise ArgumentError, "no gemspecs found at #{Dir.pwd}" else raise ArgumentError, "found multiple gemspecs at #{Dir.pwd}, " \ "use the name: option to specify the one you want" end end ## # Changes the behavior of gem dependency file loading to installing mode. # In installing mode certain restrictions are ignored such as ruby version # mismatch checks. def installing=(installing) # :nodoc: @installing = installing end ## # Loads the gem dependency file and returns self. def load instance_eval File.read(@path), @path, 1 self end ## # :category: Gem Dependencies DSL # # :call-seq: # gem(name) # gem(name, *requirements) # gem(name, *requirements, options) # # Specifies a gem dependency with the given +name+ and +requirements+. You # may also supply +options+ following the +requirements+ # # +options+ include: # # require: :: # RubyGems does not provide any autorequire features so requires in a gem # dependencies file are recorded but ignored. # # In bundler the require: option overrides the file to require during # Bundler.require. By default the name of the dependency is required in # Bundler. A single file or an Array of files may be given. # # To disable requiring any file give +false+: # # gem 'rake', require: false # # group: :: # Place the dependencies in the given dependency group. A single group or # an Array of groups may be given. # # See also #group # # platform: :: # Only install the dependency on the given platform. A single platform or # an Array of platforms may be given. # # See #platform for a list of platforms available. # # path: :: # Install this dependency from an unpacked gem in the given directory. # # gem 'modified_gem', path: 'vendor/modified_gem' # # git: :: # Install this dependency from a git repository: # # gem 'private_gem', git: git@my.company.example:private_gem.git' # # gist: :: # Install this dependency from the gist ID: # # gem 'bang', gist: '1232884' # # github: :: # Install this dependency from a github git repository: # # gem 'private_gem', github: 'my_company/private_gem' # # submodules: :: # Set to +true+ to include submodules when fetching the git repository for # git:, gist: and github: dependencies. # # ref: :: # Use the given commit name or SHA for git:, gist: and github: # dependencies. # # branch: :: # Use the given branch for git:, gist: and github: dependencies. # # tag: :: # Use the given tag for git:, gist: and github: dependencies. def gem(name, *requirements) options = requirements.pop if requirements.last.is_a?(Hash) options ||= {} options[:git] = @current_repository if @current_repository source_set = false source_set ||= gem_path name, options source_set ||= gem_git name, options source_set ||= gem_git_source name, options source_set ||= gem_source name, options duplicate = @dependencies.include? name @dependencies[name] = if requirements.empty? && !source_set Gem::Requirement.default elsif source_set Gem::Requirement.source_set else Gem::Requirement.create requirements end return unless gem_platforms name, options groups = gem_group name, options return unless (groups & @without_groups).empty? pin_gem_source name, :default unless source_set gem_requires name, options if duplicate warn <<-WARNING Gem dependencies file #{@path} requires #{name} more than once. WARNING end @set.gem name, *requirements end ## # Handles the git: option from +options+ for gem +name+. # # Returns +true+ if the gist or git option was handled. def gem_git(name, options) # :nodoc: if gist = options.delete(:gist) options[:git] = "https://gist.github.com/#{gist}.git" end return unless repository = options.delete(:git) pin_gem_source name, :git, repository reference = gem_git_reference options submodules = options.delete :submodules @git_set.add_git_gem name, repository, reference, submodules true end ## # Handles the git options from +options+ for git gem. # # Returns reference for the git gem. def gem_git_reference(options) # :nodoc: ref = options.delete :ref branch = options.delete :branch tag = options.delete :tag reference = nil reference ||= ref reference ||= branch reference ||= tag if ref && branch warn <<-WARNING Gem dependencies file #{@path} includes git reference for both ref and branch but only ref is used. WARNING end if (ref || branch) && tag warn <<-WARNING Gem dependencies file #{@path} includes git reference for both ref/branch and tag but only ref/branch is used. WARNING end reference end private :gem_git ## # Handles a git gem option from +options+ for gem +name+ for a git source # registered through git_source. # # Returns +true+ if the custom source option was handled. def gem_git_source(name, options) # :nodoc: return unless git_source = (@git_sources.keys & options.keys).last source_callback = @git_sources[git_source] source_param = options.delete git_source git_url = source_callback.call source_param options[:git] = git_url gem_git name, options true end private :gem_git_source ## # Handles the :group and :groups +options+ for the gem with the given # +name+. def gem_group(name, options) # :nodoc: g = options.delete :group all_groups = g ? Array(g) : [] groups = options.delete :groups all_groups |= groups if groups all_groups |= @current_groups if @current_groups all_groups end private :gem_group ## # Handles the path: option from +options+ for gem +name+. # # Returns +true+ if the path option was handled. def gem_path(name, options) # :nodoc: return unless directory = options.delete(:path) pin_gem_source name, :path, directory @vendor_set.add_vendor_gem name, directory true end private :gem_path ## # Handles the source: option from +options+ for gem +name+. # # Returns +true+ if the source option was handled. def gem_source(name, options) # :nodoc: return unless source = options.delete(:source) pin_gem_source name, :source, source @source_set.add_source_gem name, source true end private :gem_source ## # Handles the platforms: option from +options+. Returns true if the # platform matches the current platform. def gem_platforms(name, options) # :nodoc: platform_names = Array(options.delete(:platform)) platform_names.concat Array(options.delete(:platforms)) platform_names.concat @current_platforms if @current_platforms return true if platform_names.empty? platform_names.any? do |platform_name| raise ArgumentError, "unknown platform #{platform_name.inspect}" unless platform = PLATFORM_MAP[platform_name] next false unless Gem::Platform.match_gem? platform, name if engines = ENGINE_MAP[platform_name] next false unless engines.include? Gem.ruby_engine end case WINDOWS[platform_name] when :only then next false unless Gem.win_platform? when :never then next false if Gem.win_platform? end VERSION_MAP[platform_name].satisfied_by? Gem.ruby_version end end private :gem_platforms ## # Records the require: option from +options+ and adds those files, or the # default file to the require list for +name+. def gem_requires(name, options) # :nodoc: if options.include? :require if requires = options.delete(:require) @requires[name].concat Array requires end else @requires[name] << name end raise ArgumentError, "Unhandled gem options #{options.inspect}" unless options.empty? end private :gem_requires ## # :category: Gem Dependencies DSL # # Block form for specifying gems from a git +repository+. # # git 'https://github.com/rails/rails.git' do # gem 'activesupport' # gem 'activerecord' # end def git(repository) @current_repository = repository yield ensure @current_repository = nil end ## # Defines a custom git source that uses +name+ to expand git repositories # for use in gems built from git repositories. You must provide a block # that accepts a git repository name for expansion. def git_source(name, &callback) @git_sources[name] = callback end ## # Returns the basename of the file the dependencies were loaded from def gem_deps_file # :nodoc: File.basename @path end ## # :category: Gem Dependencies DSL # # Loads dependencies from a gemspec file. # # +options+ include: # # name: :: # The name portion of the gemspec file. Defaults to searching for any # gemspec file in the current directory. # # gemspec name: 'my_gem' # # path: :: # The path the gemspec lives in. Defaults to the current directory: # # gemspec 'my_gem', path: 'gemspecs', name: 'my_gem' # # development_group: :: # The group to add development dependencies to. By default this is # :development. Only one group may be specified. def gemspec(options = {}) name = options.delete(:name) || "{,*}" path = options.delete(:path) || "." development_group = options.delete(:development_group) || :development spec = find_gemspec name, path groups = gem_group spec.name, {} self_dep = Gem::Dependency.new spec.name, spec.version add_dependencies groups, [self_dep] add_dependencies groups, spec.runtime_dependencies @dependencies[spec.name] = Gem::Requirement.source_set spec.dependencies.each do |dep| @dependencies[dep.name] = dep.requirement end groups << development_group add_dependencies groups, spec.development_dependencies @vendor_set.add_vendor_gem spec.name, path gem_requires spec.name, options end ## # :category: Gem Dependencies DSL # # Block form for placing a dependency in the given +groups+. # # group :development do # gem 'debugger' # end # # group :development, :test do # gem 'minitest' # end # # Groups can be excluded at install time using `gem install -g --without # development`. See `gem help install` and `gem help gem_dependencies` for # further details. def group(*groups) @current_groups = groups yield ensure @current_groups = nil end ## # Pins the gem +name+ to the given +source+. Adding a gem with the same # name from a different +source+ will raise an exception. def pin_gem_source(name, type = :default, source = nil) source_description = case type when :default then "(default)" when :path then "path: #{source}" when :git then "git: #{source}" when :source then "source: #{source}" else "(unknown)" end raise ArgumentError, "duplicate source #{source_description} for gem #{name}" if @gem_sources.fetch(name, source) != source @gem_sources[name] = source end private :pin_gem_source ## # :category: Gem Dependencies DSL # # Block form for restricting gems to a set of platforms. # # The gem dependencies platform is different from Gem::Platform. A platform # gem.deps.rb platform matches on the ruby engine, the ruby version and # whether or not windows is allowed. # # :ruby, :ruby_XY :: # Matches non-windows, non-jruby implementations where X and Y can be used # to match releases in the 1.8, 1.9, 2.0 or 2.1 series. # # :mri, :mri_XY :: # Matches non-windows C Ruby (Matz Ruby) or only the 1.8, 1.9, 2.0 or # 2.1 series. # # :mingw, :mingw_XY :: # Matches 32 bit C Ruby on MinGW or only the 1.8, 1.9, 2.0 or 2.1 series. # # :x64_mingw, :x64_mingw_XY :: # Matches 64 bit C Ruby on MinGW or only the 1.8, 1.9, 2.0 or 2.1 series. # # :mswin, :mswin_XY :: # Matches 32 bit C Ruby on Microsoft Windows or only the 1.8, 1.9, 2.0 or # 2.1 series. # # :mswin64, :mswin64_XY :: # Matches 64 bit C Ruby on Microsoft Windows or only the 1.8, 1.9, 2.0 or # 2.1 series. # # :jruby, :jruby_XY :: # Matches JRuby or JRuby in 1.8 or 1.9 mode. # # :maglev :: # Matches Maglev # # :rbx :: # Matches non-windows Rubinius # # NOTE: There is inconsistency in what environment a platform matches. You # may need to read the source to know the exact details. def platform(*platforms) @current_platforms = platforms yield ensure @current_platforms = nil end ## # :category: Gem Dependencies DSL # # Block form for restricting gems to a particular set of platforms. See # #platform. alias_method :platforms, :platform ## # :category: Gem Dependencies DSL # # Restricts this gem dependencies file to the given ruby +version+. # # You may also provide +engine:+ and +engine_version:+ options to restrict # this gem dependencies file to a particular ruby engine and its engine # version. This matching is performed by using the RUBY_ENGINE and # RUBY_ENGINE_VERSION constants. def ruby(version, options = {}) engine = options[:engine] engine_version = options[:engine_version] raise ArgumentError, "You must specify engine_version along with the Ruby engine" if engine && !engine_version return true if @installing unless version == RUBY_VERSION message = "Your Ruby version is #{RUBY_VERSION}, " \ "but your #{gem_deps_file} requires #{version}" raise Gem::RubyVersionMismatch, message end if engine && engine != Gem.ruby_engine message = "Your Ruby engine is #{Gem.ruby_engine}, " \ "but your #{gem_deps_file} requires #{engine}" raise Gem::RubyVersionMismatch, message end if engine_version if engine_version != RUBY_ENGINE_VERSION message = "Your Ruby engine version is #{Gem.ruby_engine} #{RUBY_ENGINE_VERSION}, " \ "but your #{gem_deps_file} requires #{engine} #{engine_version}" raise Gem::RubyVersionMismatch, message end end true end ## # :category: Gem Dependencies DSL # # Sets +url+ as a source for gems for this dependency API. RubyGems uses # the default configured sources if no source was given. If a source is set # only that source is used. # # This method differs in behavior from Bundler: # # * The +:gemcutter+, # +:rubygems+ and +:rubyforge+ sources are not # supported as they are deprecated in bundler. # * The +prepend:+ option is not supported. If you wish to order sources # then list them in your preferred order. def source(url) Gem.sources.clear if @default_sources @default_sources = false Gem.sources << url end end PK!D3 *rubygems/request_set/lockfile/tokenizer.rbnu[# frozen_string_literal: true # ) frozen_string_literal: true require_relative "parser" class Gem::RequestSet::Lockfile::Tokenizer Token = Struct.new :type, :value, :column, :line EOF = Token.new :EOF def self.from_file(file) new File.read(file), file end def initialize(input, filename = nil, line = 0, pos = 0) @line = line @line_pos = pos @tokens = [] @filename = filename tokenize input end def make_parser(set, platforms) Gem::RequestSet::Lockfile::Parser.new self, set, platforms, @filename end def to_a @tokens.map {|token| [token.type, token.value, token.column, token.line] } end def skip(type) @tokens.shift while !@tokens.empty? && peek.type == type end ## # Calculates the column (by byte) and the line of the current token based on # +byte_offset+. def token_pos(byte_offset) # :nodoc: [byte_offset - @line_pos, @line] end def empty? @tokens.empty? end def unshift(token) @tokens.unshift token end def next_token @tokens.shift end alias_method :shift, :next_token def peek @tokens.first || EOF end private def tokenize(input) require "strscan" s = StringScanner.new input until s.eos? do pos = s.pos pos = s.pos if leading_whitespace = s.scan(/ +/) if s.scan(/[<|=>]{7}/) message = "your #{@filename} contains merge conflict markers" column, line = token_pos pos raise Gem::RequestSet::Lockfile::ParseError.new message, column, line, @filename end @tokens << if s.scan(/\r?\n/) token = Token.new(:newline, nil, *token_pos(pos)) @line_pos = s.pos @line += 1 token elsif s.scan(/[A-Z]+/) if leading_whitespace text = s.matched text += s.scan(/[^\s)]*/).to_s # in case of no match Token.new(:text, text, *token_pos(pos)) else Token.new(:section, s.matched, *token_pos(pos)) end elsif s.scan(/([a-z]+):\s/) s.pos -= 1 # rewind for possible newline Token.new(:entry, s[1], *token_pos(pos)) elsif s.scan(/\(/) Token.new(:l_paren, nil, *token_pos(pos)) elsif s.scan(/\)/) Token.new(:r_paren, nil, *token_pos(pos)) elsif s.scan(/<=|>=|=|~>|<|>|!=/) Token.new(:requirement, s.matched, *token_pos(pos)) elsif s.scan(/,/) Token.new(:comma, nil, *token_pos(pos)) elsif s.scan(/!/) Token.new(:bang, nil, *token_pos(pos)) elsif s.scan(/[^\s),!]*/) Token.new(:text, s.matched, *token_pos(pos)) else raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}" end end @tokens end end PK!/W2'rubygems/request_set/lockfile/parser.rbnu[# frozen_string_literal: true class Gem::RequestSet::Lockfile::Parser ### # Parses lockfiles def initialize(tokenizer, set, platforms, filename = nil) @tokens = tokenizer @filename = filename @set = set @platforms = platforms end def parse until @tokens.empty? do token = get case token.type when :section then @tokens.skip :newline case token.value when "DEPENDENCIES" then parse_DEPENDENCIES when "GIT" then parse_GIT when "GEM" then parse_GEM when "PATH" then parse_PATH when "PLATFORMS" then parse_PLATFORMS else token = get until @tokens.empty? || peek.first == :section end else raise "BUG: unhandled token #{token.type} (#{token.value.inspect}) at line #{token.line} column #{token.column}" end end end ## # Gets the next token for a Lockfile def get(expected_types = nil, expected_value = nil) # :nodoc: token = @tokens.shift if expected_types && !Array(expected_types).include?(token.type) unget token message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " \ "expected #{expected_types.inspect}" raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename end if expected_value && expected_value != token.value unget token message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " \ "expected [#{expected_types.inspect}, " \ "#{expected_value.inspect}]" raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename end token end def parse_DEPENDENCIES # :nodoc: while !@tokens.empty? && peek.type == :text do token = get :text requirements = [] case peek[0] when :bang then get :bang requirements << pinned_requirement(token.value) when :l_paren then get :l_paren loop do op = get(:requirement).value version = get(:text).value requirements << "#{op} #{version}" break unless peek.type == :comma get :comma end get :r_paren if peek[0] == :bang requirements.clear requirements << pinned_requirement(token.value) get :bang end end @set.gem token.value, *requirements skip :newline end end def parse_GEM # :nodoc: sources = [] while peek.first(2) == [:entry, "remote"] do get :entry, "remote" data = get(:text).value skip :newline sources << Gem::Source.new(data) end sources << Gem::Source.new(Gem::DEFAULT_HOST) if sources.empty? get :entry, "specs" skip :newline set = Gem::Resolver::LockSet.new sources last_specs = nil while !@tokens.empty? && peek.type == :text do token = get :text name = token.value column = token.column case peek[0] when :newline then last_specs.each do |spec| spec.add_dependency Gem::Dependency.new name if column == 6 end when :l_paren then get :l_paren token = get [:text, :requirement] type = token.type data = token.value if type == :text && column == 4 version, platform = data.split "-", 2 platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY last_specs = set.add name, version, platform else dependency = parse_dependency name, data last_specs.each do |spec| spec.add_dependency dependency end end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_GIT # :nodoc: get :entry, "remote" repository = get(:text).value skip :newline get :entry, "revision" revision = get(:text).value skip :newline type = peek.type value = peek.value if type == :entry && %w[branch ref tag].include?(value) get get :text skip :newline end get :entry, "specs" skip :newline set = Gem::Resolver::GitSet.new set.root_dir = @set.install_dir last_spec = nil while !@tokens.empty? && peek.type == :text do token = get :text name = token.value column = token.column case peek[0] when :newline then last_spec.add_dependency Gem::Dependency.new name if column == 6 when :l_paren then get :l_paren token = get [:text, :requirement] type = token.type data = token.value if type == :text && column == 4 last_spec = set.add_git_spec name, data, repository, revision, true else dependency = parse_dependency name, data last_spec.add_dependency dependency end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_PATH # :nodoc: get :entry, "remote" directory = get(:text).value skip :newline get :entry, "specs" skip :newline set = Gem::Resolver::VendorSet.new last_spec = nil while !@tokens.empty? && peek.first == :text do token = get :text name = token.value column = token.column case peek[0] when :newline then last_spec.add_dependency Gem::Dependency.new name if column == 6 when :l_paren then get :l_paren token = get [:text, :requirement] type = token.type data = token.value if type == :text && column == 4 last_spec = set.add_vendor_gem name, directory else dependency = parse_dependency name, data last_spec.dependencies << dependency end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_PLATFORMS # :nodoc: while !@tokens.empty? && peek.first == :text do name = get(:text).value @platforms << name skip :newline end end ## # Parses the requirements following the dependency +name+ and the +op+ for # the first token of the requirements and returns a Gem::Dependency object. def parse_dependency(name, op) # :nodoc: return Gem::Dependency.new name, op unless peek[0] == :text version = get(:text).value requirements = ["#{op} #{version}"] while peek.type == :comma do get :comma op = get(:requirement).value version = get(:text).value requirements << "#{op} #{version}" end Gem::Dependency.new name, requirements end private def skip(type) # :nodoc: @tokens.skip type end ## # Peeks at the next token for Lockfile def peek # :nodoc: @tokens.peek end def pinned_requirement(name) # :nodoc: requirement = Gem::Dependency.new name specification = @set.sets.flat_map do |set| set.find_all(requirement) end.compact.first specification&.version end ## # Ungets the last token retrieved by #get def unget(token) # :nodoc: @tokens.unshift token end end PK!J'""rubygems/source.rbnu[# frozen_string_literal: true require_relative "text" ## # A Source knows how to list and fetch gems from a RubyGems marshal index. # # There are other Source subclasses for installed gems, local gems, the # bundler dependency API and so-forth. class Gem::Source include Comparable include Gem::Text FILES = { # :nodoc: released: "specs", latest: "latest_specs", prerelease: "prerelease_specs", }.freeze ## # The URI this source will fetch gems from. attr_reader :uri ## # Creates a new Source which will use the index located at +uri+. def initialize(uri) require_relative "uri" @uri = Gem::Uri.parse!(uri) @update_cache = nil end ## # Sources are ordered by installation preference. def <=>(other) case other when Gem::Source::Installed, Gem::Source::Local, Gem::Source::Lock, Gem::Source::SpecificFile, Gem::Source::Git, Gem::Source::Vendor then -1 when Gem::Source then unless @uri return 0 unless other.uri return 1 end return -1 unless other.uri # Returning 1 here ensures that when sorting a list of sources, the # original ordering of sources supplied by the user is preserved. return 1 unless @uri.to_s == other.uri.to_s 0 end end def ==(other) # :nodoc: self.class === other && @uri == other.uri end alias_method :eql?, :== # :nodoc: ## # Returns a Set that can fetch specifications from this source. def dependency_resolver_set # :nodoc: return Gem::Resolver::IndexSet.new self if uri.scheme == "file" fetch_uri = if uri.host == "rubygems.org" index_uri = uri.dup index_uri.host = "index.rubygems.org" index_uri else uri end bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" begin fetcher = Gem::RemoteFetcher.fetcher response = fetcher.fetch_path bundler_api_uri, nil, true rescue Gem::RemoteFetcher::FetchError Gem::Resolver::IndexSet.new self else Gem::Resolver::APISet.new response.uri + "./info/" end end def hash # :nodoc: @uri.hash end ## # Returns the local directory to write +uri+ to. def cache_dir(uri) # Correct for windows paths escaped_path = uri.path.sub(%r{^/([a-z]):/}i, '/\\1-/') File.join Gem.spec_cache_dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path) end ## # Returns true when it is possible and safe to update the cache directory. def update_cache? return @update_cache unless @update_cache.nil? @update_cache = begin File.stat(Gem.user_home).uid == Process.uid rescue Errno::ENOENT false end end ## # Fetches a specification for the given +name_tuple+. def fetch_spec(name_tuple) fetcher = Gem::RemoteFetcher.fetcher spec_file_name = name_tuple.spec_name source_uri = enforce_trailing_slash(uri) + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}" cache_dir = cache_dir source_uri local_spec = File.join cache_dir, spec_file_name if File.exist? local_spec spec = Gem.read_binary local_spec Gem.load_safe_marshal spec = begin Gem::SafeMarshal.safe_load(spec) rescue StandardError nil end return spec if spec end source_uri.path << ".rz" spec = fetcher.fetch_path source_uri spec = Gem::Util.inflate spec if update_cache? require "fileutils" FileUtils.mkdir_p cache_dir File.open local_spec, "wb" do |io| io.write spec end end Gem.load_safe_marshal # TODO: Investigate setting Gem::Specification#loaded_from to a URI Gem::SafeMarshal.safe_load spec end ## # Loads +type+ kind of specs fetching from +@uri+ if the on-disk cache is # out of date. # # +type+ is one of the following: # # :released => Return the list of all released specs # :latest => Return the list of only the highest version of each gem # :prerelease => Return the list of all prerelease only specs # def load_specs(type) file = FILES[type] fetcher = Gem::RemoteFetcher.fetcher file_name = "#{file}.#{Gem.marshal_version}" spec_path = enforce_trailing_slash(uri) + "#{file_name}.gz" cache_dir = cache_dir spec_path local_file = File.join(cache_dir, file_name) retried = false if update_cache? require "fileutils" FileUtils.mkdir_p cache_dir end spec_dump = fetcher.cache_update_path spec_path, local_file, update_cache? Gem.load_safe_marshal begin Gem::NameTuple.from_list Gem::SafeMarshal.safe_load(spec_dump) rescue ArgumentError if update_cache? && !retried FileUtils.rm local_file retried = true retry else raise Gem::Exception.new("Invalid spec cache file in #{local_file}") end end end ## # Downloads +spec+ and writes it to +dir+. See also # Gem::RemoteFetcher#download. def download(spec, dir=Dir.pwd) fetcher = Gem::RemoteFetcher.fetcher fetcher.download spec, uri.to_s, dir end def pretty_print(q) # :nodoc: q.object_group(self) do q.group 2, "[Remote:", "]" do q.breakable q.text @uri.to_s if api = uri q.breakable q.text "API URI: " q.text api.to_s end end end end def typo_squatting?(host, distance_threshold=4) return if @uri.host.nil? levenshtein_distance(@uri.host, host).between? 1, distance_threshold end private def enforce_trailing_slash(uri) uri.merge(uri.path.gsub(%r{/+$}, "") + "/") end end require_relative "source/git" require_relative "source/installed" require_relative "source/specific_file" require_relative "source/local" require_relative "source/lock" require_relative "source/vendor" PK!:> rubygems/source_list.rbnu[# frozen_string_literal: true ## # The SourceList represents the sources rubygems has been configured to use. # A source may be created from an array of sources: # # Gem::SourceList.from %w[https://rubygems.example https://internal.example] # # Or by adding them: # # sources = Gem::SourceList.new # sources << 'https://rubygems.example' # # The most common way to get a SourceList is Gem.sources. class Gem::SourceList include Enumerable ## # Creates a new SourceList def initialize @sources = [] end ## # The sources in this list attr_reader :sources ## # Creates a new SourceList from an array of sources. def self.from(ary) list = new list.replace ary list end def initialize_copy(other) # :nodoc: @sources = @sources.dup end ## # Appends +obj+ to the source list which may be a Gem::Source, Gem::URI or URI # String. def <<(obj) src = case obj when Gem::Source obj else Gem::Source.new(obj) end @sources << src unless @sources.include?(src) src end ## # Replaces this SourceList with the sources in +other+ See #<< for # acceptable items in +other+. def replace(other) clear other.each do |x| self << x end self end ## # Removes all sources from the SourceList. def clear @sources.clear end ## # Yields each source URI in the list. def each @sources.each {|s| yield s.uri.to_s } end ## # Yields each source in the list. def each_source(&b) @sources.each(&b) end ## # Returns true if there are no sources in this SourceList. def empty? @sources.empty? end def ==(other) # :nodoc: to_a == other end ## # Returns an Array of source URI Strings. def to_a @sources.map {|x| x.uri.to_s } end alias_method :to_ary, :to_a ## # Returns the first source in the list. def first @sources.first end ## # Returns true if this source list includes +other+ which may be a # Gem::Source or a source URI. def include?(other) if other.is_a? Gem::Source @sources.include? other else @sources.find {|x| x.uri.to_s == other.to_s } end end ## # Deletes +source+ from the source list which may be a Gem::Source or a URI. def delete(source) if source.is_a? Gem::Source @sources.delete source else @sources.delete_if {|x| x.uri.to_s == source.to_s } end end end PK!vrubygems/vendored_timeout.rbnu[# frozen_string_literal: true # Ruby 3.3 and RubyGems 3.5 is already load Gem::Timeout from lib/rubygems/timeout.rb # We should avoid to load it again require_relative "vendor/timeout/lib/timeout" unless defined?(Gem::Timeout) PK!>T%%rubygems/resolver.rbnu[# frozen_string_literal: true require_relative "dependency" require_relative "exceptions" require_relative "util/list" ## # Given a set of Gem::Dependency objects as +needed+ and a way to query the # set of available specs via +set+, calculates a set of ActivationRequest # objects which indicate all the specs that should be activated to meet the # all the requirements. class Gem::Resolver require_relative "vendored_molinillo" ## # If the DEBUG_RESOLVER environment variable is set then debugging mode is # enabled for the resolver. This will display information about the state # of the resolver while a set of dependencies is being resolved. DEBUG_RESOLVER = !ENV["DEBUG_RESOLVER"].nil? ## # Set to true if all development dependencies should be considered. attr_accessor :development ## # Set to true if immediate development dependencies should be considered. attr_accessor :development_shallow ## # When true, no dependencies are looked up for requested gems. attr_accessor :ignore_dependencies ## # List of dependencies that could not be found in the configured sources. attr_reader :stats ## # Hash of gems to skip resolution. Keyed by gem name, with arrays of # gem specifications as values. attr_accessor :skip_gems ## # attr_accessor :soft_missing ## # Combines +sets+ into a ComposedSet that allows specification lookup in a # uniform manner. If one of the +sets+ is itself a ComposedSet its sets are # flattened into the result ComposedSet. def self.compose_sets(*sets) sets.compact! sets = sets.map do |set| case set when Gem::Resolver::BestSet then set when Gem::Resolver::ComposedSet then set.sets else set end end.flatten case sets.length when 0 then raise ArgumentError, "one set in the composition must be non-nil" when 1 then sets.first else Gem::Resolver::ComposedSet.new(*sets) end end ## # Creates a Resolver that queries only against the already installed gems # for the +needed+ dependencies. def self.for_current_gems(needed) new needed, Gem::Resolver::CurrentSet.new end ## # Create Resolver object which will resolve the tree starting # with +needed+ Dependency objects. # # +set+ is an object that provides where to look for specifications to # satisfy the Dependencies. This defaults to IndexSet, which will query # rubygems.org. def initialize(needed, set = nil) @set = set || Gem::Resolver::IndexSet.new @needed = needed @development = false @development_shallow = false @ignore_dependencies = false @skip_gems = {} @soft_missing = false @stats = Gem::Resolver::Stats.new end def explain(stage, *data) # :nodoc: return unless DEBUG_RESOLVER d = data.map(&:pretty_inspect).join(", ") $stderr.printf "%10s %s\n", stage.to_s.upcase, d end def explain_list(stage) # :nodoc: return unless DEBUG_RESOLVER data = yield $stderr.printf "%10s (%d entries)\n", stage.to_s.upcase, data.size unless data.empty? require "pp" PP.pp data, $stderr end end ## # Creates an ActivationRequest for the given +dep+ and the last +possible+ # specification. # # Returns the Specification and the ActivationRequest def activation_request(dep, possible) # :nodoc: spec = possible.pop explain :activate, [spec.full_name, possible.size] explain :possible, possible activation_request = Gem::Resolver::ActivationRequest.new spec, dep, possible [spec, activation_request] end def requests(s, act, reqs=[]) # :nodoc: return reqs if @ignore_dependencies s.fetch_development_dependencies if @development s.dependencies.reverse_each do |d| next if d.type == :development && !@development next if d.type == :development && @development_shallow && act.development? next if d.type == :development && @development_shallow && act.parent reqs << Gem::Resolver::DependencyRequest.new(d, act) @stats.requirement! end @set.prefetch reqs @stats.record_requirements reqs reqs end include Gem::Molinillo::UI def output @output ||= debug? ? $stdout : File.open(IO::NULL, "w") end def debug? DEBUG_RESOLVER end include Gem::Molinillo::SpecificationProvider ## # Proceed with resolution! Returns an array of ActivationRequest objects. def resolve Gem::Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }).tsort.map(&:payload).compact rescue Gem::Molinillo::VersionConflict => e conflict = e.conflicts.values.first raise Gem::DependencyResolutionError, Conflict.new(conflict.requirement_trees.first.first, conflict.existing, conflict.requirement) ensure @output.close if defined?(@output) && !debug? end ## # Extracts the specifications that may be able to fulfill +dependency+ and # returns those that match the local platform and all those that match. def find_possible(dependency) # :nodoc: all = @set.find_all dependency if (skip_dep_gems = skip_gems[dependency.name]) && !skip_dep_gems.empty? matching = all.select do |api_spec| skip_dep_gems.any? {|s| api_spec.version == s.version } end all = matching unless matching.empty? end matching_platform = select_local_platforms all [matching_platform, all] end ## # Returns the gems in +specs+ that match the local platform. def select_local_platforms(specs) # :nodoc: specs.select do |spec| Gem::Platform.installable? spec end end def search_for(dependency) possibles, all = find_possible(dependency) if !@soft_missing && possibles.empty? exc = Gem::UnsatisfiableDependencyError.new dependency, all exc.errors = @set.errors raise exc end groups = Hash.new {|hash, key| hash[key] = [] } # create groups & sources in the same loop sources = possibles.map do |spec| source = spec.source groups[source] << spec source end.uniq.reverse activation_requests = [] sources.each do |source| groups[source]. sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch map {|spec| ActivationRequest.new spec, dependency }. each {|activation_request| activation_requests << activation_request } end activation_requests end def dependencies_for(specification) return [] if @ignore_dependencies spec = specification.spec requests(spec, specification) end def requirement_satisfied_by?(requirement, activated, spec) matches_spec = requirement.matches_spec? spec return matches_spec if @soft_missing matches_spec && spec.spec.required_ruby_version.satisfied_by?(Gem.ruby_version) && spec.spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version) end def name_for(dependency) dependency.name end def allow_missing?(dependency) @soft_missing end def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by.with_index do |dependency, i| name = name_for(dependency) [ activated.vertex_named(name).payload ? 0 : 1, amount_constrained(dependency), conflicts[name] ? 0 : 1, activated.vertex_named(name).payload ? 0 : search_for(dependency).count, i, # for stable sort ] end end SINGLE_POSSIBILITY_CONSTRAINT_PENALTY = 1_000_000 private_constant :SINGLE_POSSIBILITY_CONSTRAINT_PENALTY if defined?(private_constant) # returns an integer \in (-\infty, 0] # a number closer to 0 means the dependency is less constraining # # dependencies w/ 0 or 1 possibilities (ignoring version requirements) # are given very negative values, so they _always_ sort first, # before dependencies that are unconstrained def amount_constrained(dependency) @amount_constrained ||= {} @amount_constrained[dependency.name] ||= begin name_dependency = Gem::Dependency.new(dependency.name) dependency_request_for_name = Gem::Resolver::DependencyRequest.new(name_dependency, dependency.requester) all = @set.find_all(dependency_request_for_name).size if all <= 1 all - SINGLE_POSSIBILITY_CONSTRAINT_PENALTY else search = search_for(dependency).size search - all end end end private :amount_constrained end require_relative "resolver/activation_request" require_relative "resolver/conflict" require_relative "resolver/dependency_request" require_relative "resolver/requirement_list" require_relative "resolver/stats" require_relative "resolver/set" require_relative "resolver/api_set" require_relative "resolver/composed_set" require_relative "resolver/best_set" require_relative "resolver/current_set" require_relative "resolver/git_set" require_relative "resolver/index_set" require_relative "resolver/installer_set" require_relative "resolver/lock_set" require_relative "resolver/vendor_set" require_relative "resolver/source_set" require_relative "resolver/specification" require_relative "resolver/spec_specification" require_relative "resolver/api_specification" require_relative "resolver/git_specification" require_relative "resolver/index_specification" require_relative "resolver/installed_specification" require_relative "resolver/local_specification" require_relative "resolver/lock_specification" require_relative "resolver/vendor_specification" PK!Fߺ̎#rubygems/core_ext/tcpsocket_init.rbnu[# frozen_string_literal: true require "socket" module CoreExtensions module TCPSocketExt def self.prepended(base) base.prepend Initializer end module Initializer CONNECTION_TIMEOUT = 5 IPV4_DELAY_SECONDS = 0.1 def initialize(host, serv, *rest) mutex = Thread::Mutex.new addrs = [] threads = [] cond_var = Thread::ConditionVariable.new Addrinfo.foreach(host, serv, nil, :STREAM) do |addr| Thread.report_on_exception = false threads << Thread.new(addr) do # give head start to ipv6 addresses sleep IPV4_DELAY_SECONDS if addr.ipv4? # raises Errno::ECONNREFUSED when ip:port is unreachable Socket.tcp(addr.ip_address, serv, connect_timeout: CONNECTION_TIMEOUT).close mutex.synchronize do addrs << addr.ip_address cond_var.signal end end end mutex.synchronize do timeout_time = CONNECTION_TIMEOUT + Time.now.to_f while addrs.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0 cond_var.wait(mutex, remaining_time) end host = addrs.shift unless addrs.empty? end threads.each {|t| t.kill.join if t.alive? } super(host, serv, *rest) end end end end TCPSocket.prepend CoreExtensions::TCPSocketExt PK!SS#rubygems/core_ext/kernel_require.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require "monitor" module Kernel RUBYGEMS_ACTIVATION_MONITOR = Monitor.new # :nodoc: # Make sure we have a reference to Ruby's original Kernel#require unless defined?(gem_original_require) # :stopdoc: alias_method :gem_original_require, :require private :gem_original_require # :startdoc: end ## # When RubyGems is required, Kernel#require is replaced with our own which # is capable of loading gems on demand. # # When you call require 'x', this is what happens: # * If the file can be loaded from the existing Ruby loadpath, it # is. # * Otherwise, installed gems are searched for a file that matches. # If it's found in gem 'y', that gem is activated (added to the # loadpath). # # The normal require functionality of returning false if # that file has already been loaded is preserved. def require(path) # :doc: return gem_original_require(path) unless Gem.discover_gems_on_require RUBYGEMS_ACTIVATION_MONITOR.synchronize do path = File.path(path) # If +path+ belongs to a default gem, we activate it and then go straight # to normal require if spec = Gem.find_default_spec(path) name = spec.name next if Gem.loaded_specs[name] # Ensure -I beats a default gem resolved_path = begin rp = nil load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths Gem.suffixes.find do |s| $LOAD_PATH[0...load_path_check_index].find do |lp| if File.symlink? lp # for backward compatibility next end full_path = File.expand_path(File.join(lp, "#{path}#{s}")) rp = full_path if File.file?(full_path) end end rp end Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless resolved_path next end # If there are no unresolved deps, then we can use just try # normal require handle loading a gem from the rescue below. if Gem::Specification.unresolved_deps.empty? next end # If +path+ is for a gem that has already been loaded, don't # bother trying to find it in an unresolved gem, just go straight # to normal require. #-- # TODO request access to the C implementation of this to speed up RubyGems if Gem::Specification.find_active_stub_by_path(path) next end # Attempt to find +path+ in any unresolved gems... found_specs = Gem::Specification.find_in_unresolved path # If there are no directly unresolved gems, then try and find +path+ # in any gems that are available via the currently unresolved gems. # For example, given: # # a => b => c => d # # If a and b are currently active with c being unresolved and d.rb is # requested, then find_in_unresolved_tree will find d.rb in d because # it's a dependency of c. # if found_specs.empty? found_specs = Gem::Specification.find_in_unresolved_tree path found_specs.each(&:activate) # We found +path+ directly in an unresolved gem. Now we figure out, of # the possible found specs, which one we should activate. else # Check that all the found specs are just different # versions of the same gem names = found_specs.map(&:name).uniq if names.size > 1 raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ", "}" end # Ok, now find a gem that has no conflicts, starting # at the highest version. valid = found_specs.find {|s| !s.has_conflicts? } unless valid le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" le.name = names.first raise le end valid.activate end end begin gem_original_require(path) rescue LoadError => load_error if load_error.path == path && RUBYGEMS_ACTIVATION_MONITOR.synchronize { Gem.try_activate(path) } return gem_original_require(path) end raise load_error end end private :require end PK!irubygems/core_ext/kernel_gem.rbnu[# frozen_string_literal: true module Kernel ## # Use Kernel#gem to activate a specific version of +gem_name+. # # +requirements+ is a list of version requirements that the # specified gem must match, most commonly "= example.version.number". See # Gem::Requirement for how to specify a version requirement. # # If you will be activating the latest version of a gem, there is no need to # call Kernel#gem, Kernel#require will do the right thing for you. # # Kernel#gem returns true if the gem was activated, otherwise false. If the # gem could not be found, didn't match the version requirements, or a # different version was already activated, an exception will be raised. # # Kernel#gem should be called *before* any require statements (otherwise # RubyGems may load a conflicting library version). # # Kernel#gem only loads prerelease versions when prerelease +requirements+ # are given: # # gem 'rake', '>= 1.1.a', '< 2' # # In older RubyGems versions, the environment variable GEM_SKIP could be # used to skip activation of specified gems, for example to test out changes # that haven't been installed yet. Now RubyGems defers to -I and the # RUBYLIB environment variable to skip activation of a gem. # # Example: # # GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb def gem(gem_name, *requirements) # :doc: skip_list = (ENV["GEM_SKIP"] || "").split(/:/) raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name if gem_name.is_a? Gem::Dependency unless Gem::Deprecate.skip warn "#{Gem.location_of_caller.join ":"}:Warning: Kernel.gem no longer "\ "accepts a Gem::Dependency object, please pass the name "\ "and requirements directly" end requirements = gem_name.requirement gem_name = gem_name.name end dep = Gem::Dependency.new(gem_name, *requirements) loaded = Gem.loaded_specs[gem_name] return false if loaded && dep.matches_spec?(loaded) spec = dep.to_spec if spec if Gem::LOADED_SPECS_MUTEX.owned? spec.activate else Gem::LOADED_SPECS_MUTEX.synchronize { spec.activate } end end end private :gem end PK!WW rubygems/core_ext/kernel_warn.rbnu[# frozen_string_literal: true module Kernel rubygems_path = "#{__dir__}/" # Frames to be skipped start with this path. original_warn = instance_method(:warn) remove_method :warn class << self remove_method :warn end module_function define_method(:warn) {|*messages, **kw| unless uplevel = kw[:uplevel] if Gem.java_platform? && RUBY_VERSION < "3.1" return original_warn.bind(self).call(*messages) else return original_warn.bind(self).call(*messages, **kw) end end # Ensure `uplevel` fits a `long` uplevel, = [uplevel].pack("l!").unpack("l!") if uplevel >= 0 start = 0 while uplevel >= 0 loc, = caller_locations(start, 1) unless loc # No more backtrace start += uplevel break end start += 1 next unless path = loc.path unless path.start_with?(rubygems_path, " # # Otherwise Gem::Source#<=> is used. def <=>(other) case other when Gem::Source::SpecificFile then return nil if @spec.name != other.spec.name @spec.version <=> other.spec.version else super end end end PK!''rrrubygems/source/git.rbnu[# frozen_string_literal: true ## # A git gem for use in a gem dependencies file. # # Example: # # source = # Gem::Source::Git.new 'rake', 'git@example:rake.git', 'rake-10.1.0', false # # source.specs class Gem::Source::Git < Gem::Source ## # The name of the gem created by this git gem. attr_reader :name ## # The commit reference used for checking out this git gem. attr_reader :reference ## # When false the cache for this repository will not be updated. attr_accessor :remote ## # The git repository this gem is sourced from. attr_reader :repository ## # The directory for cache and git gem installation attr_accessor :root_dir ## # Does this repository need submodules checked out too? attr_reader :need_submodules ## # Creates a new git gem source for a gems from loaded from +repository+ at # the given +reference+. The +name+ is only used to track the repository # back to a gem dependencies file, it has no real significance as a git # repository may contain multiple gems. If +submodules+ is true, submodules # will be checked out when the gem is installed. def initialize(name, repository, reference, submodules = false) require_relative "../uri" @uri = Gem::Uri.parse(repository) @name = name @repository = repository @reference = reference || "HEAD" @need_submodules = submodules @remote = true @root_dir = Gem.dir @git = ENV["git"] || "git" end def <=>(other) case other when Gem::Source::Git then 0 when Gem::Source::Vendor, Gem::Source::Lock then -1 when Gem::Source then 1 end end def ==(other) # :nodoc: super && @name == other.name && @repository == other.repository && @reference == other.reference && @need_submodules == other.need_submodules end ## # Checks out the files for the repository into the install_dir. def checkout # :nodoc: cache return false unless File.exist? repo_cache_dir unless File.exist? install_dir system @git, "clone", "--quiet", "--no-checkout", repo_cache_dir, install_dir end Dir.chdir install_dir do system @git, "fetch", "--quiet", "--force", "--tags", install_dir success = system @git, "reset", "--quiet", "--hard", rev_parse if @need_submodules require "open3" _, status = Open3.capture2e(@git, "submodule", "update", "--quiet", "--init", "--recursive") success &&= status.success? end success end end ## # Creates a local cache repository for the git gem. def cache # :nodoc: return unless @remote if File.exist? repo_cache_dir Dir.chdir repo_cache_dir do system @git, "fetch", "--quiet", "--force", "--tags", @repository, "refs/heads/*:refs/heads/*" end else system @git, "clone", "--quiet", "--bare", "--no-hardlinks", @repository, repo_cache_dir end end ## # Directory where git gems get unpacked and so-forth. def base_dir # :nodoc: File.join @root_dir, "bundler" end ## # A short reference for use in git gem directories def dir_shortref # :nodoc: rev_parse[0..11] end ## # Nothing to download for git gems def download(full_spec, path) # :nodoc: end ## # The directory where the git gem will be installed. def install_dir # :nodoc: return unless File.exist? repo_cache_dir File.join base_dir, "gems", "#{@name}-#{dir_shortref}" end def pretty_print(q) # :nodoc: q.object_group(self) do q.group 2, "[Git: ", "]" do q.breakable q.text @repository q.breakable q.text @reference end end end ## # The directory where the git gem's repository will be cached. def repo_cache_dir # :nodoc: File.join @root_dir, "cache", "bundler", "git", "#{@name}-#{uri_hash}" end ## # Converts the git reference for the repository into a commit hash. def rev_parse # :nodoc: hash = nil Dir.chdir repo_cache_dir do hash = Gem::Util.popen(@git, "rev-parse", @reference).strip end raise Gem::Exception, "unable to find reference #{@reference} in #{@repository}" unless $?.success? hash end ## # Loads all gemspecs in the repository def specs checkout return [] unless install_dir Dir.chdir install_dir do Dir["{,*,*/*}.gemspec"].map do |spec_file| directory = File.dirname spec_file file = File.basename spec_file Dir.chdir directory do spec = Gem::Specification.load file if spec spec.base_dir = base_dir spec.extension_dir = File.join base_dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version, "#{name}-#{dir_shortref}" spec.full_gem_path = File.dirname spec.loaded_from if spec end spec end end.compact end end ## # A hash for the git gem based on the git repository Gem::URI. def uri_hash # :nodoc: require_relative "../openssl" normalized = if @repository.match?(%r{^\w+://(\w+@)?}) uri = Gem::URI(@repository).normalize.to_s.sub %r{/$},"" uri.sub(/\A(\w+)/) { $1.downcase } else @repository end OpenSSL::Digest::SHA1.hexdigest normalized end end PK!¨y-  rubygems/source/local.rbnu[# frozen_string_literal: true ## # The local source finds gems in the current directory for fulfilling # dependencies. class Gem::Source::Local < Gem::Source def initialize # :nodoc: @specs = nil @api_uri = nil @uri = nil @load_specs_names = {} end ## # Local sorts before Gem::Source and after Gem::Source::Installed def <=>(other) case other when Gem::Source::Installed, Gem::Source::Lock then -1 when Gem::Source::Local then 0 when Gem::Source then 1 end end def inspect # :nodoc: keys = @specs ? @specs.keys.sort : "NOT LOADED" format("#<%s specs: %p>", self.class, keys) end def load_specs(type) # :nodoc: @load_specs_names[type] ||= begin names = [] @specs = {} Dir["*.gem"].each do |file| pkg = Gem::Package.new(file) spec = pkg.spec rescue SystemCallError, Gem::Package::FormatError # ignore else tup = spec.name_tuple @specs[tup] = [File.expand_path(file), pkg] case type when :released unless pkg.spec.version.prerelease? names << pkg.spec.name_tuple end when :prerelease if pkg.spec.version.prerelease? names << pkg.spec.name_tuple end when :latest tup = pkg.spec.name_tuple cur = names.find {|x| x.name == tup.name } if !cur names << tup elsif cur.version < tup.version names.delete cur names << tup end else names << pkg.spec.name_tuple end end names end end def find_gem(gem_name, version = Gem::Requirement.default, prerelease = false) # :nodoc: load_specs :complete found = [] @specs.each do |n, data| next unless n.name == gem_name s = data[1].spec if version.satisfied_by?(s.version) if prerelease found << s elsif !s.version.prerelease? || version.prerelease? found << s end end end found.max_by(&:version) end def fetch_spec(name) # :nodoc: load_specs :complete if data = @specs[name] data.last.spec else raise Gem::Exception, "Unable to find spec for #{name.inspect}" end end def download(spec, cache_dir = nil) # :nodoc: load_specs :complete @specs.each do |_name, data| return data[0] if data[1].spec == spec end raise Gem::Exception, "Unable to find file for '#{spec.full_name}'" end def pretty_print(q) # :nodoc: q.object_group(self) do q.group 2, "[Local gems:", "]" do q.breakable if @specs q.seplist @specs.keys do |v| q.text v.full_name end end end end end end PK!_t'+rubygems/source/vendor.rbnu[# frozen_string_literal: true ## # This represents a vendored source that is similar to an installed gem. class Gem::Source::Vendor < Gem::Source::Installed ## # Creates a new Vendor source for a gem that was unpacked at +path+. def initialize(path) @uri = path end def <=>(other) case other when Gem::Source::Lock then -1 when Gem::Source::Vendor then 0 when Gem::Source then 1 end end end PK!Gcrubygems/source/installed.rbnu[# frozen_string_literal: true ## # Represents an installed gem. This is used for dependency resolution. class Gem::Source::Installed < Gem::Source def initialize # :nodoc: @uri = nil end ## # Installed sources sort before all other sources def <=>(other) case other when Gem::Source::Git, Gem::Source::Lock, Gem::Source::Vendor then -1 when Gem::Source::Installed then 0 when Gem::Source then 1 end end ## # We don't need to download an installed gem def download(spec, path) nil end def pretty_print(q) # :nodoc: q.object_group(self) do q.text "[Installed]" end end end PK!_Nrubygems/source/lock.rbnu[# frozen_string_literal: true ## # A Lock source wraps an installed gem's source and sorts before other sources # during dependency resolution. This allows RubyGems to prefer gems from # dependency lock files. class Gem::Source::Lock < Gem::Source ## # The wrapped Gem::Source attr_reader :wrapped ## # Creates a new Lock source that wraps +source+ and moves it earlier in the # sort list. def initialize(source) @wrapped = source end def <=>(other) # :nodoc: case other when Gem::Source::Lock then @wrapped <=> other.wrapped when Gem::Source then 1 end end def ==(other) # :nodoc: (self <=> other) == 0 end def hash # :nodoc: @wrapped.hash ^ 3 end ## # Delegates to the wrapped source's fetch_spec method. def fetch_spec(name_tuple) @wrapped.fetch_spec name_tuple end def uri # :nodoc: @wrapped.uri end end PK!ڄ(?(?rubygems/config_file.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "user_interaction" require "rbconfig" ## # Gem::ConfigFile RubyGems options and gem command options from gemrc. # # gemrc is a YAML file that uses strings to match gem command arguments and # symbols to match RubyGems options. # # Gem command arguments use a String key that matches the command name and # allow you to specify default arguments: # # install: --no-rdoc --no-ri # update: --no-rdoc --no-ri # # You can use gem: to set default arguments for all commands. # # RubyGems options use symbol keys. Valid options are: # # +:backtrace+:: See #backtrace # +:sources+:: Sets Gem::sources # +:verbose+:: See #verbose # +:concurrent_downloads+:: See #concurrent_downloads # # gemrc files may exist in various locations and are read and merged in # the following order: # # - system wide (/etc/gemrc) # - per user (~/.gemrc) # - per environment (gemrc files listed in the GEMRC environment variable) class Gem::ConfigFile include Gem::UserInteraction DEFAULT_BACKTRACE = true DEFAULT_BULK_THRESHOLD = 1000 DEFAULT_VERBOSITY = true DEFAULT_UPDATE_SOURCES = true DEFAULT_CONCURRENT_DOWNLOADS = 8 DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365 DEFAULT_IPV4_FALLBACK_ENABLED = false ## # For Ruby packagers to set configuration defaults. Set in # rubygems/defaults/operating_system.rb OPERATING_SYSTEM_DEFAULTS = Gem.operating_system_defaults ## # For Ruby implementers to set configuration defaults. Set in # rubygems/defaults/#{RUBY_ENGINE}.rb PLATFORM_DEFAULTS = Gem.platform_defaults # :stopdoc: SYSTEM_CONFIG_PATH = begin require "etc" Etc.sysconfdir rescue LoadError, NoMethodError RbConfig::CONFIG["sysconfdir"] || "/etc" end # :startdoc: SYSTEM_WIDE_CONFIG_FILE = File.join SYSTEM_CONFIG_PATH, "gemrc" ## # List of arguments supplied to the config file object. attr_reader :args ## # Where to look for gems (deprecated) attr_accessor :path ## # Where to install gems (deprecated) attr_accessor :home ## # True if we print backtraces on errors. attr_writer :backtrace ## # Bulk threshold value. If the number of missing gems are above this # threshold value, then a bulk download technique is used. (deprecated) attr_accessor :bulk_threshold ## # Verbose level of output: # * false -- No output # * true -- Normal output # * :loud -- Extra output attr_accessor :verbose ## # Number of gem downloads that should be performed concurrently. attr_accessor :concurrent_downloads ## # True if we want to update the SourceInfoCache every time, false otherwise attr_accessor :update_sources ## # True if we want to force specification of gem server when pushing a gem attr_accessor :disable_default_gem_server # openssl verify mode value, used for remote https connection attr_reader :ssl_verify_mode ## # Path name of directory or file of openssl CA certificate, used for remote # https connection attr_accessor :ssl_ca_cert ## # sources to look for gems attr_accessor :sources ## # Expiration length to sign a certificate attr_accessor :cert_expiration_length_days ## # == Experimental == # Fallback to IPv4 when IPv6 is not reachable or slow (default: false) attr_accessor :ipv4_fallback_enabled ## # Path name of directory or file of openssl client certificate, used for remote https connection with client authentication attr_reader :ssl_client_cert ## # Create the config file object. +args+ is the list of arguments # from the command line. # # The following command line options are handled early here rather # than later at the time most command options are processed. # # --config-file, --config-file==NAME:: # Obviously these need to be handled by the ConfigFile object to ensure we # get the right config file. # # --backtrace:: # Backtrace needs to be turned on early so that errors before normal # option parsing can be properly handled. # # --debug:: # Enable Ruby level debug messages. Handled early for the same reason as # --backtrace. #-- # TODO: parse options upstream, pass in options directly def initialize(args) set_config_file_name(args) @backtrace = DEFAULT_BACKTRACE @bulk_threshold = DEFAULT_BULK_THRESHOLD @verbose = DEFAULT_VERBOSITY @update_sources = DEFAULT_UPDATE_SOURCES @concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS @cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS @ipv4_fallback_enabled = ENV["IPV4_FALLBACK_ENABLED"] == "true" || DEFAULT_IPV4_FALLBACK_ENABLED operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS) platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS) system_config = load_file SYSTEM_WIDE_CONFIG_FILE user_config = load_file config_file_name environment_config = (ENV["GEMRC"] || ""). split(File::PATH_SEPARATOR).inject({}) do |result, file| result.merge load_file file end @hash = operating_system_config.merge platform_config unless args.index "--norc" @hash = @hash.merge system_config @hash = @hash.merge user_config @hash = @hash.merge environment_config end @hash.transform_keys! do |k| # gemhome and gempath are not working with symbol keys if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days install_extension_in_lib ipv4_fallback_enabled sources disable_default_gem_server ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k) k.to_sym else k end end # HACK: these override command-line args, which is bad @backtrace = @hash[:backtrace] if @hash.key? :backtrace @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold @verbose = @hash[:verbose] if @hash.key? :verbose @update_sources = @hash[:update_sources] if @hash.key? :update_sources # TODO: We should handle concurrent_downloads same as other options @cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days @ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled @home = @hash[:gemhome] if @hash.key? :gemhome @path = @hash[:gempath] if @hash.key? :gempath @sources = @hash[:sources] if @hash.key? :sources @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert @api_keys = nil @rubygems_api_key = nil handle_arguments args end ## # Hash of RubyGems.org and alternate API keys def api_keys load_api_keys unless @api_keys @api_keys end ## # Checks the permissions of the credentials file. If they are not 0600 an # error message is displayed and RubyGems aborts. def check_credentials_permissions return if Gem.win_platform? # windows doesn't write 0600 as 0600 return unless File.exist? credentials_path existing_permissions = File.stat(credentials_path).mode & 0o777 return if existing_permissions == 0o600 alert_error <<-ERROR Your gem push credentials file located at: \t#{credentials_path} has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required. To fix this error run: \tchmod 0600 #{credentials_path} You should reset your credentials at: \thttps://rubygems.org/profile/edit if you believe they were disclosed to a third party. ERROR terminate_interaction 1 end ## # Location of RubyGems.org credentials def credentials_path credentials = File.join Gem.user_home, ".gem", "credentials" if File.exist? credentials credentials else File.join Gem.data_home, "gem", "credentials" end end def load_api_keys check_credentials_permissions @api_keys = if File.exist? credentials_path load_file(credentials_path) else @hash end if @api_keys.key? :rubygems_api_key @rubygems_api_key = @api_keys[:rubygems_api_key] @api_keys[:rubygems] = @api_keys.delete :rubygems_api_key unless @api_keys.key? :rubygems end end ## # Returns the RubyGems.org API key def rubygems_api_key load_api_keys unless @rubygems_api_key @rubygems_api_key end ## # Sets the RubyGems.org API key to +api_key+ def rubygems_api_key=(api_key) set_api_key :rubygems_api_key, api_key @rubygems_api_key = api_key end ## # Set a specific host's API key to +api_key+ def set_api_key(host, api_key) check_credentials_permissions config = load_file(credentials_path).merge(host => api_key) dirname = File.dirname credentials_path require "fileutils" FileUtils.mkdir_p(dirname) permissions = 0o600 & (~File.umask) File.open(credentials_path, "w", permissions) do |f| f.write self.class.dump_with_rubygems_yaml(config) end load_api_keys # reload end ## # Remove the +~/.gem/credentials+ file to clear all the current sessions. def unset_api_key! return false unless File.exist?(credentials_path) File.delete(credentials_path) end def load_file(filename) yaml_errors = [ArgumentError] return {} unless filename && !filename.empty? && File.exist?(filename) begin config = self.class.load_with_rubygems_config_hash(File.read(filename)) if config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") } warn "Failed to load #{filename} because it doesn't contain valid YAML hash" return {} else return config end rescue *yaml_errors => e warn "Failed to load #{filename}, #{e}" rescue Errno::EACCES warn "Failed to load #{filename} due to permissions problem." end {} end # True if the backtrace option has been specified, or debug is on. def backtrace @backtrace || $DEBUG end # Check state file is writable. Creates empty file if not present to ensure we can write to it. def state_file_writable? if File.exist?(state_file_name) File.writable?(state_file_name) else require "fileutils" FileUtils.mkdir_p File.dirname(state_file_name) File.open(state_file_name, "w") {} true end rescue Errno::EACCES false end # The name of the configuration file. def config_file_name @config_file_name || Gem.config_file end # The name of the state file. def state_file_name Gem.state_file end # Reads time of last update check from state file def last_update_check if File.readable?(state_file_name) File.read(state_file_name).to_i else 0 end end # Writes time of last update check to state file def last_update_check=(timestamp) File.write(state_file_name, timestamp.to_s) if state_file_writable? end # Delegates to @hash def each(&block) hash = @hash.dup hash.delete :update_sources hash.delete :verbose hash.delete :backtrace hash.delete :bulk_threshold yield :update_sources, @update_sources yield :verbose, @verbose yield :backtrace, @backtrace yield :bulk_threshold, @bulk_threshold yield "config_file_name", @config_file_name if @config_file_name hash.each(&block) end # Handle the command arguments. def handle_arguments(arg_list) @args = [] arg_list.each do |arg| case arg when /^--(backtrace|traceback)$/ then @backtrace = true when /^--debug$/ then $DEBUG = true warn "NOTE: Debugging mode prints all exceptions even when rescued" else @args << arg end end end # Really verbose mode gives you extra output. def really_verbose case verbose when true, false, nil then false else true end end # to_yaml only overwrites things you can't override on the command line. def to_yaml # :nodoc: yaml_hash = {} yaml_hash[:backtrace] = @hash.fetch(:backtrace, DEFAULT_BACKTRACE) yaml_hash[:bulk_threshold] = @hash.fetch(:bulk_threshold, DEFAULT_BULK_THRESHOLD) yaml_hash[:sources] = Gem.sources.to_a yaml_hash[:update_sources] = @hash.fetch(:update_sources, DEFAULT_UPDATE_SOURCES) yaml_hash[:verbose] = @hash.fetch(:verbose, DEFAULT_VERBOSITY) yaml_hash[:concurrent_downloads] = @hash.fetch(:concurrent_downloads, DEFAULT_CONCURRENT_DOWNLOADS) yaml_hash[:ssl_verify_mode] = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode yaml_hash[:ssl_ca_cert] = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert yaml_hash[:ssl_client_cert] = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert keys = yaml_hash.keys.map(&:to_s) keys << "debug" re = Regexp.union(*keys) @hash.each do |key, value| key = key.to_s next if key&.match?(re) yaml_hash[key.to_s] = value end self.class.dump_with_rubygems_yaml(yaml_hash) end # Writes out this config file, replacing its source. def write require "fileutils" FileUtils.mkdir_p File.dirname(config_file_name) File.open config_file_name, "w" do |io| io.write to_yaml end end # Return the configuration information for +key+. def [](key) @hash[key] || @hash[key.to_s] end # Set configuration option +key+ to +value+. def []=(key, value) @hash[key] = value end def ==(other) # :nodoc: self.class === other && @backtrace == other.backtrace && @bulk_threshold == other.bulk_threshold && @verbose == other.verbose && @update_sources == other.update_sources && @hash == other.hash end attr_reader :hash protected :hash def self.dump_with_rubygems_yaml(content) content.transform_keys! do |k| k.is_a?(Symbol) ? ":#{k}" : k end require_relative "yaml_serializer" Gem::YAMLSerializer.dump(content) end def self.load_with_rubygems_config_hash(yaml) require_relative "yaml_serializer" content = Gem::YAMLSerializer.load(yaml) deep_transform_config_keys!(content) end private def self.deep_transform_config_keys!(config) config.transform_keys! do |k| if k.match?(/\A:(.*)\Z/) k[1..-1].to_sym elsif k.include?("__") || k.match?(%r{/\Z}) if k.is_a?(Symbol) k.to_s.gsub(/__/,".").gsub(%r{/\Z}, "").to_sym else k.dup.gsub(/__/,".").gsub(%r{/\Z}, "") end else k end end config.transform_values! do |v| if v.is_a?(String) if v.match?(/\A:(.*)\Z/) v[1..-1].to_sym elsif v.match?(/\A[+-]?\d+\Z/) v.to_i elsif v.match?(/\Atrue|false\Z/) v == "true" elsif v.empty? nil else v end elsif v.empty? nil elsif v.is_a?(Hash) deep_transform_config_keys!(v) else v end end config end def set_config_file_name(args) @config_file_name = ENV["GEMRC"] need_config_file_name = false args.each do |arg| if need_config_file_name @config_file_name = arg need_config_file_name = false elsif arg =~ /^--config-file=(.*)/ @config_file_name = $1 elsif /^--config-file$/.match?(arg) need_config_file_name = true end end end end PK!Qe%%rubygems/remote_fetcher.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "request" require_relative "request/connection_pools" require_relative "s3_uri_signer" require_relative "uri_formatter" require_relative "uri" require_relative "user_interaction" ## # RemoteFetcher handles the details of fetching gems and gem information from # a remote source. class Gem::RemoteFetcher include Gem::UserInteraction ## # A FetchError exception wraps up the various possible IO and HTTP failures # that could happen while downloading from the internet. class FetchError < Gem::Exception ## # The URI which was being accessed when the exception happened. attr_accessor :uri, :original_uri def initialize(message, uri) uri = Gem::Uri.new(uri) super uri.redact_credentials_from(message) @original_uri = uri.to_s @uri = uri.redacted.to_s end def to_s # :nodoc: "#{super} (#{uri})" end end ## # A FetchError that indicates that the reason for not being # able to fetch data was that the host could not be contacted class UnknownHostError < FetchError end deprecate_constant(:UnknownHostError) @fetcher = nil ## # Cached RemoteFetcher instance. def self.fetcher @fetcher ||= new Gem.configuration[:http_proxy] end attr_accessor :headers ## # Initialize a remote fetcher using the source URI and possible proxy # information. # # +proxy+ # * [String]: explicit specification of proxy; overrides any environment # variable setting # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, # HTTP_PROXY_PASS) # * :no_proxy: ignore environment variables and _don't_ use a proxy # # +headers+: A set of additional HTTP headers to be sent to the server when # fetching the gem. def initialize(proxy=nil, dns=nil, headers={}) require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled require_relative "vendored_net_http" require_relative "vendor/uri/lib/uri" Socket.do_not_reverse_lookup = true @proxy = proxy @pools = {} @pool_lock = Thread::Mutex.new @cert_files = Gem::Request.get_cert_files @headers = headers end ## # Given a name and requirement, downloads this gem into cache and returns the # filename. Returns nil if the gem cannot be located. #-- # Should probably be integrated with #download below, but that will be a # larger, more encompassing effort. -erikh def download_to_cache(dependency) found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency return if found.empty? spec, source = found.max_by {|(s,_)| s.version } download spec, source.uri end ## # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is # already there. If the source_uri is local the gem cache dir copy is # always replaced. def download(spec, source_uri, install_dir = Gem.dir) install_cache_dir = File.join install_dir, "cache" cache_dir = if Dir.pwd == install_dir # see fetch_command install_dir elsif File.writable?(install_cache_dir) || (File.writable?(install_dir) && !File.exist?(install_cache_dir)) install_cache_dir else File.join Gem.user_dir, "cache" end gem_file_name = File.basename spec.cache_file local_gem_path = File.join cache_dir, gem_file_name require "fileutils" begin FileUtils.mkdir_p cache_dir rescue StandardError nil end unless File.exist? cache_dir source_uri = Gem::Uri.new(source_uri) scheme = source_uri.scheme # Gem::URI.parse gets confused by MS Windows paths with forward slashes. scheme = nil if /^[a-z]$/i.match?(scheme) # REFACTOR: split this up and dispatch on scheme (eg download_http) # REFACTOR: be sure to clean up fake fetcher when you do this... cleaner case scheme when "http", "https", "s3" then unless File.exist? local_gem_path begin verbose "Downloading gem #{gem_file_name}" remote_gem_path = source_uri + "gems/#{gem_file_name}" cache_update_path remote_gem_path, local_gem_path rescue FetchError raise if spec.original_platform == spec.platform alternate_name = "#{spec.original_name}.gem" verbose "Failed, downloading gem #{alternate_name}" remote_gem_path = source_uri + "gems/#{alternate_name}" cache_update_path remote_gem_path, local_gem_path end end when "file" then begin path = source_uri.path path = File.dirname(path) if File.extname(path) == ".gem" remote_gem_path = Gem::Util.correct_for_windows_path(File.join(path, "gems", gem_file_name)) FileUtils.cp(remote_gem_path, local_gem_path) rescue Errno::EACCES local_gem_path = source_uri.to_s end verbose "Using local gem #{local_gem_path}" when nil then # TODO: test for local overriding cache source_path = if Gem.win_platform? && source_uri.scheme && !source_uri.path.include?(":") "#{source_uri.scheme}:#{source_uri.path}" else source_uri.path end source_path = Gem::UriFormatter.new(source_path).unescape begin FileUtils.cp source_path, local_gem_path unless File.identical?(source_path, local_gem_path) rescue Errno::EACCES local_gem_path = source_uri.to_s end verbose "Using local gem #{local_gem_path}" else raise ArgumentError, "unsupported URI scheme #{source_uri.scheme}" end local_gem_path end ## # File Fetcher. Dispatched by +fetch_path+. Use it instead. def fetch_file(uri, *_) Gem.read_binary Gem::Util.correct_for_windows_path uri.path end ## # HTTP Fetcher. Dispatched by +fetch_path+. Use it instead. def fetch_http(uri, last_modified = nil, head = false, depth = 0) fetch_type = head ? Gem::Net::HTTP::Head : Gem::Net::HTTP::Get response = request uri, fetch_type, last_modified do |req| headers.each {|k,v| req.add_field(k,v) } end case response when Gem::Net::HTTPOK, Gem::Net::HTTPNotModified then response.uri = uri head ? response : response.body when Gem::Net::HTTPMovedPermanently, Gem::Net::HTTPFound, Gem::Net::HTTPSeeOther, Gem::Net::HTTPTemporaryRedirect then raise FetchError.new("too many redirects", uri) if depth > 10 unless location = response["Location"] raise FetchError.new("redirecting but no redirect location was given", uri) end location = Gem::Uri.new location if https?(uri) && !https?(location) raise FetchError.new("redirecting to non-https resource: #{location}", uri) end fetch_http(location, last_modified, head, depth + 1) else raise FetchError.new("bad response #{response.message} #{response.code}", uri) end end alias_method :fetch_https, :fetch_http ## # Downloads +uri+ and returns it as a String. def fetch_path(uri, mtime = nil, head = false) uri = Gem::Uri.new uri unless uri.scheme raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" end data = send "fetch_#{uri.scheme}", uri, mtime, head if data && !head && uri.to_s.end_with?(".gz") begin data = Gem::Util.gunzip data rescue Zlib::GzipFile::Error raise FetchError.new("server did not return a valid file", uri) end end data rescue Gem::Timeout::Error, IOError, SocketError, SystemCallError, *(OpenSSL::SSL::SSLError if Gem::HAVE_OPENSSL) => e raise FetchError.new("#{e.class}: #{e}", uri) end def fetch_s3(uri, mtime = nil, head = false) begin public_uri = s3_uri_signer(uri).sign rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e raise FetchError.new(e.message, "s3://#{uri.host}") end fetch_https public_uri, mtime, head end # we have our own signing code here to avoid a dependency on the aws-sdk gem def s3_uri_signer(uri) Gem::S3URISigner.new(uri) end ## # Downloads +uri+ to +path+ if necessary. If no path is given, it just # passes the data. def cache_update_path(uri, path = nil, update = true) mtime = begin path && File.stat(path).mtime rescue StandardError nil end data = fetch_path(uri, mtime) if data.nil? # indicates the server returned 304 Not Modified return Gem.read_binary(path) end if update && path Gem.write_binary(path, data) end data end ## # Performs a Gem::Net::HTTP request of type +request_class+ on +uri+ returning # a Gem::Net::HTTP response object. request maintains a table of persistent # connections to reduce connect overhead. def request(uri, request_class, last_modified = nil) proxy = proxy_for @proxy, uri pool = pools_for(proxy).pool_for uri request = Gem::Request.new uri, request_class, last_modified, pool request.fetch do |req| yield req if block_given? end end def https?(uri) uri.scheme.casecmp("https").zero? end def close_all @pools.each_value(&:close_all) end private def proxy_for(proxy, uri) Gem::Request.proxy_uri(proxy || Gem::Request.get_proxy_from_env(uri.scheme)) end def pools_for(proxy) @pool_lock.synchronize do @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files end end end PK!j;rubygems/requirement.rbnu[# frozen_string_literal: true require_relative "version" ## # A Requirement is a set of one or more version restrictions. It supports a # few (=, !=, >, <, >=, <=, ~>) different restriction operators. # # See Gem::Version for a description on how versions and requirements work # together in RubyGems. class Gem::Requirement OPS = { # :nodoc: "=" => lambda {|v, r| v == r }, "!=" => lambda {|v, r| v != r }, ">" => lambda {|v, r| v > r }, "<" => lambda {|v, r| v < r }, ">=" => lambda {|v, r| v >= r }, "<=" => lambda {|v, r| v <= r }, "~>" => lambda {|v, r| v >= r && v.release < r.bump }, }.freeze SOURCE_SET_REQUIREMENT = Struct.new(:for_lockfile).new "!" # :nodoc: quoted = OPS.keys.map {|k| Regexp.quote k }.join "|" PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*".freeze # :nodoc: ## # A regular expression that matches a requirement PATTERN = /\A#{PATTERN_RAW}\z/ ## # The default requirement matches any non-prerelease version DefaultRequirement = [">=", Gem::Version.new(0)].freeze ## # The default requirement matches any version DefaultPrereleaseRequirement = [">=", Gem::Version.new("0.a")].freeze ## # Raised when a bad requirement is encountered class BadRequirementError < ArgumentError; end ## # Factory method to create a Gem::Requirement object. Input may be # a Version, a String, or nil. Intended to simplify client code. # # If the input is "weird", the default version requirement is # returned. def self.create(*inputs) return new inputs if inputs.length > 1 input = inputs.shift case input when Gem::Requirement then input when Gem::Version, Array then new input when "!" then source_set else if input.respond_to? :to_str new [input.to_str] else default end end end def self.default new ">= 0" end def self.default_prerelease new ">= 0.a" end ### # A source set requirement, used for Gemfiles and lockfiles def self.source_set # :nodoc: SOURCE_SET_REQUIREMENT end ## # Parse +obj+, returning an [op, version] pair. +obj+ can # be a String or a Gem::Version. # # If +obj+ is a String, it can be either a full requirement # specification, like ">= 1.2", or a simple version number, # like "1.2". # # parse("> 1.0") # => [">", Gem::Version.new("1.0")] # parse("1.0") # => ["=", Gem::Version.new("1.0")] # parse(Gem::Version.new("1.0")) # => ["=, Gem::Version.new("1.0")] def self.parse(obj) return ["=", obj] if Gem::Version === obj unless PATTERN =~ obj.to_s raise BadRequirementError, "Illformed requirement [#{obj.inspect}]" end if $1 == ">=" && $2 == "0" DefaultRequirement elsif $1 == ">=" && $2 == "0.a" DefaultPrereleaseRequirement else [-($1 || "="), Gem::Version.new($2)] end end ## # An array of requirement pairs. The first element of the pair is # the op, and the second is the Gem::Version. attr_reader :requirements # :nodoc: ## # Constructs a requirement from +requirements+. Requirements can be # Strings, Gem::Versions, or Arrays of those. +nil+ and duplicate # requirements are ignored. An empty set of +requirements+ is the # same as ">= 0". def initialize(*requirements) requirements = requirements.flatten requirements.compact! requirements.uniq! if requirements.empty? @requirements = [DefaultRequirement] else @requirements = requirements.map! {|r| self.class.parse r } end end ## # Concatenates the +new+ requirements onto this requirement. def concat(new) new = new.flatten new.compact! new.uniq! new = new.map {|r| self.class.parse r } @requirements.concat new end ## # Formats this requirement for use in a Gem::RequestSet::Lockfile. def for_lockfile # :nodoc: return if @requirements == [DefaultRequirement] list = requirements.sort_by do |_, version| version end.map do |op, version| "#{op} #{version}" end.uniq " (#{list.join ", "})" end ## # true if this gem has no requirements. def none? if @requirements.size == 1 @requirements[0] == DefaultRequirement else false end end ## # true if the requirement is for only an exact version def exact? return false unless @requirements.size == 1 @requirements[0][0] == "=" end def as_list # :nodoc: requirements.map {|op, version| "#{op} #{version}" } end def hash # :nodoc: requirements.map {|r| r.first == "~>" ? [r[0], r[1].to_s] : r }.sort.hash end def marshal_dump # :nodoc: [@requirements] end def marshal_load(array) # :nodoc: @requirements = array[0] raise TypeError, "wrong @requirements" unless Array === @requirements end def yaml_initialize(tag, vals) # :nodoc: vals.each do |ivar, val| instance_variable_set "@#{ivar}", val end end def init_with(coder) # :nodoc: yaml_initialize coder.tag, coder.map end def to_yaml_properties # :nodoc: ["@requirements"] end def encode_with(coder) # :nodoc: coder.add "requirements", @requirements end ## # A requirement is a prerelease if any of the versions inside of it # are prereleases def prerelease? requirements.any? {|r| r.last.prerelease? } end def pretty_print(q) # :nodoc: q.group 1, "Gem::Requirement.new(", ")" do q.pp as_list end end ## # True if +version+ satisfies this Requirement. def satisfied_by?(version) raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless Gem::Version === version requirements.all? {|op, rv| OPS[op].call version, rv } end alias_method :===, :satisfied_by? alias_method :=~, :satisfied_by? ## # True if the requirement will not always match the latest version. def specific? return true if @requirements.length > 1 # GIGO, > 1, > 2 is silly !%w[> >=].include? @requirements.first.first # grab the operator end def to_s # :nodoc: as_list.join ", " end def ==(other) # :nodoc: return unless Gem::Requirement === other # An == check is always necessary return false unless _sorted_requirements == other._sorted_requirements # An == check is sufficient unless any requirements use ~> return true unless _tilde_requirements.any? # If any requirements use ~> we use the stricter `#eql?` that also checks # that version precision is the same _tilde_requirements.eql?(other._tilde_requirements) end protected def _sorted_requirements @_sorted_requirements ||= requirements.sort_by(&:to_s) end def _tilde_requirements @_tilde_requirements ||= _sorted_requirements.select {|r| r.first == "~>" } end def initialize_copy(other) # :nodoc: @requirements = other.requirements.dup super end end class Gem::Version # This is needed for compatibility with older yaml # gemspecs. Requirement = Gem::Requirement # :nodoc: end PK!jrubygems/rdoc.rbnu[# frozen_string_literal: true require_relative "../rubygems" begin require "rdoc/rubygems_hook" module Gem RDoc = ::RDoc::RubygemsHook end Gem.done_installing(&Gem::RDoc.method(:generation_hook)) rescue LoadError end PK!C  rubygems/available_set.rbnu[# frozen_string_literal: true class Gem::AvailableSet include Enumerable Tuple = Struct.new(:spec, :source) attr_accessor :remote # :nodoc: def initialize @set = [] @sorted = nil @remote = true end attr_reader :set def add(spec, source) @set << Tuple.new(spec, source) @sorted = nil self end def <<(o) case o when Gem::AvailableSet s = o.set when Array s = o.map do |sp,so| if !sp.is_a?(Gem::Specification) || !so.is_a?(Gem::Source) raise TypeError, "Array must be in [[spec, source], ...] form" end Tuple.new(sp,so) end else raise TypeError, "must be a Gem::AvailableSet" end @set += s @sorted = nil self end ## # Yields each Tuple in this AvailableSet def each return enum_for __method__ unless block_given? @set.each do |tuple| yield tuple end end ## # Yields the Gem::Specification for each Tuple in this AvailableSet def each_spec return enum_for __method__ unless block_given? each do |tuple| yield tuple.spec end end def empty? @set.empty? end def all_specs @set.map(&:spec) end def match_platform! @set.reject! {|t| !Gem::Platform.match_spec?(t.spec) } @sorted = nil self end def sorted @sorted ||= @set.sort do |a,b| i = b.spec <=> a.spec i != 0 ? i : (a.source <=> b.source) end end def size @set.size end def source_for(spec) f = @set.find {|t| t.spec == spec } f.source end ## # Converts this AvailableSet into a RequestSet that can be used to install # gems. # # If +development+ is :none then no development dependencies are installed. # Other options are :shallow for only direct development dependencies of the # gems in this set or :all for all development dependencies. def to_request_set(development = :none) request_set = Gem::RequestSet.new request_set.development = development == :all each_spec do |spec| request_set.always_install << spec request_set.gem spec.name, spec.version request_set.import spec.development_dependencies if development == :shallow end request_set end ## # # Used by the Resolver, the protocol to use a AvailableSet as a # search Set. def find_all(req) dep = req.dependency match = @set.find_all do |t| dep.match? t.spec end match.map do |t| Gem::Resolver::LocalSpecification.new(self, t.spec, t.source) end end def prefetch(reqs) end def pick_best! return self if empty? @set = [sorted.first] @sorted = nil self end def remove_installed!(dep) @set.reject! do |_t| # already locally installed Gem::Specification.any? do |installed_spec| dep.name == installed_spec.name && dep.requirement.satisfied_by?(installed_spec.version) end end @sorted = nil self end def inject_into_list(dep_list) @set.each {|t| dep_list.add t.spec } end end PK!GyIIrubygems/vendored_tsort.rbnu[# frozen_string_literal: true require_relative "vendor/tsort/lib/tsort" PK!r rubygems/yaml_serializer.rbnu[# frozen_string_literal: true module Gem # A stub yaml serializer that can handle only hashes and strings (as of now). module YAMLSerializer module_function def dump(hash) yaml = String.new("---") yaml << dump_hash(hash) end def dump_hash(hash) yaml = String.new("\n") hash.each do |k, v| yaml << k << ":" if v.is_a?(Hash) yaml << dump_hash(v).gsub(/^(?!$)/, " ") # indent all non-empty lines elsif v.is_a?(Array) # Expected to be array of strings if v.empty? yaml << " []\n" else yaml << "\n- " << v.map {|s| s.to_s.gsub(/\s+/, " ").inspect }.join("\n- ") << "\n" end else yaml << " " << v.to_s.gsub(/\s+/, " ").inspect << "\n" end end yaml end ARRAY_REGEX = / ^ (?:[ ]*-[ ]) # '- ' before array items (['"]?) # optional opening quote (.*) # value \1 # matching closing quote $ /xo HASH_REGEX = / ^ ([ ]*) # indentations ([^#]+) # key excludes comment char '#' (?::(?=(?:\s|$))) # : (without the lookahead the #key includes this when : is present in value) [ ]? (['"]?) # optional opening quote (.*) # value \3 # matching closing quote $ /xo def load(str) res = {} stack = [res] last_hash = nil last_empty_key = nil str.split(/\r?\n/) do |line| if match = HASH_REGEX.match(line) indent, key, quote, val = match.captures val = strip_comment(val) depth = indent.size / 2 if quote.empty? && val.empty? new_hash = {} stack[depth][key] = new_hash stack[depth + 1] = new_hash last_empty_key = key last_hash = stack[depth] else val = [] if val == "[]" # empty array stack[depth][key] = val end elsif match = ARRAY_REGEX.match(line) _, val = match.captures val = strip_comment(val) last_hash[last_empty_key] = [] unless last_hash[last_empty_key].is_a?(Array) last_hash[last_empty_key].push(val) end end res end def strip_comment(val) if val.include?("#") && !val.start_with?("#") val.split("#", 2).first.strip else val end end class << self private :dump_hash end end end PK!бrubygems/safe_marshal.rbnu[# frozen_string_literal: true require "stringio" require_relative "safe_marshal/reader" require_relative "safe_marshal/visitors/to_ruby" module Gem ### # This module is used for safely loading Marshal specs from a gem. The # `safe_load` method defined on this module is specifically designed for # loading Gem specifications. module SafeMarshal PERMITTED_CLASSES = %w[ Date Time Rational Gem::Dependency Gem::NameTuple Gem::Platform Gem::Requirement Gem::Specification Gem::Version Gem::Version::Requirement YAML::Syck::DefaultKey YAML::PrivateType ].freeze private_constant :PERMITTED_CLASSES PERMITTED_SYMBOLS = %w[ development runtime name number platform dependencies ].freeze private_constant :PERMITTED_SYMBOLS PERMITTED_IVARS = { "String" => %w[E encoding @taguri @debug_created_info], "Time" => %w[ offset zone nano_num nano_den submicro @_zone @marshal_with_utc_coercion ], "Gem::Dependency" => %w[ @name @requirement @prerelease @version_requirement @version_requirements @type @force_ruby_platform ], "Gem::NameTuple" => %w[@name @version @platform], "Gem::Platform" => %w[@os @cpu @version], "Psych::PrivateType" => %w[@value @type_id], }.freeze private_constant :PERMITTED_IVARS def self.safe_load(input) load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, permitted_ivars: PERMITTED_IVARS) end def self.load(input, permitted_classes: [::Symbol], permitted_symbols: [], permitted_ivars: {}) root = Reader.new(StringIO.new(input, "r").binmode).read! Visitors::ToRuby.new( permitted_classes: permitted_classes, permitted_symbols: permitted_symbols, permitted_ivars: permitted_ivars, ).visit(root) end end end PK!{'rubygems/installer_uninstaller_utils.rbnu[# frozen_string_literal: true ## # Helper methods for both Gem::Installer and Gem::Uninstaller module Gem::InstallerUninstallerUtils def regenerate_plugins_for(spec, plugins_dir) plugins = spec.plugins return if plugins.empty? require "pathname" spec.plugins.each do |plugin| plugin_script_path = File.join plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}" File.open plugin_script_path, "wb" do |file| file.puts "require_relative '#{Pathname.new(plugin).relative_path_from(Pathname.new(plugins_dir))}'" end verbose plugin_script_path end end def remove_plugins_for(spec, plugins_dir) FileUtils.rm_f Gem::Util.glob_files_in_dir("#{spec.name}#{Gem.plugin_suffix_pattern}", plugins_dir) end end PK!jJQQrubygems/exceptions.rbnu[# frozen_string_literal: true require_relative "deprecate" require_relative "unknown_command_spell_checker" ## # Base exception class for RubyGems. All exception raised by RubyGems are a # subclass of this one. class Gem::Exception < RuntimeError; end class Gem::CommandLineError < Gem::Exception; end class Gem::UnknownCommandError < Gem::Exception attr_reader :unknown_command def initialize(unknown_command) self.class.attach_correctable @unknown_command = unknown_command super("Unknown command #{unknown_command}") end def self.attach_correctable return if defined?(@attached) if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable) if DidYouMean.respond_to?(:correct_error) DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker) else DidYouMean::SPELL_CHECKERS["Gem::UnknownCommandError"] = Gem::UnknownCommandSpellChecker prepend DidYouMean::Correctable end end @attached = true end end class Gem::DependencyError < Gem::Exception; end class Gem::DependencyRemovalException < Gem::Exception; end ## # Raised by Gem::Resolver when a Gem::Dependency::Conflict reaches the # toplevel. Indicates which dependencies were incompatible through #conflict # and #conflicting_dependencies class Gem::DependencyResolutionError < Gem::DependencyError attr_reader :conflict def initialize(conflict) @conflict = conflict a, b = conflicting_dependencies super "conflicting dependencies #{a} and #{b}\n#{@conflict.explanation}" end def conflicting_dependencies @conflict.conflicting_dependencies end end ## # Raised when attempting to uninstall a gem that isn't in GEM_HOME. class Gem::GemNotInHomeException < Gem::Exception attr_accessor :spec end ### # Raised when removing a gem with the uninstall command fails class Gem::UninstallError < Gem::Exception attr_accessor :spec end class Gem::DocumentError < Gem::Exception; end ## # Potentially raised when a specification is validated. class Gem::EndOfYAMLException < Gem::Exception; end ## # Signals that a file permission error is preventing the user from # operating on the given directory. class Gem::FilePermissionError < Gem::Exception attr_reader :directory def initialize(directory) @directory = directory super "You don't have write permissions for the #{directory} directory." end end ## # Used to raise parsing and loading errors class Gem::FormatException < Gem::Exception attr_accessor :file_path end class Gem::GemNotFoundException < Gem::Exception; end class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException ## # Creates a new SpecificGemNotFoundException for a gem with the given +name+ # and +version+. Any +errors+ encountered when attempting to find the gem # are also stored. def initialize(name, version, errors=nil) super "Could not find a valid gem '#{name}' (#{version}) locally or in a repository" @name = name @version = version @errors = errors end ## # The name of the gem that could not be found. attr_reader :name ## # The version of the gem that could not be found. attr_reader :version ## # Errors encountered attempting to find the gem. attr_reader :errors end Gem.deprecate_constant :SpecificGemNotFoundException ## # Raised by Gem::Resolver when dependencies conflict and create the # inability to find a valid possible spec for a request. class Gem::ImpossibleDependenciesError < Gem::Exception attr_reader :conflicts attr_reader :request def initialize(request, conflicts) @request = request @conflicts = conflicts super build_message end def build_message # :nodoc: requester = @request.requester requester = requester ? requester.spec.full_name : "The user" dependency = @request.dependency message = "#{requester} requires #{dependency} but it conflicted:\n".dup @conflicts.each do |_, conflict| message << conflict.explanation end message end def dependency @request.dependency end end class Gem::InstallError < Gem::Exception; end class Gem::RuntimeRequirementNotMetError < Gem::InstallError attr_accessor :suggestion def message [suggestion, super].compact.join("\n\t") end end ## # Potentially raised when a specification is validated. class Gem::InvalidSpecificationException < Gem::Exception; end class Gem::OperationNotSupportedError < Gem::Exception; end ## # Signals that a remote operation cannot be conducted, probably due to not # being connected (or just not finding host). #-- # TODO: create a method that tests connection to the preferred gems server. # All code dealing with remote operations will want this. Failure in that # method should raise this error. class Gem::RemoteError < Gem::Exception; end class Gem::RemoteInstallationCancelled < Gem::Exception; end class Gem::RemoteInstallationSkipped < Gem::Exception; end ## # Represents an error communicating via HTTP. class Gem::RemoteSourceException < Gem::Exception; end ## # Raised when a gem dependencies file specifies a ruby version that does not # match the current version. class Gem::RubyVersionMismatch < Gem::Exception; end ## # Raised by Gem::Validator when something is not right in a gem. class Gem::VerificationError < Gem::Exception; end ## # Raised by Gem::WebauthnListener when an error occurs during security # device verification. class Gem::WebauthnVerificationError < Gem::Exception def initialize(message) super "Security device verification failed: #{message}" end end ## # Raised to indicate that a system exit should occur with the specified # exit_code class Gem::SystemExitException < SystemExit ## # The exit code for the process alias_method :exit_code, :status ## # Creates a new SystemExitException with the given +exit_code+ def initialize(exit_code) super exit_code, "Exiting RubyGems with exit_code #{exit_code}" end end ## # Raised by Resolver when a dependency requests a gem for which # there is no spec. class Gem::UnsatisfiableDependencyError < Gem::DependencyError ## # The unsatisfiable dependency. This is a # Gem::Resolver::DependencyRequest, not a Gem::Dependency attr_reader :dependency ## # Errors encountered which may have contributed to this exception attr_accessor :errors ## # Creates a new UnsatisfiableDependencyError for the unsatisfiable # Gem::Resolver::DependencyRequest +dep+ def initialize(dep, platform_mismatch=nil) if platform_mismatch && !platform_mismatch.empty? plats = platform_mismatch.map {|x| x.platform.to_s }.sort.uniq super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(", ")}" else if dep.explicit? super "Unable to resolve dependency: user requested '#{dep}'" else super "Unable to resolve dependency: '#{dep.request_context}' requires '#{dep}'" end end @dependency = dep @errors = [] end ## # The name of the unresolved dependency def name @dependency.name end ## # The Requirement of the unresolved dependency (not Version). def version @dependency.requirement end end ## # Backwards compatible typo'd exception class for early RubyGems 2.0.x Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError # :nodoc: Gem.deprecate_constant :UnsatisfiableDepedencyError PK!Znnrubygems/installer.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "installer_uninstaller_utils" require_relative "exceptions" require_relative "deprecate" require_relative "package" require_relative "ext" require_relative "user_interaction" ## # The installer installs the files contained in the .gem into the Gem.home. # # Gem::Installer does the work of putting files in all the right places on the # filesystem including unpacking the gem into its gem dir, installing the # gemspec in the specifications dir, storing the cached gem in the cache dir, # and installing either wrappers or symlinks for executables. # # The installer invokes pre and post install hooks. Hooks can be added either # through a rubygems_plugin.rb file in an installed gem or via a # rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb # file. See Gem.pre_install and Gem.post_install for details. class Gem::Installer extend Gem::Deprecate ## # Paths where env(1) might live. Some systems are broken and have it in # /bin ENV_PATHS = %w[/usr/bin/env /bin/env].freeze ## # Deprecated in favor of Gem::Ext::BuildError ExtensionBuildError = Gem::Ext::BuildError # :nodoc: include Gem::UserInteraction include Gem::InstallerUninstallerUtils ## # The directory a gem's executables will be installed into attr_reader :bin_dir attr_reader :build_root # :nodoc: ## # The gem repository the gem will be installed into attr_reader :gem_home ## # The options passed when the Gem::Installer was instantiated. attr_reader :options ## # The gem package instance. attr_reader :package class << self # # Changes in rubygems to lazily loading `rubygems/command` (in order to # lazily load `optparse` as a side effect) affect bundler's custom installer # which uses `Gem::Command` without requiring it (up until bundler 2.2.29). # This hook is to compensate for that missing require. # # TODO: Remove when rubygems no longer supports running on bundler older # than 2.2.29. def inherited(klass) if klass.name == "Bundler::RubyGemsGemInstaller" require "rubygems/command" end super(klass) end ## # Overrides the executable format. # # This is a sprintf format with a "%s" which will be replaced with the # executable name. It is based off the ruby executable name's difference # from "ruby". attr_writer :exec_format # Defaults to use Ruby's program prefix and suffix. def exec_format @exec_format ||= Gem.default_exec_format end end ## # Construct an installer object for the gem file located at +path+ def self.at(path, options = {}) security_policy = options[:security_policy] package = Gem::Package.new path, security_policy new package, options end class FakePackage attr_accessor :spec attr_accessor :dir_mode attr_accessor :prog_mode attr_accessor :data_mode def initialize(spec) @spec = spec end def extract_files(destination_dir, pattern = "*") FileUtils.mkdir_p destination_dir spec.files.each do |file| file = File.join destination_dir, file next if File.exist? file FileUtils.mkdir_p File.dirname(file) File.open file, "w" do |fp| fp.puts "# #{file}" end end end def copy_to(path) end end ## # Construct an installer object for an ephemeral gem (one where we don't # actually have a .gem file, just a spec) def self.for_spec(spec, options = {}) # FIXME: we should have a real Package class for this new FakePackage.new(spec), options end ## # Constructs an Installer instance that will install the gem at +package+ which # can either be a path or an instance of Gem::Package. +options+ is a Hash # with the following keys: # # :bin_dir:: Where to put a bin wrapper if needed. # :development:: Whether or not development dependencies should be installed. # :env_shebang:: Use /usr/bin/env in bin wrappers. # :force:: Overrides all version checks and security policy checks, except # for a signed-gems-only policy. # :format_executable:: Format the executable the same as the Ruby executable. # If your Ruby is ruby18, foo_exec will be installed as # foo_exec18. # :ignore_dependencies:: Don't raise if a dependency is missing. # :install_dir:: The directory to install the gem into. # :security_policy:: Use the specified security policy. See Gem::Security # :user_install:: Indicate that the gem should be unpacked into the users # personal gem directory. # :only_install_dir:: Only validate dependencies against what is in the # install_dir # :wrappers:: Install wrappers if true, symlinks if false. # :build_args:: An Array of arguments to pass to the extension builder # process. If not set, then Gem::Command.build_args is used # :post_install_message:: Print gem post install message if true def initialize(package, options={}) require "fileutils" @options = options @package = package process_options @package.dir_mode = options[:dir_mode] @package.prog_mode = options[:prog_mode] @package.data_mode = options[:data_mode] end ## # Checks if +filename+ exists in +@bin_dir+. # # If +@force+ is set +filename+ is overwritten. # # If +filename+ exists and it is a RubyGems wrapper for a different gem, then # the user is consulted. # # If +filename+ exists and +@bin_dir+ is Gem.default_bindir (/usr/local) the # user is consulted. # # Otherwise +filename+ is overwritten. def check_executable_overwrite(filename) # :nodoc: return if @force generated_bin = File.join @bin_dir, formatted_program_filename(filename) return unless File.exist? generated_bin ruby_executable = false existing = nil File.open generated_bin, "rb" do |io| line = io.gets shebang = /^#!.*ruby/o # TruffleRuby uses a bash prelude in default launchers if load_relative_enabled? || RUBY_ENGINE == "truffleruby" until line.nil? || shebang.match?(line) do line = io.gets end end next unless line&.match?(shebang) io.gets # blankline # TODO: detect a specially formatted comment instead of trying # to find a string inside Ruby code. next unless io.gets&.include?("This file was generated by RubyGems") ruby_executable = true existing = io.read.slice(/ ^\s*( gem \s | load \s Gem\.bin_path\( | load \s Gem\.activate_bin_path\( ) (['"])(.*?)(\2), /x, 3) end return if spec.name == existing # somebody has written to RubyGems' directory, overwrite, too bad return if Gem.default_bindir != @bin_dir && !ruby_executable question = "#{spec.name}'s executable \"#{filename}\" conflicts with ".dup if ruby_executable question << (existing || "an unknown executable") return if ask_yes_no "#{question}\nOverwrite the executable?", false conflict = "installed executable from #{existing}" else question << generated_bin return if ask_yes_no "#{question}\nOverwrite the executable?", false conflict = generated_bin end raise Gem::InstallError, "\"#{filename}\" from #{spec.name} conflicts with #{conflict}" end ## # Lazy accessor for the spec's gem directory. def gem_dir @gem_dir ||= File.join(gem_home, "gems", spec.full_name) end ## # Lazy accessor for the installer's spec. def spec @package.spec end ## # Installs the gem and returns a loaded Gem::Specification for the installed # gem. # # The gem will be installed with the following structure: # # @gem_home/ # cache/.gem #=> a cached copy of the installed gem # gems//... #=> extracted files # specifications/.gemspec #=> the Gem::Specification def install pre_install_checks run_pre_install_hooks # Set loaded_from to ensure extension_dir is correct if @options[:install_as_default] spec.loaded_from = default_spec_file else spec.loaded_from = spec_file end # Completely remove any previous gem files FileUtils.rm_rf gem_dir FileUtils.rm_rf spec.extension_dir dir_mode = options[:dir_mode] FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 if @options[:install_as_default] extract_bin write_default_spec else extract_files build_extensions write_build_info_file run_post_build_hooks end generate_bin generate_plugins unless @options[:install_as_default] write_spec write_cache_file end File.chmod(dir_mode, gem_dir) if dir_mode say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? Gem::Specification.add_spec(spec) unless @install_dir load_plugin run_post_install_hooks spec rescue Errno::EACCES => e # Permission denied - /path/to/foo raise Gem::FilePermissionError, e.message.split(" - ").last end def run_pre_install_hooks # :nodoc: Gem.pre_install_hooks.each do |hook| next unless hook.call(self) == false location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/ message = "pre-install hook#{location} failed for #{spec.full_name}" raise Gem::InstallError, message end end def run_post_build_hooks # :nodoc: Gem.post_build_hooks.each do |hook| next unless hook.call(self) == false FileUtils.rm_rf gem_dir location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/ message = "post-build hook#{location} failed for #{spec.full_name}" raise Gem::InstallError, message end end def run_post_install_hooks # :nodoc: Gem.post_install_hooks.each do |hook| hook.call self end end ## # # Return an Array of Specifications contained within the gem_home # we'll be installing into. def installed_specs @installed_specs ||= begin specs = [] Gem::Util.glob_files_in_dir("*.gemspec", File.join(gem_home, "specifications")).each do |path| spec = Gem::Specification.load path specs << spec if spec end specs end end ## # Ensure that the dependency is satisfied by the current installation of # gem. If it is not an exception is raised. # # spec :: Gem::Specification # dependency :: Gem::Dependency def ensure_dependency(spec, dependency) unless installation_satisfies_dependency? dependency raise Gem::InstallError, "#{spec.name} requires #{dependency}" end true end ## # True if the gems in the system satisfy +dependency+. def installation_satisfies_dependency?(dependency) return true if @options[:development] && dependency.type == :development return true if installed_specs.detect {|s| dependency.matches_spec? s } return false if @only_install_dir !dependency.matching_specs.empty? end ## # Unpacks the gem into the given directory. def unpack(directory) @gem_dir = directory extract_files end rubygems_deprecate :unpack ## # The location of the spec file that is installed. # def spec_file File.join gem_home, "specifications", "#{spec.full_name}.gemspec" end ## # The location of the default spec file for default gems. # def default_spec_file File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec" end ## # Writes the .gemspec specification (in Ruby) to the gem home's # specifications directory. def write_spec spec.installed_by_version = Gem.rubygems_version Gem.write_binary(spec_file, spec.to_ruby_for_cache) end ## # Writes the full .gemspec specification (in Ruby) to the gem home's # specifications/default directory. # # In contrast to #write_spec, this keeps file lists, so the `gem contents` # command works. def write_default_spec Gem.write_binary(default_spec_file, spec.to_ruby) end ## # Creates windows .bat files for easy running of commands def generate_windows_script(filename, bindir) if Gem.win_platform? script_name = formatted_program_filename(filename) + ".bat" script_path = File.join bindir, File.basename(script_name) File.open script_path, "w" do |file| file.puts windows_stub_script(bindir, filename) end verbose script_path end end def generate_bin # :nodoc: executables = spec.executables return if executables.nil? || executables.empty? if @gem_home == Gem.user_dir # If we get here, then one of the following likely happened: # - `--user-install` was specified # - `Gem::PathSupport#home` fell back to `Gem.user_dir` # - GEM_HOME was manually set to `Gem.user_dir` check_that_user_bin_dir_is_in_path(executables) end ensure_writable_dir @bin_dir executables.each do |filename| bin_path = File.join gem_dir, spec.bindir, filename next unless File.exist? bin_path mode = File.stat(bin_path).mode dir_mode = options[:prog_mode] || (mode | 0o111) unless dir_mode == mode File.chmod dir_mode, bin_path end check_executable_overwrite filename if @wrappers generate_bin_script filename, @bin_dir else generate_bin_symlink filename, @bin_dir end end end def generate_plugins # :nodoc: latest = Gem::Specification.latest_spec_for(spec.name) return if latest && latest.version > spec.version ensure_writable_dir @plugins_dir if spec.plugins.empty? remove_plugins_for(spec, @plugins_dir) else regenerate_plugins_for(spec, @plugins_dir) end rescue ArgumentError => e raise e, "#{latest.name} #{latest.version} #{spec.name} #{spec.version}: #{e.message}" end ## # Creates the scripts to run the applications in the gem. #-- # The Windows script is generated in addition to the regular one due to a # bug or misfeature in the Windows shell's pipe. See # https://blade.ruby-lang.org/ruby-talk/193379 def generate_bin_script(filename, bindir) bin_script_path = File.join bindir, formatted_program_filename(filename) Gem.open_file_with_lock(bin_script_path) do require "fileutils" FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers File.open(bin_script_path, "wb", 0o755) do |file| file.write app_script_text(filename) file.chmod(options[:prog_mode] || 0o755) end end verbose bin_script_path generate_windows_script filename, bindir end ## # Creates the symlinks to run the applications in the gem. Moves # the symlink if the gem being installed has a newer version. def generate_bin_symlink(filename, bindir) src = File.join gem_dir, spec.bindir, filename dst = File.join bindir, formatted_program_filename(filename) if File.exist? dst if File.symlink? dst link = File.readlink(dst).split File::SEPARATOR cur_version = Gem::Version.create(link[-3].sub(/^.*-/, "")) return if spec.version < cur_version end File.unlink dst end FileUtils.symlink src, dst, verbose: Gem.configuration.really_verbose rescue NotImplementedError, SystemCallError alert_warning "Unable to use symlinks, installing wrapper" generate_bin_script filename, bindir end ## # Generates a #! line for +bin_file_name+'s wrapper copying arguments if # necessary. # # If the :custom_shebang config is set, then it is used as a template # for how to create the shebang used for to run a gem's executables. # # The template supports 4 expansions: # # $env the path to the unix env utility # $ruby the path to the currently running ruby interpreter # $exec the path to the gem's executable # $name the name of the gem the executable is for # def shebang(bin_file_name) path = File.join gem_dir, spec.bindir, bin_file_name first_line = File.open(path, "rb", &:gets) || "" if first_line.start_with?("#!") # Preserve extra words on shebang line, like "-w". Thanks RPA. shebang = first_line.sub(/\A\#!.*?ruby\S*((\s+\S+)+)/, "#!#{Gem.ruby}") opts = $1 shebang.strip! # Avoid nasty ^M issues. end if which = Gem.configuration[:custom_shebang] # replace bin_file_name with "ruby" to avoid endless loops which = which.gsub(/ #{bin_file_name}$/," #{ruby_install_name}") which = which.gsub(/\$(\w+)/) do case $1 when "env" @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } when "ruby" "#{Gem.ruby}#{opts}" when "exec" bin_file_name when "name" spec.name end end "#!#{which}" elsif @env_shebang # Create a plain shebang line. @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } "#!#{@env_path} #{ruby_install_name}" else "#{bash_prolog_script}#!#{Gem.ruby}#{opts}" end end ## # Ensures the Gem::Specification written out for this gem is loadable upon # installation. def ensure_loadable_spec ruby = spec.to_ruby_for_cache begin eval ruby rescue StandardError, SyntaxError => e raise Gem::InstallError, "The specification for #{spec.full_name} is corrupt (#{e.class})" end end def ensure_dependencies_met # :nodoc: deps = spec.runtime_dependencies deps |= spec.development_dependencies if @development deps.each do |dep_gem| ensure_dependency spec, dep_gem end end def process_options # :nodoc: @options = { bin_dir: nil, env_shebang: false, force: false, only_install_dir: false, post_install_message: true, }.merge options @env_shebang = options[:env_shebang] @force = options[:force] @install_dir = options[:install_dir] @user_install = options[:user_install] @ignore_dependencies = options[:ignore_dependencies] @format_executable = options[:format_executable] @wrappers = options[:wrappers] @only_install_dir = options[:only_install_dir] @bin_dir = options[:bin_dir] @development = options[:development] @build_root = options[:build_root] @build_args = options[:build_args] @gem_home = @install_dir || user_install_dir || Gem.dir # If the user has asked for the gem to be installed in a directory that is # the system gem directory, then use the system bin directory, else create # (or use) a new bin dir under the gem_home. @bin_dir ||= Gem.bindir(@gem_home) @plugins_dir = Gem.plugindir(@gem_home) unless @build_root.nil? @bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, "")) @gem_home = File.join(@build_root, @gem_home.gsub(/^[a-zA-Z]:/, "")) @plugins_dir = File.join(@build_root, @plugins_dir.gsub(/^[a-zA-Z]:/, "")) alert_warning "You build with buildroot.\n Build root: #{@build_root}\n Bin dir: #{@bin_dir}\n Gem home: #{@gem_home}\n Plugins dir: #{@plugins_dir}" end end def check_that_user_bin_dir_is_in_path(executables) # :nodoc: user_bin_dir = @bin_dir || Gem.bindir(gem_home) user_bin_dir = user_bin_dir.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR path = ENV["PATH"] path = path.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR if Gem.win_platform? path = path.downcase user_bin_dir = user_bin_dir.downcase end path = path.split(File::PATH_SEPARATOR) unless path.include? user_bin_dir unless !Gem.win_platform? && (path.include? user_bin_dir.sub(ENV["HOME"], "~")) alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables (#{executables.join(", ")}) will not run." end end end def verify_gem_home # :nodoc: FileUtils.mkdir_p gem_home, mode: options[:dir_mode] && 0o755 end def verify_spec unless Gem::Specification::VALID_NAME_PATTERN.match?(spec.name) raise Gem::InstallError, "#{spec} has an invalid name" end if spec.raw_require_paths.any? {|path| path =~ /\R/ } raise Gem::InstallError, "#{spec} has an invalid require_paths" end if spec.extensions.any? {|ext| ext =~ /\R/ } raise Gem::InstallError, "#{spec} has an invalid extensions" end if /\R/.match?(spec.platform.to_s) raise Gem::InstallError, "#{spec.platform} is an invalid platform" end unless /\A\d+\z/.match?(spec.specification_version.to_s) raise Gem::InstallError, "#{spec} has an invalid specification_version" end if spec.dependencies.any? {|dep| dep.type != :runtime && dep.type != :development } raise Gem::InstallError, "#{spec} has an invalid dependencies" end if spec.dependencies.any? {|dep| dep.name =~ /(?:\R|[<>])/ } raise Gem::InstallError, "#{spec} has an invalid dependencies" end end ## # Return the text for an application file. def app_script_text(bin_file_name) # NOTE: that the `load` lines cannot be indented, as old RG versions match # against the beginning of the line <<-TEXT #{shebang bin_file_name} # # This file was generated by RubyGems. # # The application '#{spec.name}' is installed as part of a gem, and # this file is here to facilitate running it. # require 'rubygems' #{gemdeps_load(spec.name)} version = "#{Gem::Requirement.default_prerelease}" str = ARGV.first if str str = str.b[/\\A_(.*)_\\z/, 1] if str and Gem::Version.correct?(str) #{explicit_version_requirement(spec.name)} ARGV.shift end end if Gem.respond_to?(:activate_bin_path) load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) else gem #{spec.name.dump}, version load Gem.bin_path(#{spec.name.dump}, #{bin_file_name.dump}, version) end TEXT end def gemdeps_load(name) return "" if name == "bundler" <<-TEXT Gem.use_gemdeps TEXT end def explicit_version_requirement(name) code = "version = str" return code unless name == "bundler" code += <<-TEXT ENV['BUNDLER_VERSION'] = str TEXT end ## # return the stub script text used to launch the true Ruby script def windows_stub_script(bindir, bin_file_name) rb_topdir = RbConfig::TOPDIR || File.dirname(rb_config["bindir"]) # get ruby executable file name from RbConfig ruby_exe = "#{rb_config["RUBY_INSTALL_NAME"]}#{rb_config["EXEEXT"]}" ruby_exe = "ruby.exe" if ruby_exe.empty? if File.exist?(File.join(bindir, ruby_exe)) # stub & ruby.exe within same folder. Portable <<-TEXT @ECHO OFF @"%~dp0#{ruby_exe}" "%~dpn0" %* TEXT elsif bindir.downcase.start_with? rb_topdir.downcase # stub within ruby folder, but not standard bin. Portable require "pathname" from = Pathname.new bindir to = Pathname.new "#{rb_topdir}/bin" rel = to.relative_path_from from <<-TEXT @ECHO OFF @"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* TEXT else # outside ruby folder, maybe -user-install or bundler. Portable, but ruby # is dependent on PATH <<-TEXT @ECHO OFF @#{ruby_exe} "%~dpn0" %* TEXT end end ## # Builds extensions. Valid types of extensions are extconf.rb files, # configure scripts and rakefiles or mkrf_conf files. def build_extensions builder = Gem::Ext::Builder.new spec, build_args builder.build_extensions end ## # Reads the file index and extracts each file into the gem directory. # # Ensures that files can't be installed outside the gem directory. def extract_files @package.extract_files gem_dir end ## # Extracts only the bin/ files from the gem into the gem directory. # This is used by default gems to allow a gem-aware stub to function # without the full gem installed. def extract_bin @package.extract_files gem_dir, "#{spec.bindir}/*" end ## # Prefix and suffix the program filename the same as ruby. def formatted_program_filename(filename) if @format_executable self.class.exec_format % File.basename(filename) else filename end end ## # # Return the target directory where the gem is to be installed. This # directory is not guaranteed to be populated. # def dir gem_dir.to_s end ## # Filename of the gem being installed. def gem @package.gem.path end ## # Performs various checks before installing the gem such as the install # repository is writable and its directories exist, required Ruby and # rubygems versions are met and that dependencies are installed. # # Version and dependency checks are skipped if this install is forced. # # The dependent check will be skipped if the install is ignoring dependencies. def pre_install_checks verify_gem_home # The name and require_paths must be verified first, since it could contain # ruby code that would be eval'ed in #ensure_loadable_spec verify_spec ensure_loadable_spec if options[:install_as_default] Gem.ensure_default_gem_subdirectories gem_home else Gem.ensure_gem_subdirectories gem_home end return true if @force ensure_dependencies_met unless @ignore_dependencies true end ## # Writes the file containing the arguments for building this gem's # extensions. def write_build_info_file return if build_args.empty? build_info_dir = File.join gem_home, "build_info" dir_mode = options[:dir_mode] FileUtils.mkdir_p build_info_dir, mode: dir_mode && 0o755 build_info_file = File.join build_info_dir, "#{spec.full_name}.info" File.open build_info_file, "w" do |io| build_args.each do |arg| io.puts arg end end File.chmod(dir_mode, build_info_dir) if dir_mode end ## # Writes the .gem file to the cache directory def write_cache_file cache_file = File.join gem_home, "cache", spec.file_name @package.copy_to cache_file end def ensure_writable_dir(dir) # :nodoc: begin Dir.mkdir dir, *[options[:dir_mode] && 0o755].compact rescue SystemCallError raise unless File.directory? dir end raise Gem::FilePermissionError.new(dir) unless File.writable? dir end private def user_install_dir # never install to user home in --build-root mode return unless @build_root.nil? # Please note that @user_install might have three states: # * `true`: `--user-install` # * `false`: `--no-user-install` and # * `nil`: option was not specified if @user_install || (@user_install.nil? && Gem.default_user_install) Gem.user_dir end end def build_args @build_args ||= begin require_relative "command" Gem::Command.build_args end end def rb_config RbConfig::CONFIG end def ruby_install_name rb_config["ruby_install_name"] end def load_relative_enabled? rb_config["LIBRUBY_RELATIVE"] == "yes" end def bash_prolog_script if load_relative_enabled? script = +<<~EOS bindir="${0%/*}" EOS script << %(exec "$bindir/#{ruby_install_name}" "-x" "$0" "$@"\n) <<~EOS #!/bin/sh # -*- ruby -*- _=_\\ =begin #{script.chomp} =end EOS else "" end end def load_plugin specs = Gem::Specification.find_all_by_name(spec.name) # If old version already exists, this plugin isn't loaded # immediately. It's for avoiding a case that multiple versions # are loaded at the same time. return unless specs.size == 1 plugin_files = spec.plugins.map do |plugin| File.join(@plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}") end Gem.load_plugin_files(plugin_files) end end PK!{]]#rubygems/install_default_message.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "user_interaction" ## # A post-install hook that displays "Successfully installed # some_gem-1.0 as a default gem" Gem.post_install do |installer| ui = Gem::DefaultUserInteraction.ui ui.say "Successfully installed #{installer.spec.full_name} as a default gem" end PK!v4kVVrubygems/security.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "exceptions" require_relative "openssl" ## # = Signing gems # # The Gem::Security implements cryptographic signatures for gems. The section # below is a step-by-step guide to using signed gems and generating your own. # # == Walkthrough # # === Building your certificate # # In order to start signing your gems, you'll need to build a private key and # a self-signed certificate. Here's how: # # # build a private key and certificate for yourself: # $ gem cert --build you@example.com # # This could take anywhere from a few seconds to a minute or two, depending on # the speed of your computer (public key algorithms aren't exactly the # speediest crypto algorithms in the world). When it's finished, you'll see # the files "gem-private_key.pem" and "gem-public_cert.pem" in the current # directory. # # First things first: Move both files to ~/.gem if you don't already have a # key and certificate in that directory. Ensure the file permissions make the # key unreadable by others (by default the file is saved securely). # # Keep your private key hidden; if it's compromised, someone can sign packages # as you (note: PKI has ways of mitigating the risk of stolen keys; more on # that later). # # === Signing Gems # # In RubyGems 2 and newer there is no extra work to sign a gem. RubyGems will # automatically find your key and certificate in your home directory and use # them to sign newly packaged gems. # # If your certificate is not self-signed (signed by a third party) RubyGems # will attempt to load the certificate chain from the trusted certificates. # Use gem cert --add signing_cert.pem to add your signers as # trusted certificates. See below for further information on certificate # chains. # # If you build your gem it will automatically be signed. If you peek inside # your gem file, you'll see a couple of new files have been added: # # $ tar tf your-gem-1.0.gem # metadata.gz # metadata.gz.sig # metadata signature # data.tar.gz # data.tar.gz.sig # data signature # checksums.yaml.gz # checksums.yaml.gz.sig # checksums signature # # === Manually signing gems # # If you wish to store your key in a separate secure location you'll need to # set your gems up for signing by hand. To do this, set the # signing_key and cert_chain in the gemspec before # packaging your gem: # # s.signing_key = '/secure/path/to/gem-private_key.pem' # s.cert_chain = %w[/secure/path/to/gem-public_cert.pem] # # When you package your gem with these options set RubyGems will automatically # load your key and certificate from the secure paths. # # === Signed gems and security policies # # Now let's verify the signature. Go ahead and install the gem, but add the # following options: -P HighSecurity, like this: # # # install the gem with using the security policy "HighSecurity" # $ sudo gem install your.gem -P HighSecurity # # The -P option sets your security policy -- we'll talk about # that in just a minute. Eh, what's this? # # $ gem install -P HighSecurity your-gem-1.0.gem # ERROR: While executing gem ... (Gem::Security::Exception) # root cert /CN=you/DC=example is not trusted # # The culprit here is the security policy. RubyGems has several different # security policies. Let's take a short break and go over the security # policies. Here's a list of the available security policies, and a brief # description of each one: # # * NoSecurity - Well, no security at all. Signed packages are treated like # unsigned packages. # * LowSecurity - Pretty much no security. If a package is signed then # RubyGems will make sure the signature matches the signing # certificate, and that the signing certificate hasn't expired, but # that's it. A malicious user could easily circumvent this kind of # security. # * MediumSecurity - Better than LowSecurity and NoSecurity, but still # fallible. Package contents are verified against the signing # certificate, and the signing certificate is checked for validity, # and checked against the rest of the certificate chain (if you don't # know what a certificate chain is, stay tuned, we'll get to that). # The biggest improvement over LowSecurity is that MediumSecurity # won't install packages that are signed by untrusted sources. # Unfortunately, MediumSecurity still isn't totally secure -- a # malicious user can still unpack the gem, strip the signatures, and # distribute the gem unsigned. # * HighSecurity - Here's the bugger that got us into this mess. # The HighSecurity policy is identical to the MediumSecurity policy, # except that it does not allow unsigned gems. A malicious user # doesn't have a whole lot of options here; they can't modify the # package contents without invalidating the signature, and they can't # modify or remove signature or the signing certificate chain, or # RubyGems will simply refuse to install the package. Oh well, maybe # they'll have better luck causing problems for CPAN users instead :). # # The reason RubyGems refused to install your shiny new signed gem was because # it was from an untrusted source. Well, your code is infallible (naturally), # so you need to add yourself as a trusted source: # # # add trusted certificate # gem cert --add ~/.gem/gem-public_cert.pem # # You've now added your public certificate as a trusted source. Now you can # install packages signed by your private key without any hassle. Let's try # the install command above again: # # # install the gem with using the HighSecurity policy (and this time # # without any shenanigans) # $ gem install -P HighSecurity your-gem-1.0.gem # Successfully installed your-gem-1.0 # 1 gem installed # # This time RubyGems will accept your signed package and begin installing. # # While you're waiting for RubyGems to work it's magic, have a look at some of # the other security commands by running gem help cert: # # Options: # -a, --add CERT Add a trusted certificate. # -l, --list [FILTER] List trusted certificates where the # subject contains FILTER # -r, --remove FILTER Remove trusted certificates where the # subject contains FILTER # -b, --build EMAIL_ADDR Build private key and self-signed # certificate for EMAIL_ADDR # -C, --certificate CERT Signing certificate for --sign # -K, --private-key KEY Key for --sign or --build # -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA. # -s, --sign CERT Signs CERT with the key from -K # and the certificate from -C # -d, --days NUMBER_OF_DAYS Days before the certificate expires # -R, --re-sign Re-signs the certificate from -C with the key from -K # # We've already covered the --build option, and the # --add, --list, and --remove commands # seem fairly straightforward; they allow you to add, list, and remove the # certificates in your trusted certificate list. But what's with this # --sign option? # # === Certificate chains # # To answer that question, let's take a look at "certificate chains", a # concept I mentioned earlier. There are a couple of problems with # self-signed certificates: first of all, self-signed certificates don't offer # a whole lot of security. Sure, the certificate says Yukihiro Matsumoto, but # how do I know it was actually generated and signed by matz himself unless he # gave me the certificate in person? # # The second problem is scalability. Sure, if there are 50 gem authors, then # I have 50 trusted certificates, no problem. What if there are 500 gem # authors? 1000? Having to constantly add new trusted certificates is a # pain, and it actually makes the trust system less secure by encouraging # RubyGems users to blindly trust new certificates. # # Here's where certificate chains come in. A certificate chain establishes an # arbitrarily long chain of trust between an issuing certificate and a child # certificate. So instead of trusting certificates on a per-developer basis, # we use the PKI concept of certificate chains to build a logical hierarchy of # trust. Here's a hypothetical example of a trust hierarchy based (roughly) # on geography: # # -------------------------- # | rubygems@rubygems.org | # -------------------------- # | # ----------------------------------- # | | # ---------------------------- ----------------------------- # | seattlerb@seattlerb.org | | dcrubyists@richkilmer.com | # ---------------------------- ----------------------------- # | | | | # --------------- ---------------- ----------- -------------- # | drbrain | | zenspider | | pabs@dc | | tomcope@dc | # --------------- ---------------- ----------- -------------- # # # Now, rather than having 4 trusted certificates (one for drbrain, zenspider, # pabs@dc, and tomecope@dc), a user could actually get by with one # certificate, the "rubygems@rubygems.org" certificate. # # Here's how it works: # # I install "rdoc-3.12.gem", a package signed by "drbrain". I've never heard # of "drbrain", but his certificate has a valid signature from the # "seattle.rb@seattlerb.org" certificate, which in turn has a valid signature # from the "rubygems@rubygems.org" certificate. Voila! At this point, it's # much more reasonable for me to trust a package signed by "drbrain", because # I can establish a chain to "rubygems@rubygems.org", which I do trust. # # === Signing certificates # # The --sign option allows all this to happen. A developer # creates their build certificate with the --build option, then # has their certificate signed by taking it with them to their next regional # Ruby meetup (in our hypothetical example), and it's signed there by the # person holding the regional RubyGems signing certificate, which is signed at # the next RubyConf by the holder of the top-level RubyGems certificate. At # each point the issuer runs the same command: # # # sign a certificate with the specified key and certificate # # (note that this modifies client_cert.pem!) # $ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem # --sign client_cert.pem # # Then the holder of issued certificate (in this case, your buddy "drbrain"), # can start using this signed certificate to sign RubyGems. By the way, in # order to let everyone else know about his new fancy signed certificate, # "drbrain" would save his newly signed certificate as # ~/.gem/gem-public_cert.pem # # Obviously this RubyGems trust infrastructure doesn't exist yet. Also, in # the "real world", issuers actually generate the child certificate from a # certificate request, rather than sign an existing certificate. And our # hypothetical infrastructure is missing a certificate revocation system. # These are that can be fixed in the future... # # At this point you should know how to do all of these new and interesting # things: # # * build a gem signing key and certificate # * adjust your security policy # * modify your trusted certificate list # * sign a certificate # # == Manually verifying signatures # # In case you don't trust RubyGems you can verify gem signatures manually: # # 1. Fetch and unpack the gem # # gem fetch some_signed_gem # tar -xf some_signed_gem-1.0.gem # # 2. Grab the public key from the gemspec # # gem spec some_signed_gem-1.0.gem cert_chain | \ # ruby -rpsych -e 'puts Psych.load($stdin)' > public_key.crt # # 3. Generate a SHA1 hash of the data.tar.gz # # openssl dgst -sha1 < data.tar.gz > my.hash # # 4. Verify the signature # # openssl rsautl -verify -inkey public_key.crt -certin \ # -in data.tar.gz.sig > verified.hash # # 5. Compare your hash to the verified hash # # diff -s verified.hash my.hash # # 6. Repeat 5 and 6 with metadata.gz # # == OpenSSL Reference # # The .pem files generated by --build and --sign are PEM files. Here's a # couple of useful OpenSSL commands for manipulating them: # # # convert a PEM format X509 certificate into DER format: # # (note: Windows .cer files are X509 certificates in DER format) # $ openssl x509 -in input.pem -outform der -out output.der # # # print out the certificate in a human-readable format: # $ openssl x509 -in input.pem -noout -text # # And you can do the same thing with the private key file as well: # # # convert a PEM format RSA key into DER format: # $ openssl rsa -in input_key.pem -outform der -out output_key.der # # # print out the key in a human readable format: # $ openssl rsa -in input_key.pem -noout -text # # == Bugs/TODO # # * There's no way to define a system-wide trust list. # * custom security policies (from a YAML file, etc) # * Simple method to generate a signed certificate request # * Support for OCSP, SCVP, CRLs, or some other form of cert status check # (list is in order of preference) # * Support for encrypted private keys # * Some sort of semi-formal trust hierarchy (see long-winded explanation # above) # * Path discovery (for gem certificate chains that don't have a self-signed # root) -- by the way, since we don't have this, THE ROOT OF THE CERTIFICATE # CHAIN MUST BE SELF SIGNED if Policy#verify_root is true (and it is for the # MediumSecurity and HighSecurity policies) # * Better explanation of X509 naming (ie, we don't have to use email # addresses) # * Honor AIA field (see note about OCSP above) # * Honor extension restrictions # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12 # file, instead of an array embedded in the metadata. # # == Original author # # Paul Duncan # https://pablotron.org/ module Gem::Security ## # Gem::Security default exception type class Exception < Gem::Exception; end ## # Used internally to select the signing digest from all computed digests DIGEST_NAME = "SHA256" # :nodoc: ## # Length of keys created by RSA and DSA keys RSA_DSA_KEY_LENGTH = 3072 ## # Default algorithm to use when building a key pair DEFAULT_KEY_ALGORITHM = "RSA" ## # Named curve used for Elliptic Curve EC_NAME = "secp384r1" ## # Cipher used to encrypt the key pair used to sign gems. # Must be in the list returned by OpenSSL::Cipher.ciphers KEY_CIPHER = OpenSSL::Cipher.new("AES-256-CBC") if defined?(OpenSSL::Cipher) ## # One day in seconds ONE_DAY = 86_400 ## # One year in seconds ONE_YEAR = ONE_DAY * 365 ## # The default set of extensions are: # # * The certificate is not a certificate authority # * The key for the certificate may be used for key and data encipherment # and digital signatures # * The certificate contains a subject key identifier EXTENSIONS = { "basicConstraints" => "CA:FALSE", "keyUsage" => "keyEncipherment,dataEncipherment,digitalSignature", "subjectKeyIdentifier" => "hash", }.freeze def self.alt_name_or_x509_entry(certificate, x509_entry) alt_name = certificate.extensions.find do |extension| extension.oid == "#{x509_entry}AltName" end return alt_name.value if alt_name certificate.send x509_entry end ## # Creates an unsigned certificate for +subject+ and +key+. The lifetime of # the key is from the current time to +age+ which defaults to one year. # # The +extensions+ restrict the key to the indicated uses. def self.create_cert(subject, key, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1) cert = OpenSSL::X509::Certificate.new cert.public_key = get_public_key(key) cert.version = 2 cert.serial = serial cert.not_before = Time.now cert.not_after = Time.now + age cert.subject = subject ef = OpenSSL::X509::ExtensionFactory.new nil, cert cert.extensions = extensions.map do |ext_name, value| ef.create_extension ext_name, value end cert end ## # Gets the right public key from a PKey instance def self.get_public_key(key) # Ruby 3.0 (Ruby/OpenSSL 2.2) or later return OpenSSL::PKey.read(key.public_to_der) if key.respond_to?(:public_to_der) return key.public_key unless key.is_a?(OpenSSL::PKey::EC) ec_key = OpenSSL::PKey::EC.new(key.group.curve_name) ec_key.public_key = key.public_key ec_key end ## # Creates a self-signed certificate with an issuer and subject from +email+, # a subject alternative name of +email+ and the given +extensions+ for the # +key+. def self.create_cert_email(email, key, age = ONE_YEAR, extensions = EXTENSIONS) subject = email_to_name email extensions = extensions.merge "subjectAltName" => "email:#{email}" create_cert_self_signed subject, key, age, extensions end ## # Creates a self-signed certificate with an issuer and subject of +subject+ # and the given +extensions+ for the +key+. def self.create_cert_self_signed(subject, key, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1) certificate = create_cert subject, key, age, extensions sign certificate, key, certificate, age, extensions, serial end ## # Creates a new digest instance using the specified +algorithm+. The default # is SHA256. def self.create_digest(algorithm = DIGEST_NAME) OpenSSL::Digest.new(algorithm) end ## # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC # are supported. def self.create_key(algorithm) if defined?(OpenSSL::PKey) case algorithm.downcase when "dsa" OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH) when "rsa" OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH) when "ec" OpenSSL::PKey::EC.generate(EC_NAME) else raise Gem::Security::Exception, "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported." end end end ## # Turns +email_address+ into an OpenSSL::X509::Name def self.email_to_name(email_address) email_address = email_address.gsub(/[^\w@.-]+/i, "_") cn, dcs = email_address.split "@" dcs = dcs.split "." OpenSSL::X509::Name.new([ ["CN", cn], *dcs.map {|dc| ["DC", dc] }, ]) end ## # Signs +expired_certificate+ with +private_key+ if the keys match and the # expired certificate was self-signed. #-- # TODO increment serial def self.re_sign(expired_certificate, private_key, age = ONE_YEAR, extensions = EXTENSIONS) raise Gem::Security::Exception, "incorrect signing key for re-signing " + expired_certificate.subject.to_s unless expired_certificate.check_private_key(private_key) unless expired_certificate.subject.to_s == expired_certificate.issuer.to_s subject = alt_name_or_x509_entry expired_certificate, :subject issuer = alt_name_or_x509_entry expired_certificate, :issuer raise Gem::Security::Exception, "#{subject} is not self-signed, contact #{issuer} " \ "to obtain a valid certificate" end serial = expired_certificate.serial + 1 create_cert_self_signed(expired_certificate.subject, private_key, age, extensions, serial) end ## # Resets the trust directory for verifying gems. def self.reset @trust_dir = nil end ## # Sign the public key from +certificate+ with the +signing_key+ and # +signing_cert+, using the Gem::Security::DIGEST_NAME. Uses the # default certificate validity range and extensions. # # Returns the newly signed certificate. def self.sign(certificate, signing_key, signing_cert, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1) signee_subject = certificate.subject signee_key = certificate.public_key alt_name = certificate.extensions.find do |extension| extension.oid == "subjectAltName" end extensions = extensions.merge "subjectAltName" => alt_name.value if alt_name issuer_alt_name = signing_cert.extensions.find do |extension| extension.oid == "subjectAltName" end extensions = extensions.merge "issuerAltName" => issuer_alt_name.value if issuer_alt_name signed = create_cert signee_subject, signee_key, age, extensions, serial signed.issuer = signing_cert.subject signed.sign signing_key, Gem::Security::DIGEST_NAME end ## # Returns a Gem::Security::TrustDir which wraps the directory where trusted # certificates live. def self.trust_dir return @trust_dir if @trust_dir dir = File.join Gem.user_home, ".gem", "trust" @trust_dir ||= Gem::Security::TrustDir.new dir end ## # Enumerates the trusted certificates via Gem::Security::TrustDir. def self.trusted_certificates(&block) trust_dir.each_certificate(&block) end ## # Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given # +permissions+. If passed +cipher+ and +passphrase+ those arguments will be # passed to +to_pem+. def self.write(pemmable, path, permissions = 0o600, passphrase = nil, cipher = KEY_CIPHER) path = File.expand_path path File.open path, "wb", permissions do |io| if passphrase && cipher io.write pemmable.to_pem cipher, passphrase else io.write pemmable.to_pem end end path end reset end if Gem::HAVE_OPENSSL require_relative "security/policy" require_relative "security/policies" require_relative "security/trust_dir" end require_relative "security/signer" PK!Hrubygems/stub_specification.rbnu[# frozen_string_literal: true ## # Gem::StubSpecification reads the stub: line from the gemspec. This prevents # us having to eval the entire gemspec in order to find out certain # information. class Gem::StubSpecification < Gem::BasicSpecification # :nodoc: PREFIX = "# stub: " # :nodoc: OPEN_MODE = "r:UTF-8:-" class StubLine # :nodoc: all attr_reader :name, :version, :platform, :require_paths, :extensions, :full_name NO_EXTENSIONS = [].freeze # These are common require paths. REQUIRE_PATHS = { # :nodoc: "lib" => "lib", "test" => "test", "ext" => "ext", }.freeze # These are common require path lists. This hash is used to optimize # and consolidate require_path objects. Most specs just specify "lib" # in their require paths, so lets take advantage of that by pre-allocating # a require path list for that case. REQUIRE_PATH_LIST = { # :nodoc: "lib" => ["lib"].freeze, }.freeze def initialize(data, extensions) parts = data[PREFIX.length..-1].split(" ", 4) @name = -parts[0] @version = if Gem::Version.correct?(parts[1]) Gem::Version.new(parts[1]) else Gem::Version.new(0) end @platform = Gem::Platform.new parts[2] @extensions = extensions @full_name = if platform == Gem::Platform::RUBY "#{name}-#{version}" else "#{name}-#{version}-#{platform}" end path_list = parts.last @require_paths = REQUIRE_PATH_LIST[path_list] || path_list.split("\0").map! do |x| REQUIRE_PATHS[x] || x end end end def self.default_gemspec_stub(filename, base_dir, gems_dir) new filename, base_dir, gems_dir, true end def self.gemspec_stub(filename, base_dir, gems_dir) new filename, base_dir, gems_dir, false end attr_reader :base_dir, :gems_dir def initialize(filename, base_dir, gems_dir, default_gem) super() self.loaded_from = filename @data = nil @name = nil @spec = nil @base_dir = base_dir @gems_dir = gems_dir @default_gem = default_gem end ## # True when this gem has been activated def activated? @activated ||= !loaded_spec.nil? end def default_gem? @default_gem end def build_extensions # :nodoc: return if default_gem? return if extensions.empty? to_spec.build_extensions end ## # If the gemspec contains a stubline, returns a StubLine instance. Otherwise # returns the full Gem::Specification. def data unless @data begin saved_lineno = $. Gem.open_file loaded_from, OPEN_MODE do |file| file.readline # discard encoding line stubline = file.readline if stubline.start_with?(PREFIX) extline = file.readline extensions = if extline.delete_prefix!(PREFIX) extline.chomp! extline.split "\0" else StubLine::NO_EXTENSIONS end stubline.chomp! # readline(chomp: true) allocates 3x as much as .readline.chomp! @data = StubLine.new stubline, extensions end rescue EOFError end ensure $. = saved_lineno end end @data ||= to_spec end private :data def raw_require_paths # :nodoc: data.require_paths end def missing_extensions? return false if default_gem? return false if extensions.empty? return false if File.exist? gem_build_complete_path to_spec.missing_extensions? end ## # Name of the gem def name data.name end ## # Platform of the gem def platform data.platform end ## # Extensions for this gem def extensions data.extensions end ## # Version of the gem def version data.version end def full_name data.full_name end ## # The full Gem::Specification for this gem, loaded from evalling its gemspec def spec @spec ||= loaded_spec if @data @spec ||= Gem::Specification.load(loaded_from) end alias_method :to_spec, :spec ## # Is this StubSpecification valid? i.e. have we found a stub line, OR does # the filename contain a valid gemspec? def valid? data end ## # Is there a stub line present for this StubSpecification? def stubbed? data.is_a? StubLine end def ==(other) # :nodoc: self.class === other && name == other.name && version == other.version && platform == other.platform end alias_method :eql?, :== # :nodoc: def hash # :nodoc: name.hash ^ version.hash ^ platform.hash end def <=>(other) # :nodoc: sort_obj <=> other.sort_obj end def sort_obj # :nodoc: [name, version, Gem::Platform.sort_priority(platform)] end private def loaded_spec spec = Gem.loaded_specs[name] return unless spec && spec.version == version && spec.default_gem? == default_gem? spec end end PK!!!rubygems/vendored_net_http.rbnu[# frozen_string_literal: true # Ruby 3.3 and RubyGems 3.5 is already load Gem::Timeout from lib/rubygems/net/http.rb # We should avoid to load it again require_relative "vendor/net-http/lib/net/http" unless defined?(Gem::Net::HTTP) PK!z!eerubygems/specification.rbnu[# frozen_string_literal: true # #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "deprecate" require_relative "basic_specification" require_relative "stub_specification" require_relative "platform" require_relative "specification_record" require_relative "util/list" require "rbconfig" ## # The Specification class contains the information for a gem. Typically # defined in a .gemspec file or a Rakefile, and looks like this: # # Gem::Specification.new do |s| # s.name = 'example' # s.version = '0.1.0' # s.licenses = ['MIT'] # s.summary = "This is an example!" # s.description = "Much longer explanation of the example!" # s.authors = ["Ruby Coder"] # s.email = 'rubycoder@example.com' # s.files = ["lib/example.rb"] # s.homepage = 'https://rubygems.org/gems/example' # s.metadata = { "source_code_uri" => "https://github.com/example/example" } # end # # Starting in RubyGems 2.0, a Specification can hold arbitrary # metadata. See #metadata for restrictions on the format and size of metadata # items you may add to a specification. class Gem::Specification < Gem::BasicSpecification extend Gem::Deprecate # REFACTOR: Consider breaking out this version stuff into a separate # module. There's enough special stuff around it that it may justify # a separate class. ## # The version number of a specification that does not specify one # (i.e. RubyGems 0.7 or earlier). NONEXISTENT_SPECIFICATION_VERSION = -1 ## # The specification version applied to any new Specification instances # created. This should be bumped whenever something in the spec format # changes. # # Specification Version History: # # spec ruby # ver ver yyyy-mm-dd description # -1 <0.8.0 pre-spec-version-history # 1 0.8.0 2004-08-01 Deprecated "test_suite_file" for "test_files" # "test_file=x" is a shortcut for "test_files=[x]" # 2 0.9.5 2007-10-01 Added "required_rubygems_version" # Now forward-compatible with future versions # 3 1.3.2 2009-01-03 Added Fixnum validation to specification_version # 4 1.9.0 2011-06-07 Added metadata #-- # When updating this number, be sure to also update #to_ruby. # # NOTE RubyGems < 1.2 cannot load specification versions > 2. CURRENT_SPECIFICATION_VERSION = 4 # :nodoc: ## # An informal list of changes to the specification. The highest-valued # key should be equal to the CURRENT_SPECIFICATION_VERSION. SPECIFICATION_VERSION_HISTORY = { # :nodoc: -1 => ["(RubyGems versions up to and including 0.7 did not have versioned specifications)"], 1 => [ 'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"', '"test_file=x" is a shortcut for "test_files=[x]"', ], 2 => [ 'Added "required_rubygems_version"', "Now forward-compatible with future versions", ], 3 => [ "Added Fixnum validation to the specification_version", ], 4 => [ "Added sandboxed freeform metadata to the specification version.", ], }.freeze MARSHAL_FIELDS = { # :nodoc: -1 => 16, 1 => 16, 2 => 16, 3 => 17, 4 => 18, }.freeze today = Time.now.utc TODAY = Time.utc(today.year, today.month, today.day) # :nodoc: @load_cache = {} # :nodoc: @load_cache_mutex = Thread::Mutex.new VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc: # :startdoc: ## # List of attribute names: [:name, :version, ...] @@required_attributes = [:rubygems_version, :specification_version, :name, :version, :date, :summary, :require_paths] ## # Map of attribute names to default values. @@default_value = { authors: [], autorequire: nil, bindir: "bin", cert_chain: [], date: nil, dependencies: [], description: nil, email: nil, executables: [], extensions: [], extra_rdoc_files: [], files: [], homepage: nil, licenses: [], metadata: {}, name: nil, platform: Gem::Platform::RUBY, post_install_message: nil, rdoc_options: [], require_paths: ["lib"], required_ruby_version: Gem::Requirement.default, required_rubygems_version: Gem::Requirement.default, requirements: [], rubygems_version: Gem::VERSION, signing_key: nil, specification_version: CURRENT_SPECIFICATION_VERSION, summary: nil, test_files: [], version: nil, }.freeze # rubocop:disable Style/MutableConstant INITIALIZE_CODE_FOR_DEFAULTS = {} # :nodoc: # rubocop:enable Style/MutableConstant @@default_value.each do |k,v| INITIALIZE_CODE_FOR_DEFAULTS[k] = case v when [], {}, true, false, nil, Numeric, Symbol v.inspect when String v.dump else "default_value(:#{k}).dup" end end @@attributes = @@default_value.keys.sort_by(&:to_s) @@array_attributes = @@default_value.select {|_k,v| v.is_a?(Array) }.keys @@nil_attributes, @@non_nil_attributes = @@default_value.keys.partition do |k| @@default_value[k].nil? end # Sentinel object to represent "not found" stubs NOT_FOUND = Struct.new(:to_spec, :this).new # :nodoc: deprecate_constant :NOT_FOUND # Tracking removed method calls to warn users during build time. REMOVED_METHODS = [:rubyforge_project=, :mark_version].freeze # :nodoc: def removed_method_calls @removed_method_calls ||= [] end ###################################################################### # :section: Required gemspec attributes ## # This gem's name. # # Usage: # # spec.name = 'rake' attr_accessor :name ## # This gem's version. # # The version string can contain numbers and periods, such as +1.0.0+. # A gem is a 'prerelease' gem if the version has a letter in it, such as # +1.0.0.pre+. # # Usage: # # spec.version = '0.4.1' attr_reader :version ## # A short summary of this gem's description. Displayed in gem list -d. # # The #description should be more detailed than the summary. # # Usage: # # spec.summary = "This is a small summary of my gem" attr_reader :summary ## # Files included in this gem. You cannot append to this accessor, you must # assign to it. # # Only add files you can require to this list, not directories, etc. # # Directories are automatically stripped from this list when building a gem, # other non-files cause an error. # # Usage: # # require 'rake' # spec.files = FileList['lib/**/*.rb', # 'bin/*', # '[A-Z]*'].to_a # # # or without Rake... # spec.files = Dir['lib/**/*.rb'] + Dir['bin/*'] # spec.files += Dir['[A-Z]*'] # spec.files.reject! { |fn| fn.include? "CVS" } def files # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks) # DOC: Why isn't it normal? Why does it suck? How can we fix this? @files = [@files, @test_files, add_bindir(@executables), @extra_rdoc_files, @extensions].flatten.compact.uniq.sort end ## # A list of authors for this gem. # # Alternatively, a single author can be specified by assigning a string to # +spec.author+ # # Usage: # # spec.authors = ['John Jones', 'Mary Smith'] def authors=(value) @authors = Array(value).flatten.grep(String) end ###################################################################### # :section: Recommended gemspec attributes ## # The version of Ruby required by this gem # # Usage: # # spec.required_ruby_version = '>= 2.7.0' attr_reader :required_ruby_version ## # A long description of this gem # # The description should be more detailed than the summary but not # excessively long. A few paragraphs is a recommended length with no # examples or formatting. # # Usage: # # spec.description = <<~EOF # Rake is a Make-like program implemented in Ruby. Tasks and # dependencies are specified in standard Ruby syntax. # EOF attr_reader :description ## # A contact email address (or addresses) for this gem # # Usage: # # spec.email = 'john.jones@example.com' # spec.email = ['jack@example.com', 'jill@example.com'] attr_accessor :email ## # The URL of this gem's home page # # Usage: # # spec.homepage = 'https://github.com/ruby/rake' attr_accessor :homepage ## # The license for this gem. # # The license must be no more than 64 characters. # # This should just be the name of your license. The full text of the license # should be inside of the gem (at the top level) when you build it. # # The simplest way is to specify the standard SPDX ID # https://spdx.org/licenses/ for the license. # Ideally, you should pick one that is OSI (Open Source Initiative) # https://opensource.org/licenses/ approved. # # The most commonly used OSI-approved licenses are MIT and Apache-2.0. # GitHub also provides a license picker at https://choosealicense.com/. # # You can also use a custom license file along with your gemspec and specify # a LicenseRef-, where idstring is the name of the file containing # the license text. # # You should specify a license for your gem so that people know how they are # permitted to use it and any restrictions you're placing on it. Not # specifying a license means all rights are reserved; others have no right # to use the code for any purpose. # # You can set multiple licenses with #licenses= # # Usage: # spec.license = 'MIT' def license=(o) self.licenses = [o] end ## # The license(s) for the library. # # Each license must be a short name, no more than 64 characters. # # This should just be the name of your license. The full # text of the license should be inside of the gem when you build it. # # See #license= for more discussion # # Usage: # spec.licenses = ['MIT', 'GPL-2.0'] def licenses=(licenses) @licenses = Array licenses end ## # The metadata holds extra data for this gem that may be useful to other # consumers and is settable by gem authors. # # Metadata items have the following restrictions: # # * The metadata must be a Hash object # * All keys and values must be Strings # * Keys can be a maximum of 128 bytes and values can be a maximum of 1024 # bytes # * All strings must be UTF-8, no binary data is allowed # # You can use metadata to specify links to your gem's homepage, codebase, # documentation, wiki, mailing list, issue tracker and changelog. # # s.metadata = { # "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", # "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", # "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", # "homepage_uri" => "https://bestgemever.example.io", # "mailing_list_uri" => "https://groups.example.com/bestgemever", # "source_code_uri" => "https://example.com/user/bestgemever", # "wiki_uri" => "https://example.com/user/bestgemever/wiki" # "funding_uri" => "https://example.com/donate" # } # # These links will be used on your gem's page on rubygems.org and must pass # validation against following regex. # # %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} attr_accessor :metadata ###################################################################### # :section: Optional gemspec attributes ## # Singular (alternative) writer for #authors # # Usage: # # spec.author = 'John Jones' def author=(o) self.authors = [o] end ## # The path in the gem for executable scripts. Usually 'exe' # # Usage: # # spec.bindir = 'exe' attr_accessor :bindir ## # The certificate chain used to sign this gem. See Gem::Security for # details. attr_accessor :cert_chain ## # A message that gets displayed after the gem is installed. # # Usage: # # spec.post_install_message = "Thanks for installing!" attr_accessor :post_install_message ## # The platform this gem runs on. # # This is usually Gem::Platform::RUBY or Gem::Platform::CURRENT. # # Most gems contain pure Ruby code; they should simply leave the default # value in place. Some gems contain C (or other) code to be compiled into a # Ruby "extension". The gem should leave the default value in place unless # the code will only compile on a certain type of system. Some gems consist # of pre-compiled code ("binary gems"). It's especially important that they # set the platform attribute appropriately. A shortcut is to set the # platform to Gem::Platform::CURRENT, which will cause the gem builder to set # the platform to the appropriate value for the system on which the build is # being performed. # # If this attribute is set to a non-default value, it will be included in # the filename of the gem when it is built such as: # nokogiri-1.6.0-x86-mingw32.gem # # Usage: # # spec.platform = Gem::Platform.local def platform=(platform) if @original_platform.nil? || @original_platform == Gem::Platform::RUBY @original_platform = platform end case platform when Gem::Platform::CURRENT then @new_platform = Gem::Platform.local @original_platform = @new_platform.to_s when Gem::Platform then @new_platform = platform # legacy constants when nil, Gem::Platform::RUBY then @new_platform = Gem::Platform::RUBY when "mswin32" then # was Gem::Platform::WIN32 @new_platform = Gem::Platform.new "x86-mswin32" when "i586-linux" then # was Gem::Platform::LINUX_586 @new_platform = Gem::Platform.new "x86-linux" when "powerpc-darwin" then # was Gem::Platform::DARWIN @new_platform = Gem::Platform.new "ppc-darwin" else @new_platform = Gem::Platform.new platform end @platform = @new_platform.to_s invalidate_memoized_attributes end ## # Paths in the gem to add to $LOAD_PATH when this gem is # activated. #-- # See also #require_paths #++ # If you have an extension you do not need to add "ext" to the # require path, the extension build process will copy the extension files # into "lib" for you. # # The default value is "lib" # # Usage: # # # If all library files are in the root directory... # spec.require_paths = ['.'] def require_paths=(val) @require_paths = Array(val) end ## # The RubyGems version required by this gem attr_reader :required_rubygems_version ## # The key used to sign this gem. See Gem::Security for details. attr_accessor :signing_key ## # Adds a development dependency named +gem+ with +requirements+ to this # gem. # # Usage: # # spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4' # # Development dependencies aren't installed by default and aren't # activated when a gem is required. def add_development_dependency(gem, *requirements) add_dependency_with_type(gem, :development, requirements) end ## # Adds a runtime dependency named +gem+ with +requirements+ to this gem. # # Usage: # # spec.add_dependency 'example', '~> 1.1', '>= 1.1.4' def add_dependency(gem, *requirements) if requirements.uniq.size != requirements.size warn "WARNING: duplicated #{gem} dependency #{requirements}" end add_dependency_with_type(gem, :runtime, requirements) end ## # Executables included in the gem. # # For example, the rake gem has rake as an executable. You don't specify the # full path (as in bin/rake); all application-style files are expected to be # found in bindir. These files must be executable Ruby files. Files that # use bash or other interpreters will not work. # # Executables included may only be ruby scripts, not scripts for other # languages or compiled binaries. # # Usage: # # spec.executables << 'rake' def executables @executables ||= [] end ## # Extensions to build when installing the gem, specifically the paths to # extconf.rb-style files used to compile extensions. # # These files will be run when the gem is installed, causing the C (or # whatever) code to be compiled on the user's machine. # # Usage: # # spec.extensions << 'ext/rmagic/extconf.rb' # # See Gem::Ext::Builder for information about writing extensions for gems. def extensions @extensions ||= [] end ## # Extra files to add to RDoc such as README or doc/examples.txt # # When the user elects to generate the RDoc documentation for a gem (typically # at install time), all the library files are sent to RDoc for processing. # This option allows you to have some non-code files included for a more # complete set of documentation. # # Usage: # # spec.extra_rdoc_files = ['README', 'doc/user-guide.txt'] def extra_rdoc_files @extra_rdoc_files ||= [] end ## # The version of RubyGems that installed this gem. Returns # Gem::Version.new(0) for gems installed by versions earlier # than RubyGems 2.2.0. def installed_by_version # :nodoc: @installed_by_version ||= Gem::Version.new(0) end ## # Sets the version of RubyGems that installed this gem. See also # #installed_by_version. def installed_by_version=(version) # :nodoc: @installed_by_version = Gem::Version.new version end ## # Specifies the rdoc options to be used when generating API documentation. # # Usage: # # spec.rdoc_options << '--title' << 'Rake -- Ruby Make' << # '--main' << 'README' << # '--line-numbers' def rdoc_options @rdoc_options ||= [] end LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1") ## # The version of Ruby required by this gem. The ruby version can be # specified to the patch-level: # # $ ruby -v -e 'p Gem.ruby_version' # ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0] # # # # Prereleases can also be specified. # # Usage: # # # This gem will work with 1.8.6 or greater... # spec.required_ruby_version = '>= 1.8.6' # # # Only with final releases of major version 2 where minor version is at least 3 # spec.required_ruby_version = '~> 2.3' # # # Only prereleases or final releases after 2.6.0.preview2 # spec.required_ruby_version = '> 2.6.0.preview2' # # # This gem will work with 2.3.0 or greater, including major version 3, but lesser than 4.0.0 # spec.required_ruby_version = '>= 2.3', '< 4' def required_ruby_version=(req) @required_ruby_version = Gem::Requirement.create req @required_ruby_version.requirements.map! do |op, v| if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4 [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))] else [op, v] end end end ## # The RubyGems version required by this gem def required_rubygems_version=(req) @required_rubygems_version = Gem::Requirement.create req end ## # Lists the external (to RubyGems) requirements that must be met for this gem # to work. It's simply information for the user. # # Usage: # # spec.requirements << 'libmagick, v6.0' # spec.requirements << 'A good graphics card' def requirements @requirements ||= [] end ## # A collection of unit test files. They will be loaded as unit tests when # the user requests a gem to be unit tested. # # Usage: # spec.test_files = Dir.glob('test/tc_*.rb') # spec.test_files = ['tests/test-suite.rb'] def test_files=(files) # :nodoc: @test_files = Array files end ###################################################################### # :section: Read-only attributes ## # The version of RubyGems used to create this gem. attr_accessor :rubygems_version ## # The path where this gem installs its extensions. def extensions_dir @extensions_dir ||= super end ###################################################################### # :section: Specification internals ## # True when this gemspec has been activated. This attribute is not persisted. attr_accessor :activated alias_method :activated?, :activated ## # Autorequire was used by old RubyGems to automatically require a file. # # Deprecated: It is neither supported nor functional. attr_accessor :autorequire # :nodoc: ## # Sets the default executable for this gem. # # Deprecated: You must now specify the executable name to Gem.bin_path. attr_writer :default_executable rubygems_deprecate :default_executable= ## # Allows deinstallation of gems with legacy platforms. attr_writer :original_platform # :nodoc: ## # The Gem::Specification version of this gemspec. # # Do not set this, it is set automatically when the gem is packaged. attr_accessor :specification_version def self._all # :nodoc: specification_record.all end def self.clear_load_cache # :nodoc: @load_cache_mutex.synchronize do @load_cache.clear end end private_class_method :clear_load_cache def self.gem_path # :nodoc: Gem.path end private_class_method :gem_path def self.each_gemspec(dirs) # :nodoc: dirs.each do |dir| Gem::Util.glob_files_in_dir("*.gemspec", dir).each do |path| yield path end end end def self.gemspec_stubs_in(dir, pattern) # :nodoc: Gem::Util.glob_files_in_dir(pattern, dir).map {|path| yield path }.select(&:valid?) end def self.each_spec(dirs) # :nodoc: each_gemspec(dirs) do |path| spec = self.load path yield spec if spec end end ## # Returns a Gem::StubSpecification for every installed gem def self.stubs specification_record.stubs end ## # Returns a Gem::StubSpecification for default gems def self.default_stubs(pattern = "*.gemspec") base_dir = Gem.default_dir gems_dir = File.join base_dir, "gems" gemspec_stubs_in(Gem.default_specifications_dir, pattern) do |path| Gem::StubSpecification.default_gemspec_stub(path, base_dir, gems_dir) end end ## # Returns a Gem::StubSpecification for installed gem named +name+ # only returns stubs that match Gem.platforms def self.stubs_for(name) specification_record.stubs_for(name) end ## # Finds stub specifications matching a pattern from the standard locations, # optionally filtering out specs not matching the current platform # def self.stubs_for_pattern(pattern, match_platform = true) # :nodoc: specification_record.stubs_for_pattern(pattern, match_platform) end def self._resort!(specs) # :nodoc: specs.sort! do |a, b| names = a.name <=> b.name next names if names.nonzero? versions = b.version <=> a.version next versions if versions.nonzero? platforms = Gem::Platform.sort_priority(b.platform) <=> Gem::Platform.sort_priority(a.platform) next platforms if platforms.nonzero? default_gem = a.default_gem_priority <=> b.default_gem_priority next default_gem if default_gem.nonzero? a.base_dir_priority(gem_path) <=> b.base_dir_priority(gem_path) end end ## # Loads the default specifications. It should be called only once. def self.load_defaults each_spec([Gem.default_specifications_dir]) do |spec| # #load returns nil if the spec is bad, so we just ignore # it at this stage Gem.register_default_spec(spec) end end ## # Adds +spec+ to the known specifications, keeping the collection # properly sorted. def self.add_spec(spec) specification_record.add_spec(spec) end ## # Removes +spec+ from the known specs. def self.remove_spec(spec) specification_record.remove_spec(spec) end ## # Returns all specifications. This method is discouraged from use. # You probably want to use one of the Enumerable methods instead. def self.all warn "NOTE: Specification.all called from #{caller(1, 1).first}" unless Gem::Deprecate.skip _all end ## # Sets the known specs to +specs+. def self.all=(specs) specification_record.all = specs end ## # Return full names of all specs in sorted order. def self.all_names specification_record.all_names end ## # Return the list of all array-oriented instance variables. #-- # Not sure why we need to use so much stupid reflection in here... def self.array_attributes @@array_attributes.dup end ## # Return the list of all instance variables. #-- # Not sure why we need to use so much stupid reflection in here... def self.attribute_names @@attributes.dup end ## # Return the directories that Specification uses to find specs. def self.dirs @@dirs ||= Gem::SpecificationRecord.dirs_from(gem_path) end ## # Set the directories that Specification uses to find specs. Setting # this resets the list of known specs. def self.dirs=(dirs) reset @@dirs = Gem::SpecificationRecord.dirs_from(Array(dirs)) end extend Enumerable ## # Enumerate every known spec. See ::dirs= and ::add_spec to set the list of # specs. def self.each(&block) specification_record.each(&block) end ## # Returns every spec that matches +name+ and optional +requirements+. def self.find_all_by_name(name, *requirements) specification_record.find_all_by_name(name, *requirements) end ## # Returns every spec that has the given +full_name+ def self.find_all_by_full_name(full_name) stubs.select {|s| s.full_name == full_name }.map(&:to_spec) end ## # Find the best specification matching a +name+ and +requirements+. Raises # if the dependency doesn't resolve to a valid specification. def self.find_by_name(name, *requirements) requirements = Gem::Requirement.default if requirements.empty? Gem::Dependency.new(name, *requirements).to_spec end ## # Find the best specification matching a +full_name+. def self.find_by_full_name(full_name) stubs.find {|s| s.full_name == full_name }&.to_spec end ## # Return the best specification that contains the file matching +path+. def self.find_by_path(path) specification_record.find_by_path(path) end ## # Return the best specification that contains the file matching +path+ # amongst the specs that are not activated. def self.find_inactive_by_path(path) specification_record.find_inactive_by_path(path) end ## # Return the best specification that contains the file matching +path+, among # those already activated. def self.find_active_stub_by_path(path) specification_record.find_active_stub_by_path(path) end ## # Return currently unresolved specs that contain the file matching +path+. def self.find_in_unresolved(path) unresolved_specs.find_all {|spec| spec.contains_requirable_file? path } end ## # Search through all unresolved deps and sub-dependencies and return # specs that contain the file matching +path+. def self.find_in_unresolved_tree(path) unresolved_specs.each do |spec| spec.traverse do |_from_spec, _dep, to_spec, trail| if to_spec.has_conflicts? || to_spec.conficts_when_loaded_with?(trail) :next else return trail.reverse if to_spec.contains_requirable_file? path end end end [] end def self.unresolved_specs unresolved_deps.values.map(&:to_specs).flatten end private_class_method :unresolved_specs ## # Special loader for YAML files. When a Specification object is loaded # from a YAML file, it bypasses the normal Ruby object initialization # routine (#initialize). This method makes up for that and deals with # gems of different ages. # # +input+ can be anything that YAML.load() accepts: String or IO. def self.from_yaml(input) Gem.load_yaml input = normalize_yaml_input input spec = Gem::SafeYAML.safe_load input if spec && spec.class == FalseClass raise Gem::EndOfYAMLException end unless Gem::Specification === spec raise Gem::Exception, "YAML data doesn't evaluate to gem specification" end spec.specification_version ||= NONEXISTENT_SPECIFICATION_VERSION spec.reset_nil_attributes_to_default spec.flatten_require_paths spec end ## # Return the latest specs, optionally including prerelease specs if # +prerelease+ is true. def self.latest_specs(prerelease = false) specification_record.latest_specs(prerelease) end ## # Return the latest installed spec for gem +name+. def self.latest_spec_for(name) specification_record.latest_spec_for(name) end def self._latest_specs(specs, prerelease = false) # :nodoc: result = {} specs.reverse_each do |spec| unless prerelease next if spec.version.prerelease? end result[spec.name] = spec end result.map(&:last).flatten.sort_by(&:name) end ## # Loads Ruby format gemspec from +file+. def self.load(file) return unless file spec = @load_cache_mutex.synchronize { @load_cache[file] } return spec if spec return unless File.file?(file) code = Gem.open_file(file, "r:UTF-8:-", &:read) begin spec = eval code, binding, file if Gem::Specification === spec spec.loaded_from = File.expand_path file.to_s @load_cache_mutex.synchronize do prev = @load_cache[file] if prev spec = prev else @load_cache[file] = spec end end return spec end warn "[#{file}] isn't a Gem::Specification (#{spec.class} instead)." rescue SignalException, SystemExit raise rescue SyntaxError, StandardError => e warn "Invalid gemspec in [#{file}]: #{e}" end nil end ## # Specification attributes that must be non-nil def self.non_nil_attributes @@non_nil_attributes.dup end ## # Make sure the YAML specification is properly formatted with dashes def self.normalize_yaml_input(input) result = input.respond_to?(:read) ? input.read : input result = "--- " + result unless result.start_with?("--- ") result = result.dup result.gsub!(/ !!null \n/, " \n") # date: 2011-04-26 00:00:00.000000000Z # date: 2011-04-26 00:00:00.000000000 Z result.gsub!(/^(date: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+?)Z/, '\1 Z') result end ## # Return a list of all outdated local gem names. This method is HEAVY # as it must go fetch specifications from the server. # # Use outdated_and_latest_version if you wish to retrieve the latest remote # version as well. def self.outdated outdated_and_latest_version.map {|local, _| local.name } end ## # Enumerates the outdated local gems yielding the local specification and # the latest remote version. # # This method may take some time to return as it must check each local gem # against the server's index. def self.outdated_and_latest_version return enum_for __method__ unless block_given? # TODO: maybe we should switch to rubygems' version service? fetcher = Gem::SpecFetcher.fetcher latest_specs(true).each do |local_spec| dependency = Gem::Dependency.new local_spec.name, ">= #{local_spec.version}" remotes, = fetcher.search_for_dependency dependency remotes = remotes.map {|n, _| n.version } latest_remote = remotes.sort.last yield [local_spec, latest_remote] if latest_remote && local_spec.version < latest_remote end nil end ## # Is +name+ a required attribute? def self.required_attribute?(name) @@required_attributes.include? name.to_sym end ## # Required specification attributes def self.required_attributes @@required_attributes.dup end ## # Reset the list of known specs, running pre and post reset hooks # registered in Gem. def self.reset @@dirs = nil Gem.pre_reset_hooks.each(&:call) @specification_record = nil clear_load_cache unresolved = unresolved_deps unless unresolved.empty? warn "WARN: Unresolved or ambiguous specs during Gem::Specification.reset:" unresolved.values.each do |dep| warn " #{dep}" versions = find_all_by_name(dep.name).uniq(&:full_name) unless versions.empty? warn " Available/installed versions of this gem:" versions.each {|s| warn " - #{s.version}" } end end warn "WARN: Clearing out unresolved specs. Try 'gem cleanup '" warn "Please report a bug if this causes problems." unresolved.clear end Gem.post_reset_hooks.each(&:call) end ## # Keeps track of all currently known specifications def self.specification_record @specification_record ||= Gem::SpecificationRecord.new(dirs) end # DOC: This method needs documented or nodoc'd def self.unresolved_deps @unresolved_deps ||= Hash.new {|h, n| h[n] = Gem::Dependency.new n } end ## # Load custom marshal format, re-initializing defaults as needed def self._load(str) Gem.load_yaml Gem.load_safe_marshal yaml_set = false retry_count = 0 array = begin Gem::SafeMarshal.safe_load str rescue ArgumentError => e # Avoid an infinite retry loop when the argument error has nothing to do # with the classes not being defined. # 1 retry each allowed in case all 3 of # - YAML # - YAML::Syck::DefaultKey # - YAML::PrivateType # need to be defined raise if retry_count >= 3 # # Some very old marshaled specs included references to `YAML::PrivateType` # and `YAML::Syck::DefaultKey` constants due to bugs in the old emitter # that generated them. Workaround the issue by defining the necessary # constants and retrying. # message = e.message raise unless message.include?("YAML::") unless Object.const_defined?(:YAML) Object.const_set "YAML", Psych yaml_set = true end if message.include?("YAML::Syck::") YAML.const_set "Syck", YAML unless YAML.const_defined?(:Syck) YAML::Syck.const_set "DefaultKey", Class.new if message.include?("YAML::Syck::DefaultKey") && !YAML::Syck.const_defined?(:DefaultKey) elsif message.include?("YAML::PrivateType") && !YAML.const_defined?(:PrivateType) YAML.const_set "PrivateType", Class.new end retry_count += 1 retry ensure Object.__send__(:remove_const, "YAML") if yaml_set end spec = Gem::Specification.new spec.instance_variable_set :@specification_version, array[1] current_version = CURRENT_SPECIFICATION_VERSION field_count = if spec.specification_version > current_version spec.instance_variable_set :@specification_version, current_version MARSHAL_FIELDS[current_version] else MARSHAL_FIELDS[spec.specification_version] end if array.size < field_count raise TypeError, "invalid Gem::Specification format #{array.inspect}" end spec.instance_variable_set :@rubygems_version, array[0] # spec version spec.instance_variable_set :@name, array[2] spec.instance_variable_set :@version, array[3] spec.date = array[4] spec.instance_variable_set :@summary, array[5] spec.instance_variable_set :@required_ruby_version, array[6] spec.instance_variable_set :@required_rubygems_version, array[7] spec.instance_variable_set :@original_platform, array[8] spec.instance_variable_set :@dependencies, array[9] # offset due to rubyforge_project removal spec.instance_variable_set :@email, array[11] spec.instance_variable_set :@authors, array[12] spec.instance_variable_set :@description, array[13] spec.instance_variable_set :@homepage, array[14] spec.instance_variable_set :@has_rdoc, array[15] spec.instance_variable_set :@new_platform, array[16] spec.instance_variable_set :@platform, array[16].to_s spec.instance_variable_set :@licenses, [array[17]] spec.instance_variable_set :@metadata, array[18] spec.instance_variable_set :@loaded, false spec.instance_variable_set :@activated, false spec end def <=>(other) # :nodoc: sort_obj <=> other.sort_obj end def ==(other) # :nodoc: self.class === other && name == other.name && version == other.version && platform == other.platform end ## # Dump only crucial instance variables. #-- # MAINTAIN ORDER! # (down with the man) def _dump(limit) Marshal.dump [ @rubygems_version, @specification_version, @name, @version, date, @summary, @required_ruby_version, @required_rubygems_version, @original_platform, @dependencies, "", # rubyforge_project @email, @authors, @description, @homepage, true, # has_rdoc @new_platform, @licenses, @metadata, ] end ## # Activate this spec, registering it as a loaded spec and adding # it's lib paths to $LOAD_PATH. Returns true if the spec was # activated, false if it was previously activated. Freaks out if # there are conflicts upon activation. def activate other = Gem.loaded_specs[name] if other check_version_conflict other return false end raise_if_conflicts activate_dependencies add_self_to_load_path Gem.loaded_specs[name] = self @activated = true @loaded = true true end ## # Activate all unambiguously resolved runtime dependencies of this # spec. Add any ambiguous dependencies to the unresolved list to be # resolved later, as needed. def activate_dependencies unresolved = Gem::Specification.unresolved_deps runtime_dependencies.each do |spec_dep| if loaded = Gem.loaded_specs[spec_dep.name] next if spec_dep.matches_spec? loaded msg = "can't satisfy '#{spec_dep}', already activated '#{loaded.full_name}'" e = Gem::LoadError.new msg e.name = spec_dep.name raise e end begin specs = spec_dep.to_specs.uniq(&:full_name) rescue Gem::MissingSpecError => e raise Gem::MissingSpecError.new(e.name, e.requirement, "at: #{spec_file}") end if specs.size == 1 specs.first.activate else name = spec_dep.name unresolved[name] = unresolved[name].merge spec_dep end end unresolved.delete self.name end ## # Abbreviate the spec for downloading. Abbreviated specs are only used for # searching, downloading and related activities and do not need deployment # specific information (e.g. list of files). So we abbreviate the spec, # making it much smaller for quicker downloads. def abbreviate self.files = [] self.test_files = [] self.rdoc_options = [] self.extra_rdoc_files = [] self.cert_chain = [] end ## # Sanitize the descriptive fields in the spec. Sometimes non-ASCII # characters will garble the site index. Non-ASCII characters will # be replaced by their XML entity equivalent. def sanitize self.summary = sanitize_string(summary) self.description = sanitize_string(description) self.post_install_message = sanitize_string(post_install_message) self.authors = authors.collect {|a| sanitize_string(a) } end ## # Sanitize a single string. def sanitize_string(string) return string unless string # HACK: the #to_s is in here because RSpec has an Array of Arrays of # Strings for authors. Need a way to disallow bad values on gemspec # generation. (Probably won't happen.) string.to_s end ## # Returns an array with bindir attached to each executable in the # +executables+ list def add_bindir(executables) return nil if executables.nil? if @bindir Array(executables).map {|e| File.join(@bindir, e) } else executables end rescue StandardError nil end ## # Adds a dependency on gem +dependency+ with type +type+ that requires # +requirements+. Valid types are currently :runtime and # :development. def add_dependency_with_type(dependency, type, requirements) requirements = if requirements.empty? Gem::Requirement.default else requirements.flatten end unless dependency.respond_to?(:name) && dependency.respond_to?(:requirement) dependency = Gem::Dependency.new(dependency.to_s, requirements, type) end dependencies << dependency end private :add_dependency_with_type alias_method :add_runtime_dependency, :add_dependency ## # Adds this spec's require paths to LOAD_PATH, in the proper location. def add_self_to_load_path return if default_gem? paths = full_require_paths Gem.add_to_load_path(*paths) end ## # Singular reader for #authors. Returns the first author in the list def author (val = authors) && val.first end ## # The list of author names who wrote this gem. # # spec.authors = ['Chad Fowler', 'Jim Weirich', 'Rich Kilmer'] def authors @authors ||= [] end ## # Returns the full path to installed gem's bin directory. # # NOTE: do not confuse this with +bindir+, which is just 'bin', not # a full path. def bin_dir @bin_dir ||= File.join gem_dir, bindir end ## # Returns the full path to an executable named +name+ in this gem. def bin_file(name) File.join bin_dir, name end ## # Returns the build_args used to install the gem def build_args if File.exist? build_info_file build_info = File.readlines build_info_file build_info = build_info.map(&:strip) build_info.delete "" build_info else [] end end ## # Builds extensions for this platform if the gem has extensions listed and # the gem.build_complete file is missing. def build_extensions # :nodoc: return if extensions.empty? return if default_gem? # we need to fresh build when same name and version of default gems return if self.class.find_by_full_name(full_name)&.default_gem? return if File.exist? gem_build_complete_path return unless File.writable?(base_dir) return unless File.exist?(File.join(base_dir, "extensions")) begin # We need to require things in $LOAD_PATH without looking for the # extension we are about to build. unresolved_deps = Gem::Specification.unresolved_deps.dup Gem::Specification.unresolved_deps.clear require_relative "config_file" require_relative "ext" require_relative "user_interaction" ui = Gem::SilentUI.new Gem::DefaultUserInteraction.use_ui ui do builder = Gem::Ext::Builder.new self builder.build_extensions end ensure ui&.close Gem::Specification.unresolved_deps.replace unresolved_deps end end ## # Returns the full path to the build info directory def build_info_dir File.join base_dir, "build_info" end ## # Returns the full path to the file containing the build # information generated when the gem was installed def build_info_file File.join build_info_dir, "#{full_name}.info" end ## # Returns the full path to the cache directory containing this # spec's cached gem. def cache_dir @cache_dir ||= File.join base_dir, "cache" end ## # Returns the full path to the cached gem for this spec. def cache_file @cache_file ||= File.join cache_dir, "#{full_name}.gem" end ## # Return any possible conflicts against the currently loaded specs. def conflicts conflicts = {} runtime_dependencies.each do |dep| spec = Gem.loaded_specs[dep.name] if spec && !spec.satisfies_requirement?(dep) (conflicts[spec] ||= []) << dep end end env_req = Gem.env_requirement(name) (conflicts[self] ||= []) << env_req unless env_req.satisfied_by? version conflicts end ## # return true if there will be conflict when spec if loaded together with the list of specs. def conficts_when_loaded_with?(list_of_specs) # :nodoc: result = list_of_specs.any? do |spec| spec.runtime_dependencies.any? {|dep| (dep.name == name) && !satisfies_requirement?(dep) } end result end ## # Return true if there are possible conflicts against the currently loaded specs. def has_conflicts? return true unless Gem.env_requirement(name).satisfied_by?(version) runtime_dependencies.any? do |dep| spec = Gem.loaded_specs[dep.name] spec && !spec.satisfies_requirement?(dep) end rescue ArgumentError => e raise e, "#{name} #{version}: #{e.message}" end # The date this gem was created. # # If SOURCE_DATE_EPOCH is set as an environment variable, use that to support # reproducible builds; otherwise, default to the current UTC date. # # Details on SOURCE_DATE_EPOCH: # https://reproducible-builds.org/specs/source-date-epoch/ def date @date ||= Time.utc(*Gem.source_date_epoch.utc.to_a[3..5].reverse) end DateLike = Object.new # :nodoc: def DateLike.===(obj) # :nodoc: defined?(::Date) && Date === obj end DateTimeFormat = # :nodoc: /\A (\d{4})-(\d{2})-(\d{2}) (\s+ \d{2}:\d{2}:\d{2}\.\d+ \s* (Z | [-+]\d\d:\d\d) )? \Z/x ## # The date this gem was created # # DO NOT set this, it is set automatically when the gem is packaged. def date=(date) # We want to end up with a Time object with one-day resolution. # This is the cleanest, most-readable, faster-than-using-Date # way to do it. @date = case date when String then if DateTimeFormat =~ date Time.utc($1.to_i, $2.to_i, $3.to_i) else raise(Gem::InvalidSpecificationException, "invalid date format in specification: #{date.inspect}") end when Time, DateLike then Time.utc(date.year, date.month, date.day) else TODAY end end ## # The default executable for this gem. # # Deprecated: The name of the gem is assumed to be the name of the # executable now. See Gem.bin_path. def default_executable # :nodoc: if defined?(@default_executable) && @default_executable result = @default_executable elsif @executables && @executables.size == 1 result = Array(@executables).first else result = nil end result end rubygems_deprecate :default_executable ## # The default value for specification attribute +name+ def default_value(name) @@default_value[name] end ## # A list of Gem::Dependency objects this gem depends on. # # Use #add_dependency or #add_development_dependency to add dependencies to # a gem. def dependencies @dependencies ||= [] end ## # Return a list of all gems that have a dependency on this gemspec. The # list is structured with entries that conform to: # # [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]] def dependent_gems(check_dev=true) out = [] Gem::Specification.each do |spec| deps = check_dev ? spec.dependencies : spec.runtime_dependencies deps.each do |dep| next unless satisfies_requirement?(dep) sats = [] find_all_satisfiers(dep) do |sat| sats << sat end out << [spec, dep, sats] end end out end ## # Returns all specs that matches this spec's runtime dependencies. def dependent_specs runtime_dependencies.map(&:to_specs).flatten end ## # A detailed description of this gem. See also #summary def description=(str) @description = str.to_s end ## # List of dependencies that are used for development def development_dependencies dependencies.select {|d| d.type == :development } end ## # Returns the full path to this spec's documentation directory. If +type+ # is given it will be appended to the end. For example: # # spec.doc_dir # => "/path/to/gem_repo/doc/a-1" # # spec.doc_dir 'ri' # => "/path/to/gem_repo/doc/a-1/ri" def doc_dir(type = nil) @doc_dir ||= File.join base_dir, "doc", full_name if type File.join @doc_dir, type else @doc_dir end end def encode_with(coder) # :nodoc: coder.add "name", @name coder.add "version", @version platform = case @original_platform when nil, "" then "ruby" when String then @original_platform else @original_platform.to_s end coder.add "platform", platform attributes = @@attributes.map(&:to_s) - %w[name version platform] attributes.each do |name| coder.add name, instance_variable_get("@#{name}") end end def eql?(other) # :nodoc: self.class === other && same_attributes?(other) end ## # Singular accessor for #executables def executable (val = executables) && val.first end ## # Singular accessor for #executables def executable=(o) self.executables = [o] end ## # Sets executables to +value+, ensuring it is an array. def executables=(value) @executables = Array(value) end ## # Sets extensions to +extensions+, ensuring it is an array. def extensions=(extensions) @extensions = Array extensions end ## # Sets extra_rdoc_files to +files+, ensuring it is an array. def extra_rdoc_files=(files) @extra_rdoc_files = Array files end ## # The default (generated) file name of the gem. See also #spec_name. # # spec.file_name # => "example-1.0.gem" def file_name "#{full_name}.gem" end ## # Sets files to +files+, ensuring it is an array. def files=(files) @files = Array files end ## # Finds all gems that satisfy +dep+ def find_all_satisfiers(dep) Gem::Specification.each do |spec| yield spec if spec.satisfies_requirement? dep end end private :find_all_satisfiers ## # Creates a duplicate spec without large blobs that aren't used at runtime. def for_cache spec = dup spec.files = nil spec.test_files = nil spec end def full_name @full_name ||= super end ## # Work around bundler removing my methods def gem_dir # :nodoc: super end def gems_dir @gems_dir ||= File.join(base_dir, "gems") end ## # Deprecated and ignored, defaults to true. # # Formerly used to indicate this gem was RDoc-capable. def has_rdoc # :nodoc: true end rubygems_deprecate :has_rdoc ## # Deprecated and ignored. # # Formerly used to indicate this gem was RDoc-capable. def has_rdoc=(ignored) # :nodoc: @has_rdoc = true end rubygems_deprecate :has_rdoc= alias_method :has_rdoc?, :has_rdoc # :nodoc: rubygems_deprecate :has_rdoc? ## # True if this gem has files in test_files def has_unit_tests? # :nodoc: !test_files.empty? end # :stopdoc: alias_method :has_test_suite?, :has_unit_tests? # :startdoc: def hash # :nodoc: name.hash ^ version.hash end def init_with(coder) # :nodoc: @installed_by_version ||= nil yaml_initialize coder.tag, coder.map end eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # frozen_string_literal: true def set_nil_attributes_to_nil #{@@nil_attributes.map {|key| "@#{key} = nil" }.join "; "} end private :set_nil_attributes_to_nil def set_not_nil_attributes_to_default_values #{@@non_nil_attributes.map {|key| "@#{key} = #{INITIALIZE_CODE_FOR_DEFAULTS[key]}" }.join ";"} end private :set_not_nil_attributes_to_default_values RUBY ## # Specification constructor. Assigns the default values to the attributes # and yields itself for further initialization. Optionally takes +name+ and # +version+. def initialize(name = nil, version = nil) super() @gems_dir = nil @base_dir = nil @loaded = false @activated = false @loaded_from = nil @original_platform = nil @installed_by_version = nil set_nil_attributes_to_nil set_not_nil_attributes_to_default_values @new_platform = Gem::Platform::RUBY self.name = name if name self.version = version if version if (platform = Gem.platforms.last) && platform != Gem::Platform::RUBY && platform != Gem::Platform.local self.platform = platform end yield self if block_given? end ## # Duplicates Array and Gem::Requirement attributes from +other_spec+ so state isn't shared. # def initialize_copy(other_spec) self.class.array_attributes.each do |name| name = :"@#{name}" next unless other_spec.instance_variable_defined? name begin val = other_spec.instance_variable_get(name) if val instance_variable_set name, val.dup elsif Gem.configuration.really_verbose warn "WARNING: #{full_name} has an invalid nil value for #{name}" end rescue TypeError e = Gem::FormatException.new \ "#{full_name} has an invalid value for #{name}" e.file_path = loaded_from raise e end end @required_ruby_version = other_spec.required_ruby_version.dup @required_rubygems_version = other_spec.required_rubygems_version.dup end def base_dir return Gem.dir unless loaded_from @base_dir ||= if default_gem? File.dirname File.dirname File.dirname loaded_from else File.dirname File.dirname loaded_from end end ## # Expire memoized instance variables that can incorrectly generate, replace # or miss files due changes in certain attributes used to compute them. def invalidate_memoized_attributes @full_name = nil @cache_file = nil end private :invalidate_memoized_attributes def inspect # :nodoc: if $DEBUG super else "#{super[0..-2]} #{full_name}>" end end ## # Files in the Gem under one of the require_paths def lib_files @files.select do |file| require_paths.any? do |path| file.start_with? path end end end ## # Singular accessor for #licenses def license licenses.first end ## # Plural accessor for setting licenses # # See #license= for details def licenses @licenses ||= [] end def internal_init # :nodoc: super @bin_dir = nil @cache_dir = nil @cache_file = nil @doc_dir = nil @ri_dir = nil @spec_dir = nil @spec_file = nil end ## # Track removed method calls to warn about during build time. # Warn about unknown attributes while loading a spec. def method_missing(sym, *a, &b) # :nodoc: if REMOVED_METHODS.include?(sym) removed_method_calls << sym return end if @specification_version > CURRENT_SPECIFICATION_VERSION && sym.to_s.end_with?("=") warn "ignoring #{sym} loading #{full_name}" if $DEBUG else super end end ## # Is this specification missing its extensions? When this returns true you # probably want to build_extensions def missing_extensions? return false if extensions.empty? return false if default_gem? return false if File.exist? gem_build_complete_path true end ## # Normalize the list of files so that: # * All file lists have redundancies removed. # * Files referenced in the extra_rdoc_files are included in the package # file list. def normalize if defined?(@extra_rdoc_files) && @extra_rdoc_files @extra_rdoc_files.uniq! @files ||= [] @files.concat(@extra_rdoc_files) end @files = @files.uniq if @files @extensions = @extensions.uniq if @extensions @test_files = @test_files.uniq if @test_files @executables = @executables.uniq if @executables @extra_rdoc_files = @extra_rdoc_files.uniq if @extra_rdoc_files end ## # Return a NameTuple that represents this Specification def name_tuple Gem::NameTuple.new name, version, original_platform end ## # Returns the full name (name-version) of this gemspec using the original # platform. For use with legacy gems. def original_name # :nodoc: if platform == Gem::Platform::RUBY || platform.nil? "#{@name}-#{@version}" else "#{@name}-#{@version}-#{@original_platform}" end end ## # Cruft. Use +platform+. def original_platform # :nodoc: @original_platform ||= platform end ## # The platform this gem runs on. See Gem::Platform for details. def platform @new_platform ||= Gem::Platform::RUBY # rubocop:disable Naming/MemoizedInstanceVariableName end def pretty_print(q) # :nodoc: q.group 2, "Gem::Specification.new do |s|", "end" do q.breakable attributes = @@attributes - [:name, :version] attributes.unshift :installed_by_version attributes.unshift :version attributes.unshift :name attributes.each do |attr_name| current_value = send attr_name current_value = current_value.sort if [:files, :test_files].include? attr_name next unless current_value != default_value(attr_name) || self.class.required_attribute?(attr_name) q.text "s.#{attr_name} = " if attr_name == :date current_value = current_value.utc q.text "Time.utc(#{current_value.year}, #{current_value.month}, #{current_value.day})" else q.pp current_value end q.breakable end end end ## # Raise an exception if the version of this spec conflicts with the one # that is already loaded (+other+) def check_version_conflict(other) # :nodoc: return if version == other.version # This gem is already loaded. If the currently loaded gem is not in the # list of candidate gems, then we have a version conflict. msg = "can't activate #{full_name}, already activated #{other.full_name}" e = Gem::LoadError.new msg e.name = name raise e end private :check_version_conflict ## # Check the spec for possible conflicts and freak out if there are any. def raise_if_conflicts # :nodoc: if has_conflicts? raise Gem::ConflictError.new self, conflicts end end ## # Sets rdoc_options to +value+, ensuring it is an array. def rdoc_options=(options) @rdoc_options = Array options end ## # Singular accessor for #require_paths def require_path (val = require_paths) && val.first end ## # Singular accessor for #require_paths def require_path=(path) self.require_paths = Array(path) end ## # Set requirements to +req+, ensuring it is an array. def requirements=(req) @requirements = Array req end def respond_to_missing?(m, include_private = false) # :nodoc: false end ## # Returns the full path to this spec's ri directory. def ri_dir @ri_dir ||= File.join base_dir, "ri", full_name end ## # Return a string containing a Ruby code representation of the given # object. def ruby_code(obj) case obj when String then obj.dump + ".freeze" when Array then "[" + obj.map {|x| ruby_code x }.join(", ") + "]" when Hash then seg = obj.keys.sort.map {|k| "#{k.to_s.dump} => #{obj[k].to_s.dump}" } "{ #{seg.join(", ")} }" when Gem::Version then ruby_code(obj.to_s) when DateLike then obj.strftime("%Y-%m-%d").dump when Time then obj.strftime("%Y-%m-%d").dump when Numeric then obj.inspect when true, false, nil then obj.inspect when Gem::Platform then "Gem::Platform.new(#{ruby_code obj.to_a})" when Gem::Requirement then list = obj.as_list "Gem::Requirement.new(#{ruby_code(list.size == 1 ? obj.to_s : list)})" else raise Gem::Exception, "ruby_code case not handled: #{obj.class}" end end private :ruby_code ## # List of dependencies that will automatically be activated at runtime. def runtime_dependencies dependencies.select(&:runtime?) end ## # True if this gem has the same attributes as +other+. def same_attributes?(spec) @@attributes.all? {|name, _default| send(name) == spec.send(name) } end private :same_attributes? ## # Checks if this specification meets the requirement of +dependency+. def satisfies_requirement?(dependency) @name == dependency.name && dependency.requirement.satisfied_by?(@version) end ## # Returns an object you can use to sort specifications in #sort_by. def sort_obj [@name, @version, Gem::Platform.sort_priority(@new_platform)] end ## # Used by Gem::Resolver to order Gem::Specification objects def source # :nodoc: Gem::Source::Installed.new end ## # Returns the full path to the directory containing this spec's # gemspec file. eg: /usr/local/lib/ruby/gems/1.8/specifications def spec_dir @spec_dir ||= File.join base_dir, "specifications" end ## # Returns the full path to this spec's gemspec file. # eg: /usr/local/lib/ruby/gems/1.8/specifications/mygem-1.0.gemspec def spec_file @spec_file ||= File.join spec_dir, "#{full_name}.gemspec" end ## # The default name of the gemspec. See also #file_name # # spec.spec_name # => "example-1.0.gemspec" def spec_name "#{full_name}.gemspec" end ## # A short summary of this gem's description. def summary=(str) @summary = str.to_s.strip. gsub(/(\w-)\n[ \t]*(\w)/, '\1\2').gsub(/\n[ \t]*/, " ") # so. weird. end ## # Singular accessor for #test_files def test_file # :nodoc: (val = test_files) && val.first end ## # Singular mutator for #test_files def test_file=(file) # :nodoc: self.test_files = [file] end ## # Test files included in this gem. You cannot append to this accessor, you # must assign to it. def test_files # :nodoc: # Handle the possibility that we have @test_suite_file but not # @test_files. This will happen when an old gem is loaded via # YAML. if defined? @test_suite_file @test_files = [@test_suite_file].flatten @test_suite_file = nil end if defined?(@test_files) && @test_files @test_files else @test_files = [] end end ## # Returns a Ruby code representation of this specification, such that it can # be eval'ed and reconstruct the same specification later. Attributes that # still have their default values are omitted. def to_ruby result = [] result << "# -*- encoding: utf-8 -*-" result << "#{Gem::StubSpecification::PREFIX}#{name} #{version} #{platform} #{raw_require_paths.join("\0")}" result << "#{Gem::StubSpecification::PREFIX}#{extensions.join "\0"}" unless extensions.empty? result << nil result << "Gem::Specification.new do |s|" result << " s.name = #{ruby_code name}" result << " s.version = #{ruby_code version}" unless platform.nil? || platform == Gem::Platform::RUBY result << " s.platform = #{ruby_code original_platform}" end result << "" result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version=" if metadata && !metadata.empty? result << " s.metadata = #{ruby_code metadata} if s.respond_to? :metadata=" end result << " s.require_paths = #{ruby_code raw_require_paths}" handled = [ :dependencies, :name, :platform, :require_paths, :required_rubygems_version, :specification_version, :version, :has_rdoc, :default_executable, :metadata, :signing_key, ] @@attributes.each do |attr_name| next if handled.include? attr_name current_value = send(attr_name) if current_value != default_value(attr_name) || self.class.required_attribute?(attr_name) result << " s.#{attr_name} = #{ruby_code current_value}" end end if String === signing_key result << " s.signing_key = #{ruby_code signing_key}" end if @installed_by_version result << nil result << " s.installed_by_version = #{ruby_code Gem::VERSION}" end unless dependencies.empty? result << nil result << " s.specification_version = #{specification_version}" result << nil dependencies.each do |dep| dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>.freeze, #{ruby_code dep.requirements_list})" end end result << "end" result << nil result.join "\n" end ## # Returns a Ruby lighter-weight code representation of this specification, # used for indexing only. # # See #to_ruby. def to_ruby_for_cache for_cache.to_ruby end def to_s # :nodoc: "#" end ## # Returns self def to_spec self end def to_yaml(opts = {}) # :nodoc: Gem.load_yaml # Because the user can switch the YAML engine behind our # back, we have to check again here to make sure that our # psych code was properly loaded, and load it if not. unless Gem.const_defined?(:NoAliasYAMLTree) require_relative "psych_tree" end builder = Gem::NoAliasYAMLTree.create builder << self ast = builder.tree require "stringio" io = StringIO.new io.set_encoding Encoding::UTF_8 Psych::Visitors::Emitter.new(io).accept(ast) io.string.gsub(/ !!null \n/, " \n") end ## # Recursively walk dependencies of this spec, executing the +block+ for each # hop. def traverse(trail = [], visited = {}, &block) trail.push(self) begin runtime_dependencies.each do |dep| dep.matching_specs(true).each do |dep_spec| next if visited.key?(dep_spec) visited[dep_spec] = true trail.push(dep_spec) begin result = block[self, dep, dep_spec, trail] ensure trail.pop end next if result == :next spec_name = dep_spec.name dep_spec.traverse(trail, visited, &block) unless trail.any? {|s| s.name == spec_name } end end ensure trail.pop end end ## # Checks that the specification contains all required fields, and does a # very basic sanity check. # # Raises InvalidSpecificationException if the spec does not pass the # checks.. def validate(packaging = true, strict = false) normalize validation_policy = Gem::SpecificationPolicy.new(self) validation_policy.packaging = packaging validation_policy.validate(strict) end def keep_only_files_and_directories @executables.delete_if {|x| File.directory?(File.join(@bindir, x)) } @extensions.delete_if {|x| File.directory?(x) && !File.symlink?(x) } @extra_rdoc_files.delete_if {|x| File.directory?(x) && !File.symlink?(x) } @files.delete_if {|x| File.directory?(x) && !File.symlink?(x) } @test_files.delete_if {|x| File.directory?(x) && !File.symlink?(x) } end def validate_for_resolution Gem::SpecificationPolicy.new(self).validate_for_resolution end def validate_metadata Gem::SpecificationPolicy.new(self).validate_metadata end rubygems_deprecate :validate_metadata def validate_dependencies Gem::SpecificationPolicy.new(self).validate_dependencies end rubygems_deprecate :validate_dependencies def validate_permissions Gem::SpecificationPolicy.new(self).validate_permissions end rubygems_deprecate :validate_permissions ## # Set the version to +version+. def version=(version) @version = Gem::Version.create(version) return if @version.nil? invalidate_memoized_attributes end def stubbed? false end def yaml_initialize(tag, vals) # :nodoc: vals.each do |ivar, val| case ivar when "date" # Force Date to go through the extra coerce logic in date= self.date = val else instance_variable_set "@#{ivar}", val end end @original_platform = @platform # for backwards compatibility self.platform = Gem::Platform.new @platform end ## # Reset nil attributes to their default values to make the spec valid def reset_nil_attributes_to_default nil_attributes = self.class.non_nil_attributes.find_all do |name| !instance_variable_defined?("@#{name}") || instance_variable_get("@#{name}").nil? end nil_attributes.each do |attribute| default = default_value attribute value = case default when Time, Numeric, Symbol, true, false, nil then default else default.dup end instance_variable_set "@#{attribute}", value end @installed_by_version ||= nil nil end def flatten_require_paths # :nodoc: return unless raw_require_paths.first.is_a?(Array) warn "#{name} #{version} includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this" raw_require_paths.flatten! end def raw_require_paths # :nodoc: @require_paths end end PK!Duqrubygems/psych_tree.rbnu[# frozen_string_literal: true module Gem if defined? ::Psych::Visitors class NoAliasYAMLTree < Psych::Visitors::YAMLTree def self.create new({}) end unless respond_to? :create def visit_String(str) return super unless str == "=" # or whatever you want quote = Psych::Nodes::Scalar::SINGLE_QUOTED @emitter.scalar str, nil, nil, false, true, quote end # Noop this out so there are no anchors def register(target, obj) end # This is ported over from the yaml_tree in 1.9.3 def format_time(time) if time.utc? time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") else time.strftime("%Y-%m-%d %H:%M:%S.%9N %:z") end end private :format_time end end end PK!(++rubygems/safe_yaml.rbnu[# frozen_string_literal: true module Gem ### # This module is used for safely loading YAML specs from a gem. The # `safe_load` method defined on this module is specifically designed for # loading Gem specifications. For loading other YAML safely, please see # Psych.safe_load module SafeYAML PERMITTED_CLASSES = %w[ Symbol Time Date Gem::Dependency Gem::Platform Gem::Requirement Gem::Specification Gem::Version Gem::Version::Requirement ].freeze PERMITTED_SYMBOLS = %w[ development runtime ].freeze @aliases_enabled = true def self.aliases_enabled=(value) # :nodoc: @aliases_enabled = !!value end def self.aliases_enabled? # :nodoc: @aliases_enabled end def self.safe_load(input) ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled) end def self.load(input) ::Psych.safe_load(input, permitted_classes: [::Symbol]) end end end PK!':rubygems/compatibility.rbnu[# frozen_string_literal: true #-- # This file contains all sorts of little compatibility hacks that we've # had to introduce over the years. Quarantining them into one file helps # us know when we can get rid of them. # # Ruby 1.9.x has introduced some things that are awkward, and we need to # support them, so we define some constants to use later. # # TODO remove at RubyGems 4 #++ module Gem # :stopdoc: RubyGemsVersion = VERSION deprecate_constant(:RubyGemsVersion) RbConfigPriorities = %w[ MAJOR MINOR TEENY EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir rubylibdir ].freeze if defined?(ConfigMap) RbConfigPriorities.each do |key| ConfigMap[key.to_sym] = RbConfig::CONFIG[key] end else ## # Configuration settings from ::RbConfig ConfigMap = Hash.new do |cm, key| cm[key] = RbConfig::CONFIG[key.to_s] end deprecate_constant(:ConfigMap) end end PK!` rubygems/name_tuple.rbnu[# frozen_string_literal: true ## # # Represents a gem of name +name+ at +version+ of +platform+. These # wrap the data returned from the indexes. class Gem::NameTuple def initialize(name, version, platform=Gem::Platform::RUBY) @name = name @version = version platform &&= platform.to_s platform = Gem::Platform::RUBY if !platform || platform.empty? @platform = platform end attr_reader :name, :version, :platform ## # Turn an array of [name, version, platform] into an array of # NameTuple objects. def self.from_list(list) list.map {|t| new(*t) } end ## # Turn an array of NameTuple objects back into an array of # [name, version, platform] tuples. def self.to_basic(list) list.map(&:to_a) end ## # A null NameTuple, ie name=nil, version=0 def self.null new nil, Gem::Version.new(0), nil end ## # Returns the full name (name-version) of this Gem. Platform information is # included if it is not the default Ruby platform. This mimics the behavior # of Gem::Specification#full_name. def full_name case @platform when nil, "", Gem::Platform::RUBY "#{@name}-#{@version}" else "#{@name}-#{@version}-#{@platform}" end end ## # Indicate if this NameTuple matches the current platform. def match_platform? Gem::Platform.match_gem? @platform, @name end ## # Indicate if this NameTuple is for a prerelease version. def prerelease? @version.prerelease? end ## # Return the name that the gemspec file would be def spec_name "#{full_name}.gemspec" end ## # Convert back to the [name, version, platform] tuple def to_a [@name, @version, @platform] end def inspect # :nodoc: "#" end alias_method :to_s, :inspect # :nodoc: def <=>(other) [@name, @version, Gem::Platform.sort_priority(@platform)] <=> [other.name, other.version, Gem::Platform.sort_priority(other.platform)] end include Comparable ## # Compare with +other+. Supports another NameTuple or an Array # in the [name, version, platform] format. def ==(other) case other when self.class @name == other.name && @version == other.version && @platform == other.platform when Array to_a == other else false end end alias_method :eql?, :== def hash to_a.hash end end PK!^i&??rubygems/command.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "vendored_optparse" require_relative "requirement" require_relative "user_interaction" ## # Base class for all Gem commands. When creating a new gem command, define # #initialize, #execute, #arguments, #defaults_str, #description and #usage # (as appropriate). See the above mentioned methods for details. # # A very good example to look at is Gem::Commands::ContentsCommand class Gem::Command include Gem::UserInteraction Gem::OptionParser.accept Symbol, &:to_sym ## # The name of the command. attr_reader :command ## # The options for the command. attr_reader :options ## # The default options for the command. attr_accessor :defaults ## # The name of the command for command-line invocation. attr_accessor :program_name ## # A short description of the command. attr_accessor :summary ## # Arguments used when building gems def self.build_args @build_args ||= [] end def self.build_args=(value) @build_args = value end def self.common_options @common_options ||= [] end def self.add_common_option(*args, &handler) Gem::Command.common_options << [args, handler] end def self.extra_args @extra_args ||= [] end def self.extra_args=(value) case value when Array @extra_args = value when String @extra_args = value.split(" ") end end ## # Return an array of extra arguments for the command. The extra arguments # come from the gem configuration file read at program startup. def self.specific_extra_args(cmd) specific_extra_args_hash[cmd] end ## # Add a list of extra arguments for the given command. +args+ may be an # array or a string to be split on white space. def self.add_specific_extra_args(cmd,args) args = args.split(/\s+/) if args.is_a? String specific_extra_args_hash[cmd] = args end ## # Accessor for the specific extra args hash (self initializing). def self.specific_extra_args_hash @specific_extra_args_hash ||= Hash.new do |h,k| h[k] = Array.new end end ## # Initializes a generic gem command named +command+. +summary+ is a short # description displayed in `gem help commands`. +defaults+ are the default # options. Defaults should be mirrored in #defaults_str, unless there are # none. # # When defining a new command subclass, use add_option to add command-line # switches. # # Unhandled arguments (gem names, files, etc.) are left in # options[:args]. def initialize(command, summary=nil, defaults={}) @command = command @summary = summary @program_name = "gem #{command}" @defaults = defaults @options = defaults.dup @option_groups = Hash.new {|h,k| h[k] = [] } @deprecated_options = { command => {} } @parser = nil @when_invoked = nil end ## # True if +long+ begins with the characters from +short+. def begins?(long, short) return false if short.nil? long[0, short.length] == short end ## # Override to provide command handling. # # #options will be filled in with your parsed options, unparsed options will # be left in options[:args]. # # See also: #get_all_gem_names, #get_one_gem_name, # #get_one_optional_argument def execute raise Gem::Exception, "generic command has no actions" end ## # Display to the user that a gem couldn't be found and reasons why #-- def show_lookup_failure(gem_name, version, errors, suppress_suggestions = false, required_by = nil) gem = "'#{gem_name}' (#{version})" msg = String.new "Could not find a valid gem #{gem}" if errors && !errors.empty? msg << ", here is why:\n" errors.each {|x| msg << " #{x.wordy}\n" } else if required_by && gem != required_by msg << " (required by #{required_by}) in any repository" else msg << " in any repository" end end alert_error msg unless suppress_suggestions suggestions = Gem::SpecFetcher.fetcher.suggest_gems_from_name(gem_name, :latest, 10) unless suggestions.empty? alert_error "Possible alternatives: #{suggestions.join(", ")}" end end end ## # Get all gem names from the command line. def get_all_gem_names args = options[:args] if args.nil? || args.empty? raise Gem::CommandLineError, "Please specify at least one gem name (e.g. gem build GEMNAME)" end args.reject {|arg| arg.start_with?("-") } end ## # Get all [gem, version] from the command line. # # An argument in the form gem:ver is pull apart into the gen name and version, # respectively. def get_all_gem_names_and_versions get_all_gem_names.map do |name| extract_gem_name_and_version(name) end end def extract_gem_name_and_version(name) # :nodoc: if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name [$1, $2] else [name] end end ## # Get a single gem name from the command line. Fail if there is no gem name # or if there is more than one gem name given. def get_one_gem_name args = options[:args] if args.nil? || args.empty? raise Gem::CommandLineError, "Please specify a gem name on the command line (e.g. gem build GEMNAME)" end if args.size > 1 raise Gem::CommandLineError, "Too many gem names (#{args.join(", ")}); please specify only one" end args.first end ## # Get a single optional argument from the command line. If more than one # argument is given, return only the first. Return nil if none are given. def get_one_optional_argument args = options[:args] || [] args.first end ## # Override to provide details of the arguments a command takes. It should # return a left-justified string, one argument per line. # # For example: # # def usage # "#{program_name} FILE [FILE ...]" # end # # def arguments # "FILE name of file to find" # end def arguments "" end ## # Override to display the default values of the command options. (similar to # +arguments+, but displays the default values). # # For example: # # def defaults_str # --no-gems-first --no-all # end def defaults_str "" end ## # Override to display a longer description of what this command does. def description nil end ## # Override to display the usage for an individual gem command. # # The text "[options]" is automatically appended to the usage text. def usage program_name end ## # Display the help message for the command. def show_help parser.program_name = usage say parser end ## # Invoke the command with the given list of arguments. def invoke(*args) invoke_with_build_args args, nil end ## # Invoke the command with the given list of normal arguments # and additional build arguments. def invoke_with_build_args(args, build_args) handle_options args options[:build_args] = build_args if options[:silent] old_ui = ui self.ui = ui = Gem::SilentUI.new end if options[:help] show_help elsif @when_invoked @when_invoked.call options else execute end ensure if ui self.ui = old_ui ui.close end end ## # Call the given block when invoked. # # Normal command invocations just executes the +execute+ method of the # command. Specifying an invocation block allows the test methods to # override the normal action of a command to determine that it has been # invoked correctly. def when_invoked(&block) @when_invoked = block end ## # Add a command-line option and handler to the command. # # See Gem::OptionParser#make_switch for an explanation of +opts+. # # +handler+ will be called with two values, the value of the argument and # the options hash. # # If the first argument of add_option is a Symbol, it's used to group # options in output. See `gem help list` for an example. def add_option(*opts, &handler) # :yields: value, options group_name = Symbol === opts.first ? opts.shift : :options raise "Do not pass an empty string in opts" if opts.include?("") @option_groups[group_name] << [opts, handler] end ## # Remove previously defined command-line argument +name+. def remove_option(name) @option_groups.each do |_, option_list| option_list.reject! {|args, _| args.any? {|x| x.is_a?(String) && x =~ /^#{name}/ } } end end ## # Mark a command-line option as deprecated, and optionally specify a # deprecation horizon. # # Note that with the current implementation, every version of the option needs # to be explicitly deprecated, so to deprecate an option defined as # # add_option('-t', '--[no-]test', 'Set test mode') do |value, options| # # ... stuff ... # end # # you would need to explicitly add a call to `deprecate_option` for every # version of the option you want to deprecate, like # # deprecate_option('-t') # deprecate_option('--test') # deprecate_option('--no-test') def deprecate_option(name, version: nil, extra_msg: nil) @deprecated_options[command].merge!({ name => { "rg_version_to_expire" => version, "extra_msg" => extra_msg } }) end def check_deprecated_options(options) options.each do |option| next unless option_is_deprecated?(option) deprecation = @deprecated_options[command][option] version_to_expire = deprecation["rg_version_to_expire"] deprecate_option_msg = if version_to_expire "The \"#{option}\" option has been deprecated and will be removed in Rubygems #{version_to_expire}." else "The \"#{option}\" option has been deprecated and will be removed in future versions of Rubygems." end extra_msg = deprecation["extra_msg"] deprecate_option_msg += " #{extra_msg}" if extra_msg alert_warning(deprecate_option_msg) end end ## # Merge a set of command options with the set of default options (without # modifying the default option hash). def merge_options(new_options) @options = @defaults.clone new_options.each {|k,v| @options[k] = v } end ## # True if the command handles the given argument list. def handles?(args) parser.parse!(args.dup) true rescue StandardError false end ## # Handle the given list of arguments by parsing them and recording the # results. def handle_options(args) args = add_extra_args(args) check_deprecated_options(args) @options = Marshal.load Marshal.dump @defaults # deep copy parser.parse!(args) @options[:args] = args end ## # Adds extra args from ~/.gemrc def add_extra_args(args) result = [] s_extra = Gem::Command.specific_extra_args(@command) extra = Gem::Command.extra_args + s_extra until extra.empty? do ex = [] ex << extra.shift ex << extra.shift if /^[^-]/.match?(extra.first.to_s) result << ex if handles?(ex) end result.flatten! result.concat(args) result end def deprecated? false end private def option_is_deprecated?(option) @deprecated_options[command].key?(option) end def add_parser_description # :nodoc: return unless description formatted = description.split("\n\n").map do |chunk| wrap chunk, 80 - 4 end.join "\n" @parser.separator nil @parser.separator " Description:" formatted.each_line do |line| @parser.separator " #{line.rstrip}" end end def add_parser_options # :nodoc: @parser.separator nil regular_options = @option_groups.delete :options configure_options "", regular_options @option_groups.sort_by {|n,_| n.to_s }.each do |group_name, option_list| @parser.separator nil configure_options group_name, option_list end end ## # Adds a section with +title+ and +content+ to the parser help view. Used # for adding command arguments and default arguments. def add_parser_run_info(title, content) return if content.empty? @parser.separator nil @parser.separator " #{title}:" content.each_line do |line| @parser.separator " #{line.rstrip}" end end def add_parser_summary # :nodoc: return unless @summary @parser.separator nil @parser.separator " Summary:" wrap(@summary, 80 - 4).each_line do |line| @parser.separator " #{line.strip}" end end ## # Create on demand parser. def parser create_option_parser if @parser.nil? @parser end ## # Creates an option parser and fills it in with the help info for the # command. def create_option_parser @parser = Gem::OptionParser.new add_parser_options @parser.separator nil configure_options "Common", Gem::Command.common_options add_parser_run_info "Arguments", arguments add_parser_summary add_parser_description add_parser_run_info "Defaults", defaults_str end def configure_options(header, option_list) return if option_list.nil? || option_list.empty? header = header.to_s.empty? ? "" : "#{header} " @parser.separator " #{header}Options:" option_list.each do |args, handler| @parser.on(*args) do |value| handler.call(value, @options) end end @parser.separator "" end ## # Wraps +text+ to +width+ def wrap(text, width) # :doc: text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n") end # ---------------------------------------------------------------- # Add the options common to all commands. add_common_option("-h", "--help", "Get help on this command") do |_value, options| options[:help] = true end add_common_option("-V", "--[no-]verbose", "Set the verbose level of output") do |value, _options| # Set us to "really verbose" so the progress meter works if Gem.configuration.verbose && value Gem.configuration.verbose = 1 else Gem.configuration.verbose = value end end add_common_option("-q", "--quiet", "Silence command progress meter") do |_value, _options| Gem.configuration.verbose = false end add_common_option("--silent", "Silence RubyGems output") do |_value, options| options[:silent] = true end # Backtrace and config-file are added so they show up in the help # commands. Both options are actually handled before the other # options get parsed. add_common_option("--config-file FILE", "Use this config file instead of default") do end add_common_option("--backtrace", "Show stack backtrace on errors") do end add_common_option("--debug", "Turn on Ruby debugging") do end add_common_option("--norc", "Avoid loading any .gemrc file") do end # :stopdoc: HELP = <<-HELP RubyGems is a package manager for Ruby. Usage: gem -h/--help gem -v/--version gem [global options...] command [arguments...] [options...] Global options: -C PATH run as if gem was started in instead of the current working directory Examples: gem install rake gem list --local gem build package.gemspec gem push package-0.0.1.gem gem help install Further help: gem help commands list all 'gem' commands gem help examples show some examples of usage gem help gem_dependencies gem dependencies file guide gem help platforms gem platforms guide gem help show help on COMMAND (e.g. 'gem help install') gem server present a web page at http://localhost:8808/ with info about installed gems Further information: https://guides.rubygems.org HELP # :startdoc: end ## # \Commands will be placed in this namespace module Gem::Commands end PK!#Yrubygems/s3_uri_signer.rbnu[# frozen_string_literal: true require_relative "openssl" ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems # More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html class Gem::S3URISigner class ConfigurationError < Gem::Exception def initialize(message) super message end def to_s # :nodoc: super.to_s end end class InstanceProfileError < Gem::Exception def initialize(message) super message end def to_s # :nodoc: super.to_s end end attr_accessor :uri def initialize(uri) @uri = uri end ## # Signs S3 URI using query-params according to the reference: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html def sign(expiration = 86_400) s3_config = fetch_s3_config current_time = Time.now.utc date_time = current_time.strftime("%Y%m%dT%H%m%SZ") date = date_time[0,8] credential_info = "#{date}/#{s3_config.region}/s3/aws4_request" canonical_host = "#{uri.host}.s3.#{s3_config.region}.amazonaws.com" query_params = generate_canonical_query_params(s3_config, date_time, credential_info, expiration) canonical_request = generate_canonical_request(canonical_host, query_params) string_to_sign = generate_string_to_sign(date_time, credential_info, canonical_request) signature = generate_signature(s3_config, date, string_to_sign) Gem::URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}") end private S3Config = Struct.new :access_key_id, :secret_access_key, :security_token, :region def generate_canonical_query_params(s3_config, date_time, credential_info, expiration) canonical_params = {} canonical_params["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256" canonical_params["X-Amz-Credential"] = "#{s3_config.access_key_id}/#{credential_info}" canonical_params["X-Amz-Date"] = date_time canonical_params["X-Amz-Expires"] = expiration.to_s canonical_params["X-Amz-SignedHeaders"] = "host" canonical_params["X-Amz-Security-Token"] = s3_config.security_token if s3_config.security_token # Sorting is required to generate proper signature canonical_params.sort.to_h.map do |key, value| "#{base64_uri_escape(key)}=#{base64_uri_escape(value)}" end.join("&") end def generate_canonical_request(canonical_host, query_params) [ "GET", uri.path, query_params, "host:#{canonical_host}", "", # empty params "host", "UNSIGNED-PAYLOAD", ].join("\n") end def generate_string_to_sign(date_time, credential_info, canonical_request) [ "AWS4-HMAC-SHA256", date_time, credential_info, OpenSSL::Digest::SHA256.hexdigest(canonical_request), ].join("\n") end def generate_signature(s3_config, date, string_to_sign) date_key = OpenSSL::HMAC.digest("sha256", "AWS4" + s3_config.secret_access_key, date) date_region_key = OpenSSL::HMAC.digest("sha256", date_key, s3_config.region) date_region_service_key = OpenSSL::HMAC.digest("sha256", date_region_key, "s3") signing_key = OpenSSL::HMAC.digest("sha256", date_region_service_key, "aws4_request") OpenSSL::HMAC.hexdigest("sha256", signing_key, string_to_sign) end ## # Extracts S3 configuration for S3 bucket def fetch_s3_config return S3Config.new(uri.user, uri.password, nil, "us-east-1") if uri.user && uri.password s3_source = Gem.configuration[:s3_source] || Gem.configuration["s3_source"] host = uri.host raise ConfigurationError.new("no s3_source key exists in .gemrc") unless s3_source auth = s3_source[host] || s3_source[host.to_sym] raise ConfigurationError.new("no key for host #{host} in s3_source in .gemrc") unless auth provider = auth[:provider] || auth["provider"] case provider when "env" id = ENV["AWS_ACCESS_KEY_ID"] secret = ENV["AWS_SECRET_ACCESS_KEY"] security_token = ENV["AWS_SESSION_TOKEN"] when "instance_profile" credentials = ec2_metadata_credentials_json id = credentials["AccessKeyId"] secret = credentials["SecretAccessKey"] security_token = credentials["Token"] else id = auth[:id] || auth["id"] secret = auth[:secret] || auth["secret"] security_token = auth[:security_token] || auth["security_token"] end raise ConfigurationError.new("s3_source for #{host} missing id or secret") unless id && secret region = auth[:region] || auth["region"] || "us-east-1" S3Config.new(id, secret, security_token, region) end def base64_uri_escape(str) str.gsub(%r{[\+/=\n]}, BASE64_URI_TRANSLATE) end def ec2_metadata_credentials_json require_relative "vendored_net_http" require_relative "request" require_relative "request/connection_pools" require "json" iam_info = ec2_metadata_request(EC2_IAM_INFO) # Expected format: arn:aws:iam:::instance-profile/ role_name = iam_info["InstanceProfileArn"].split("/").last ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name) end def ec2_metadata_request(url) uri = Gem::URI(url) @request_pool ||= create_request_pool(uri) request = Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, @request_pool) response = request.fetch case response when Gem::Net::HTTPOK then JSON.parse(response.body) else raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}") end end def create_request_pool(uri) proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme)) certs = Gem::Request.get_cert_files Gem::Request::ConnectionPools.new(proxy_uri, certs).pool_for(uri) end BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info" EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" end PK!~O&rubygems/vendor/timeout/lib/timeout.rbnu[# frozen_string_literal: true # Timeout long-running blocks # # == Synopsis # # require 'rubygems/vendor/timeout/lib/timeout' # status = Gem::Timeout::timeout(5) { # # Something that should be interrupted if it takes more than 5 seconds... # } # # == Description # # Gem::Timeout provides a way to auto-terminate a potentially long-running # operation if it hasn't finished in a fixed amount of time. # # Previous versions didn't use a module for namespacing, however # #timeout is provided for backwards compatibility. You # should prefer Gem::Timeout.timeout instead. # # == Copyright # # Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc. # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Gem::Timeout VERSION = "0.4.1" # Internal error raised to when a timeout is triggered. class ExitException < Exception def exception(*) self end end # Raised by Gem::Timeout.timeout when the block times out. class Error < RuntimeError def self.handle_timeout(message) exc = ExitException.new(message) begin yield exc rescue ExitException => e raise new(message) if exc.equal?(e) raise end end end # :stopdoc: CONDVAR = ConditionVariable.new QUEUE = Queue.new QUEUE_MUTEX = Mutex.new TIMEOUT_THREAD_MUTEX = Mutex.new @timeout_thread = nil private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX class Request attr_reader :deadline def initialize(thread, timeout, exception_class, message) @thread = thread @deadline = GET_TIME.call(Process::CLOCK_MONOTONIC) + timeout @exception_class = exception_class @message = message @mutex = Mutex.new @done = false # protected by @mutex end def done? @mutex.synchronize do @done end end def expired?(now) now >= @deadline end def interrupt @mutex.synchronize do unless @done @thread.raise @exception_class, @message @done = true end end end def finished @mutex.synchronize do @done = true end end end private_constant :Request def self.create_timeout_thread watcher = Thread.new do requests = [] while true until QUEUE.empty? and !requests.empty? # wait to have at least one request req = QUEUE.pop requests << req unless req.done? end closest_deadline = requests.min_by(&:deadline).deadline now = 0.0 QUEUE_MUTEX.synchronize do while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty? CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now) end end requests.each do |req| req.interrupt if req.expired?(now) end requests.reject!(&:done?) end end ThreadGroup::Default.add(watcher) unless watcher.group.enclosed? watcher.name = "Gem::Timeout stdlib thread" watcher.thread_variable_set(:"\0__detached_thread__", true) watcher end private_class_method :create_timeout_thread def self.ensure_timeout_thread_created unless @timeout_thread and @timeout_thread.alive? TIMEOUT_THREAD_MUTEX.synchronize do unless @timeout_thread and @timeout_thread.alive? @timeout_thread = create_timeout_thread end end end end # We keep a private reference so that time mocking libraries won't break # Gem::Timeout. GET_TIME = Process.method(:clock_gettime) private_constant :GET_TIME # :startdoc: # Perform an operation in a block, raising an error if it takes longer than # +sec+ seconds to complete. # # +sec+:: Number of seconds to wait for the block to terminate. Any number # may be used, including Floats to specify fractional seconds. A # value of 0 or +nil+ will execute the block without any timeout. # +klass+:: Exception Class to raise if the block fails to terminate # in +sec+ seconds. Omitting will use the default, Gem::Timeout::Error # +message+:: Error message to raise with Exception Class. # Omitting will use the default, "execution expired" # # Returns the result of the block *if* the block completed before # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. # # The exception thrown to terminate the given block cannot be rescued inside # the block unless +klass+ is given explicitly. However, the block can use # ensure to prevent the handling of the exception. For that reason, this # method cannot be relied on to enforce timeouts for untrusted blocks. # # If a scheduler is defined, it will be used to handle the timeout by invoking # Scheduler#timeout_after. # # Note that this is both a method of module Gem::Timeout, so you can include # Gem::Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Gem::Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? message ||= "execution expired" if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after) return scheduler.timeout_after(sec, klass || Error, message, &block) end Gem::Timeout.ensure_timeout_thread_created perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) QUEUE_MUTEX.synchronize do QUEUE << request CONDVAR.signal end begin return yield(sec) ensure request.finished end end if klass perform.call(klass) else Error.handle_timeout(message, &perform) end end module_function :timeout end PK!L;;,rubygems/vendor/optparse/lib/optionparser.rbnu[# frozen_string_literal: false require_relative 'optparse' PK!#d0rubygems/vendor/optparse/lib/optparse/version.rbnu[# frozen_string_literal: false # Gem::OptionParser internal utility class << Gem::OptionParser def show_version(*pkgs) progname = ARGV.options.program_name result = false show = proc do |klass, cname, version| str = "#{progname}" unless klass == ::Object and cname == :VERSION version = version.join(".") if Array === version str << ": #{klass}" unless klass == Object str << " version #{version}" end [:Release, :RELEASE].find do |rel| if klass.const_defined?(rel) str << " (#{klass.const_get(rel)})" end end puts str result = true end if pkgs.size == 1 and pkgs[0] == "all" self.search_const(::Object, /\AV(?:ERSION|ersion)\z/) do |klass, cname, version| unless cname[1] == ?e and klass.const_defined?(:Version) show.call(klass, cname.intern, version) end end else pkgs.each do |pkg| begin pkg = pkg.split(/::|\//).inject(::Object) {|m, c| m.const_get(c)} v = case when pkg.const_defined?(:Version) pkg.const_get(n = :Version) when pkg.const_defined?(:VERSION) pkg.const_get(n = :VERSION) else n = nil "unknown" end show.call(pkg, n, v) rescue NameError end end end result end def each_const(path, base = ::Object) path.split(/::|\//).inject(base) do |klass, name| raise NameError, path unless Module === klass klass.constants.grep(/#{name}/i) do |c| klass.const_defined?(c) or next klass.const_get(c) end end end def search_const(klass, name) klasses = [klass] while klass = klasses.shift klass.constants.each do |cname| klass.const_defined?(cname) or next const = klass.const_get(cname) yield klass, cname, const if name === cname klasses << const if Module === const and const != ::Object end end end end PK!ý''/rubygems/vendor/optparse/lib/optparse/kwargs.rbnu[# frozen_string_literal: true require_relative '../optparse' class Gem::OptionParser # :call-seq: # define_by_keywords(options, method, **params) # # :include: ../../doc/optparse/creates_option.rdoc # def define_by_keywords(options, meth, **opts) meth.parameters.each do |type, name| case type when :key, :keyreq op, cl = *(type == :key ? %w"[ ]" : ["", ""]) define("--#{name}=#{op}#{name.upcase}#{cl}", *opts[name]) do |o| options[name] = o end end end options end end PK!lV,rubygems/vendor/optparse/lib/optparse/uri.rbnu[# frozen_string_literal: false # -*- ruby -*- require_relative '../optparse' require_relative '../../../uri/lib/uri' Gem::OptionParser.accept(Gem::URI) {|s,| Gem::URI.parse(s) if s} PK!"j3rubygems/vendor/optparse/lib/optparse/shellwords.rbnu[# frozen_string_literal: false # -*- ruby -*- require 'shellwords' require_relative '../optparse' Gem::OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)} PK!O-rubygems/vendor/optparse/lib/optparse/date.rbnu[# frozen_string_literal: false require_relative '../optparse' require 'date' Gem::OptionParser.accept(DateTime) do |s,| begin DateTime.parse(s) if s rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end end Gem::OptionParser.accept(Date) do |s,| begin Date.parse(s) if s rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end end PK!.U-rubygems/vendor/optparse/lib/optparse/time.rbnu[# frozen_string_literal: false require_relative '../optparse' require 'time' Gem::OptionParser.accept(Time) do |s,| begin (Time.httpdate(s) rescue Time.parse(s)) if s rescue raise Gem::OptionParser::InvalidArgument, s end end PK!X$$+rubygems/vendor/optparse/lib/optparse/ac.rbnu[# frozen_string_literal: false require_relative '../optparse' class Gem::OptionParser::AC < Gem::OptionParser private def _check_ac_args(name, block) unless /\A\w[-\w]*\z/ =~ name raise ArgumentError, name end unless block raise ArgumentError, "no block given", ParseError.filter_backtrace(caller) end end ARG_CONV = proc {|val| val.nil? ? true : val} def _ac_arg_enable(prefix, name, help_string, block) _check_ac_args(name, block) sdesc = [] ldesc = ["--#{prefix}-#{name}"] desc = [help_string] q = name.downcase ac_block = proc {|val| block.call(ARG_CONV.call(val))} enable = Switch::PlacedArgument.new(nil, ARG_CONV, sdesc, ldesc, nil, desc, ac_block) disable = Switch::NoArgument.new(nil, proc {false}, sdesc, ldesc, nil, desc, ac_block) top.append(enable, [], ["enable-" + q], disable, ['disable-' + q]) enable end public def ac_arg_enable(name, help_string, &block) _ac_arg_enable("enable", name, help_string, block) end def ac_arg_disable(name, help_string, &block) _ac_arg_enable("disable", name, help_string, block) end def ac_arg_with(name, help_string, &block) _check_ac_args(name, block) sdesc = [] ldesc = ["--with-#{name}"] desc = [help_string] q = name.downcase with = Switch::PlacedArgument.new(*search(:atype, String), sdesc, ldesc, nil, desc, block) without = Switch::NoArgument.new(nil, proc {}, sdesc, ldesc, nil, desc, block) top.append(with, [], ["with-" + q], without, ['without-' + q]) with end end PK!gydd(rubygems/vendor/optparse/lib/optparse.rbnu[# frozen_string_literal: true # # optparse.rb - command-line option analysis with the Gem::OptionParser class. # # Author:: Nobu Nakada # Documentation:: Nobu Nakada and Gavin Sinclair. # # See Gem::OptionParser for documentation. # #-- # == Developer Documentation (not for RDoc output) # # === Class tree # # - Gem::OptionParser:: front end # - Gem::OptionParser::Switch:: each switches # - Gem::OptionParser::List:: options list # - Gem::OptionParser::ParseError:: errors on parsing # - Gem::OptionParser::AmbiguousOption # - Gem::OptionParser::NeedlessArgument # - Gem::OptionParser::MissingArgument # - Gem::OptionParser::InvalidOption # - Gem::OptionParser::InvalidArgument # - Gem::OptionParser::AmbiguousArgument # # === Object relationship diagram # # +--------------+ # | Gem::OptionParser |<>-----+ # +--------------+ | +--------+ # | ,-| Switch | # on_head -------->+---------------+ / +--------+ # accept/reject -->| List |<|>- # | |<|>- +----------+ # on ------------->+---------------+ `-| argument | # : : | class | # +---------------+ |==========| # on_tail -------->| | |pattern | # +---------------+ |----------| # Gem::OptionParser.accept ->| DefaultList | |converter | # reject |(shared between| +----------+ # | all instances)| # +---------------+ # #++ # # == Gem::OptionParser # # === New to +Gem::OptionParser+? # # See the {Tutorial}[optparse/tutorial.rdoc]. # # === Introduction # # Gem::OptionParser is a class for command-line option analysis. It is much more # advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented # solution. # # === Features # # 1. The argument specification and the code to handle it are written in the # same place. # 2. It can output an option summary; you don't need to maintain this string # separately. # 3. Optional and mandatory arguments are specified very gracefully. # 4. Arguments can be automatically converted to a specified class. # 5. Arguments can be restricted to a certain set. # # All of these features are demonstrated in the examples below. See # #make_switch for full documentation. # # === Minimal example # # require 'rubygems/vendor/optparse/lib/optparse' # # options = {} # Gem::OptionParser.new do |parser| # parser.banner = "Usage: example.rb [options]" # # parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| # options[:verbose] = v # end # end.parse! # # p options # p ARGV # # === Generating Help # # Gem::OptionParser can be used to automatically generate help for the commands you # write: # # require 'rubygems/vendor/optparse/lib/optparse' # # Options = Struct.new(:name) # # class Parser # def self.parse(options) # args = Options.new("world") # # opt_parser = Gem::OptionParser.new do |parser| # parser.banner = "Usage: example.rb [options]" # # parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| # args.name = n # end # # parser.on("-h", "--help", "Prints this help") do # puts parser # exit # end # end # # opt_parser.parse!(options) # return args # end # end # options = Parser.parse %w[--help] # # #=> # # Usage: example.rb [options] # # -n, --name=NAME Name to say hello to # # -h, --help Prints this help # # === Required Arguments # # For options that require an argument, option specification strings may include an # option name in all caps. If an option is used without the required argument, # an exception will be raised. # # require 'rubygems/vendor/optparse/lib/optparse' # # options = {} # Gem::OptionParser.new do |parser| # parser.on("-r", "--require LIBRARY", # "Require the LIBRARY before executing your script") do |lib| # puts "You required #{lib}!" # end # end.parse! # # Used: # # $ ruby optparse-test.rb -r # optparse-test.rb:9:in `
': missing argument: -r (Gem::OptionParser::MissingArgument) # $ ruby optparse-test.rb -r my-library # You required my-library! # # === Type Coercion # # Gem::OptionParser supports the ability to coerce command line arguments # into objects for us. # # Gem::OptionParser comes with a few ready-to-use kinds of type # coercion. They are: # # - Date -- Anything accepted by +Date.parse+ (need to require +optparse/date+) # - DateTime -- Anything accepted by +DateTime.parse+ (need to require +optparse/date+) # - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+ (need to require +optparse/time+) # - URI -- Anything accepted by +Gem::URI.parse+ (need to require +optparse/uri+) # - Shellwords -- Anything accepted by +Shellwords.shellwords+ (need to require +optparse/shellwords+) # - String -- Any non-empty string # - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040) # - Float -- Any float. (e.g. 10, 3.14, -100E+13) # - Numeric -- Any integer, float, or rational (1, 3.4, 1/3) # - DecimalInteger -- Like +Integer+, but no octal format. # - OctalInteger -- Like +Integer+, but no decimal format. # - DecimalNumeric -- Decimal integer or float. # - TrueClass -- Accepts '+, yes, true, -, no, false' and # defaults as +true+ # - FalseClass -- Same as +TrueClass+, but defaults to +false+ # - Array -- Strings separated by ',' (e.g. 1,2,3) # - Regexp -- Regular expressions. Also includes options. # # We can also add our own coercions, which we will cover below. # # ==== Using Built-in Conversions # # As an example, the built-in +Time+ conversion is used. The other built-in # conversions behave in the same way. # Gem::OptionParser will attempt to parse the argument # as a +Time+. If it succeeds, that time will be passed to the # handler block. Otherwise, an exception will be raised. # # require 'rubygems/vendor/optparse/lib/optparse' # require 'rubygems/vendor/optparse/lib/optparse/time' # Gem::OptionParser.new do |parser| # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| # p time # end # end.parse! # # Used: # # $ ruby optparse-test.rb -t nonsense # ... invalid argument: -t nonsense (Gem::OptionParser::InvalidArgument) # $ ruby optparse-test.rb -t 10-11-12 # 2010-11-12 00:00:00 -0500 # $ ruby optparse-test.rb -t 9:30 # 2014-08-13 09:30:00 -0400 # # ==== Creating Custom Conversions # # The +accept+ method on Gem::OptionParser may be used to create converters. # It specifies which conversion block to call whenever a class is specified. # The example below uses it to fetch a +User+ object before the +on+ handler receives it. # # require 'rubygems/vendor/optparse/lib/optparse' # # User = Struct.new(:id, :name) # # def find_user id # not_found = ->{ raise "No User Found for id #{id}" } # [ User.new(1, "Sam"), # User.new(2, "Gandalf") ].find(not_found) do |u| # u.id == id # end # end # # op = Gem::OptionParser.new # op.accept(User) do |user_id| # find_user user_id.to_i # end # # op.on("--user ID", User) do |user| # puts user # end # # op.parse! # # Used: # # $ ruby optparse-test.rb --user 1 # # # $ ruby optparse-test.rb --user 2 # # # $ ruby optparse-test.rb --user 3 # optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) # # === Store options to a Hash # # The +into+ option of +order+, +parse+ and so on methods stores command line options into a Hash. # # require 'rubygems/vendor/optparse/lib/optparse' # # options = {} # Gem::OptionParser.new do |parser| # parser.on('-a') # parser.on('-b NUM', Integer) # parser.on('-v', '--verbose') # end.parse!(into: options) # # p options # # Used: # # $ ruby optparse-test.rb -a # {:a=>true} # $ ruby optparse-test.rb -a -v # {:a=>true, :verbose=>true} # $ ruby optparse-test.rb -a -b 100 # {:a=>true, :b=>100} # # === Complete example # # The following example is a complete Ruby program. You can run it and see the # effect of specifying various options. This is probably the best way to learn # the features of +optparse+. # # require 'rubygems/vendor/optparse/lib/optparse' # require 'rubygems/vendor/optparse/lib/optparse/time' # require 'ostruct' # require 'pp' # # class OptparseExample # Version = '1.0.0' # # CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] # CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } # # class ScriptOptions # attr_accessor :library, :inplace, :encoding, :transfer_type, # :verbose, :extension, :delay, :time, :record_separator, # :list # # def initialize # self.library = [] # self.inplace = false # self.encoding = "utf8" # self.transfer_type = :auto # self.verbose = false # end # # def define_options(parser) # parser.banner = "Usage: example.rb [options]" # parser.separator "" # parser.separator "Specific options:" # # # add additional options # perform_inplace_option(parser) # delay_execution_option(parser) # execute_at_time_option(parser) # specify_record_separator_option(parser) # list_example_option(parser) # specify_encoding_option(parser) # optional_option_argument_with_keyword_completion_option(parser) # boolean_verbose_option(parser) # # parser.separator "" # parser.separator "Common options:" # # No argument, shows at tail. This will print an options summary. # # Try it and see! # parser.on_tail("-h", "--help", "Show this message") do # puts parser # exit # end # # Another typical switch to print the version. # parser.on_tail("--version", "Show version") do # puts Version # exit # end # end # # def perform_inplace_option(parser) # # Specifies an optional option argument # parser.on("-i", "--inplace [EXTENSION]", # "Edit ARGV files in place", # "(make backup if EXTENSION supplied)") do |ext| # self.inplace = true # self.extension = ext || '' # self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot. # end # end # # def delay_execution_option(parser) # # Cast 'delay' argument to a Float. # parser.on("--delay N", Float, "Delay N seconds before executing") do |n| # self.delay = n # end # end # # def execute_at_time_option(parser) # # Cast 'time' argument to a Time object. # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| # self.time = time # end # end # # def specify_record_separator_option(parser) # # Cast to octal integer. # parser.on("-F", "--irs [OCTAL]", Gem::OptionParser::OctalInteger, # "Specify record separator (default \\0)") do |rs| # self.record_separator = rs # end # end # # def list_example_option(parser) # # List of arguments. # parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| # self.list = list # end # end # # def specify_encoding_option(parser) # # Keyword completion. We are specifying a specific set of arguments (CODES # # and CODE_ALIASES - notice the latter is a Hash), and the user may provide # # the shortest unambiguous text. # code_list = (CODE_ALIASES.keys + CODES).join(', ') # parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding", # "(#{code_list})") do |encoding| # self.encoding = encoding # end # end # # def optional_option_argument_with_keyword_completion_option(parser) # # Optional '--type' option argument with keyword completion. # parser.on("--type [TYPE]", [:text, :binary, :auto], # "Select transfer type (text, binary, auto)") do |t| # self.transfer_type = t # end # end # # def boolean_verbose_option(parser) # # Boolean switch. # parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| # self.verbose = v # end # end # end # # # # # Return a structure describing the options. # # # def parse(args) # # The options specified on the command line will be collected in # # *options*. # # @options = ScriptOptions.new # @args = Gem::OptionParser.new do |parser| # @options.define_options(parser) # parser.parse!(args) # end # @options # end # # attr_reader :parser, :options # end # class OptparseExample # # example = OptparseExample.new # options = example.parse(ARGV) # pp options # example.options # pp ARGV # # === Shell Completion # # For modern shells (e.g. bash, zsh, etc.), you can use shell # completion for command line options. # # === Further documentation # # The above examples, along with the accompanying # {Tutorial}[optparse/tutorial.rdoc], # should be enough to learn how to use this class. # If you have any questions, file a ticket at http://bugs.ruby-lang.org. # class Gem::OptionParser Gem::OptionParser::Version = "0.4.0" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze # :startdoc: # # Keyword completion module. This allows partial arguments to be specified # and resolved against a list of acceptable values. # module Completion def self.regexp(key, icase) Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) end def self.candidate(key, icase = false, pat = nil, &block) pat ||= Completion.regexp(key, icase) candidates = [] block.call do |k, *v| (if Regexp === k kn = "" k === key else kn = defined?(k.id2name) ? k.id2name : k pat === kn end) or next v << k if v.empty? candidates << [k, v, kn] end candidates end def candidate(key, icase = false, pat = nil) Completion.candidate(key, icase, pat, &method(:each)) end public def complete(key, icase = false, pat = nil) candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} if candidates.size == 1 canon, sw, * = candidates[0] elsif candidates.size > 1 canon, sw, cn = candidates.shift candidates.each do |k, v, kn| next if sw == v if String === cn and String === kn if cn.rindex(kn, 0) canon, sw, cn = k, v, kn next elsif kn.rindex(cn, 0) next end end throw :ambiguous, key end end if canon block_given? or return key, *sw yield(key, *sw) end end def convert(opt = nil, val = nil, *) val end end # # Map from option/keyword string to object with completion. # class OptionMap < Hash include Completion end # # Individual switch class. Not important to the user. # # Defined within Switch are several Switch-derived classes: NoArgument, # RequiredArgument, etc. # class Switch attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block # # Guesses argument style from +arg+. Returns corresponding # Gem::OptionParser::Switch class (OptionalArgument, etc.). # def self.guess(arg) case arg when "" t = self when /\A=?\[/ t = Switch::OptionalArgument when /\A\s+\[/ t = Switch::PlacedArgument else t = Switch::RequiredArgument end self >= t or incompatible_argument_styles(arg, t) t end def self.incompatible_argument_styles(arg, t) raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}", ParseError.filter_backtrace(caller(2))) end def self.pattern NilClass end def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, desc = ([] if short or long), block = nil, &_block) raise if Array === pattern block ||= _block @pattern, @conv, @short, @long, @arg, @desc, @block = pattern, conv, short, long, arg, desc, block end # # Parses +arg+ and returns rest of +arg+ and matched portion to the # argument pattern. Yields when the pattern doesn't match substring. # def parse_arg(arg) # :nodoc: pattern or return nil, [arg] unless m = pattern.match(arg) yield(InvalidArgument, arg) return arg, [] end if String === m m = [s = m] else m = m.to_a s = m[0] return nil, m unless String === s end raise InvalidArgument, arg unless arg.rindex(s, 0) return nil, m if s.length == arg.length yield(InvalidArgument, arg) # didn't match whole arg return arg[s.length..-1], m end private :parse_arg # # Parses argument, converts and returns +arg+, +block+ and result of # conversion. Yields at semi-error condition instead of raising an # exception. # def conv_arg(arg, val = []) # :nodoc: if conv val = conv.call(*val) else val = proc {|v| v}.call(*val) end return arg, block, val end private :conv_arg # # Produces the summary text. Each line of the summary is yielded to the # block (without newline). # # +sdone+:: Already summarized short style options keyed hash. # +ldone+:: Already summarized long style options keyed hash. # +width+:: Width of left side (option part). In other words, the right # side (description part) starts after +width+ columns. # +max+:: Maximum width of left side -> the options are filled within # +max+ columns. # +indent+:: Prefix string indents all summarized lines. # def summarize(sdone = {}, ldone = {}, width = 1, max = width - 1, indent = "") sopts, lopts = [], [], nil @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long return if sopts.empty? and lopts.empty? # completely hidden left = [sopts.join(', ')] right = desc.dup while s = lopts.shift l = left[-1].length + s.length l += arg.length if left.size == 1 && arg l < max or sopts.empty? or left << +'' left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s end if arg left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg) end mlen = left.collect {|ss| ss.length}.max.to_i while mlen > width and l = left.shift mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen if l.length < width and (r = right[0]) and !r.empty? l = l.to_s.ljust(width) + ' ' + r right.shift end yield(indent + l) end while begin l = left.shift; r = right.shift; l or r end l = l.to_s.ljust(width) + ' ' + r if r and !r.empty? yield(indent + l) end self end def add_banner(to) # :nodoc: unless @short or @long s = desc.join to << " [" + s + "]..." unless s.empty? end to end def match_nonswitch?(str) # :nodoc: @pattern =~ str unless @short or @long end # # Main name of the switch. # def switch_name (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '') end def compsys(sdone, ldone) # :nodoc: sopts, lopts = [], [] @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long return if sopts.empty? and lopts.empty? # completely hidden (sopts+lopts).each do |opt| # "(-x -c -r)-l[left justify]" if /^--\[no-\](.+)$/ =~ opt o = $1 yield("--#{o}", desc.join("")) yield("--no-#{o}", desc.join("")) else yield("#{opt}", desc.join("")) end end end def pretty_print_contents(q) # :nodoc: if @block q.text ":" + @block.source_location.join(":") + ":" first = false else first = true end [@short, @long].each do |list| list.each do |opt| if first q.text ":" first = false end q.breakable q.text opt end end end def pretty_print(q) # :nodoc: q.object_group(self) {pretty_print_contents(q)} end # # Switch that takes no arguments. # class NoArgument < self # # Raises an exception if any arguments given. # def parse(arg, argv) yield(NeedlessArgument, arg) if arg conv_arg(arg) end def self.incompatible_argument_styles(*) end def self.pattern Object end def pretty_head # :nodoc: "NoArgument" end end # # Switch that takes an argument. # class RequiredArgument < self # # Raises an exception if argument is not present. # def parse(arg, argv) unless arg raise MissingArgument if argv.empty? arg = argv.shift end conv_arg(*parse_arg(arg, &method(:raise))) end def pretty_head # :nodoc: "Required" end end # # Switch that can omit argument. # class OptionalArgument < self # # Parses argument if given, or uses default value. # def parse(arg, argv, &error) if arg conv_arg(*parse_arg(arg, &error)) else conv_arg(arg) end end def pretty_head # :nodoc: "Optional" end end # # Switch that takes an argument, which does not begin with '-' or is '-'. # class PlacedArgument < self # # Returns nil if argument is not present or begins with '-' and is not '-'. # def parse(arg, argv, &error) if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0])) return nil, block, nil end opt = (val = parse_arg(val, &error))[1] val = conv_arg(*val) if opt and !arg argv.shift else val[0] = nil end val end def pretty_head # :nodoc: "Placed" end end end # # Simple option list providing mapping from short and/or long option # string to Gem::OptionParser::Switch and mapping from acceptable argument to # matching pattern and converter pair. Also provides summary feature. # class List # Map from acceptable argument types to pattern and converter pairs. attr_reader :atype # Map from short style option switches to actual switch objects. attr_reader :short # Map from long style option switches to actual switch objects. attr_reader :long # List of all switches and summary string. attr_reader :list # # Just initializes all instance variables. # def initialize @atype = {} @short = OptionMap.new @long = OptionMap.new @list = [] end def pretty_print(q) # :nodoc: q.group(1, "(", ")") do @list.each do |sw| next unless Switch === sw q.group(1, "(" + sw.pretty_head, ")") do sw.pretty_print_contents(q) end end end end # # See Gem::OptionParser.accept. # def accept(t, pat = /.*/m, &block) if pat pat.respond_to?(:match) or raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2)) else pat = t if t.respond_to?(:match) end unless block block = pat.method(:convert).to_proc if pat.respond_to?(:convert) end @atype[t] = [pat, block] end # # See Gem::OptionParser.reject. # def reject(t) @atype.delete(t) end # # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+. # # +sw+:: Gem::OptionParser::Switch instance to be added. # +sopts+:: Short style option list. # +lopts+:: Long style option list. # +nlopts+:: Negated long style options list. # def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: sopts.each {|o| @short[o] = sw} if sopts lopts.each {|o| @long[o] = sw} if lopts nlopts.each {|o| @long[o] = nsw} if nsw and nlopts used = @short.invert.update(@long.invert) @list.delete_if {|o| Switch === o and !used[o]} end private :update # # Inserts +switch+ at the head of the list, and associates short, long # and negated long options. Arguments are: # # +switch+:: Gem::OptionParser::Switch instance to be inserted. # +short_opts+:: List of short style options. # +long_opts+:: List of long style options. # +nolong_opts+:: List of long style options with "no-" prefix. # # prepend(switch, short_opts, long_opts, nolong_opts) # def prepend(*args) update(*args) @list.unshift(args[0]) end # # Appends +switch+ at the tail of the list, and associates short, long # and negated long options. Arguments are: # # +switch+:: Gem::OptionParser::Switch instance to be inserted. # +short_opts+:: List of short style options. # +long_opts+:: List of long style options. # +nolong_opts+:: List of long style options with "no-" prefix. # # append(switch, short_opts, long_opts, nolong_opts) # def append(*args) update(*args) @list.push(args[0]) end # # Searches +key+ in +id+ list. The result is returned or yielded if a # block is given. If it isn't found, nil is returned. # def search(id, key) if list = __send__(id) val = list.fetch(key) {return nil} block_given? ? yield(val) : val end end # # Searches list +id+ for +opt+ and the optional patterns for completion # +pat+. If +icase+ is true, the search is case insensitive. The result # is returned or yielded if a block is given. If it isn't found, nil is # returned. # def complete(id, opt, icase = false, *pat, &block) __send__(id).complete(opt, icase, *pat, &block) end def get_candidates(id) yield __send__(id).keys end # # Iterates over each option, passing the option to the +block+. # def each_option(&block) list.each(&block) end # # Creates the summary table, passing each line to the +block+ (without # newline). The arguments +args+ are passed along to the summarize # method which is called on every option. # def summarize(*args, &block) sum = [] list.reverse_each do |opt| if opt.respond_to?(:summarize) # perhaps Gem::OptionParser::Switch s = [] opt.summarize(*args) {|l| s << l} sum.concat(s.reverse) elsif !opt or opt.empty? sum << "" elsif opt.respond_to?(:each_line) sum.concat([*opt.each_line].reverse) else sum.concat([*opt.each].reverse) end end sum.reverse_each(&block) end def add_banner(to) # :nodoc: list.each do |opt| if opt.respond_to?(:add_banner) opt.add_banner(to) end end to end def compsys(*args, &block) # :nodoc: list.each do |opt| if opt.respond_to?(:compsys) opt.compsys(*args, &block) end end end end # # Hash with completion search feature. See Gem::OptionParser::Completion. # class CompletingHash < Hash include Completion # # Completion for hash key. # def match(key) *values = fetch(key) { raise AmbiguousArgument, catch(:ambiguous) {return complete(key)} } return key, *values end end # :stopdoc: # # Enumeration of acceptable argument styles. Possible values are: # # NO_ARGUMENT:: The switch takes no arguments. (:NONE) # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED) # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL) # # Use like --switch=argument (long style) or -Xargument (short style). For # short style, only portion matched to argument pattern is treated as # argument. # ArgumentStyle = {} NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument} RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument} OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument} ArgumentStyle.freeze # # Switches common used such as '--', and also provides default # argument classes # DefaultList = List.new DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} COMPSYS_HEADER = <<'XXX' # :nodoc: typeset -A opt_args local context state line _arguments -s -S \ XXX def compsys(to, name = File.basename($0)) # :nodoc: to << "#compdef #{name}\n" to << COMPSYS_HEADER visit(:compsys, {}, {}) {|o, d| to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n] } to << " '*:file:_files' && return 0\n" end # # Default options for ARGV, which never appear in option summary. # Officious = {} # # --help # Shows option summary. # Officious['help'] = proc do |parser| Switch::NoArgument.new do |arg| puts parser.help exit end end # # --*-completion-bash=WORD # Shows candidates for command line completion. # Officious['*-completion-bash'] = proc do |parser| Switch::RequiredArgument.new do |arg| puts parser.candidate(arg) exit end end # # --*-completion-zsh[=NAME:FILE] # Creates zsh completion file. # Officious['*-completion-zsh'] = proc do |parser| Switch::OptionalArgument.new do |arg| parser.compsys(STDOUT, arg) exit end end # # --version # Shows version string if Version is defined. # Officious['version'] = proc do |parser| Switch::OptionalArgument.new do |pkg| if pkg begin require_relative 'optparse/version' rescue LoadError else show_version(*pkg.split(/,/)) or abort("#{parser.program_name}: no version found in package #{pkg}") exit end end v = parser.ver or abort("#{parser.program_name}: version unknown") puts v exit end end # :startdoc: # # Class methods # # # Initializes a new instance and evaluates the optional block in context # of the instance. Arguments +args+ are passed to #new, see there for # description of parameters. # # This method is *deprecated*, its behavior corresponds to the older #new # method. # def self.with(*args, &block) opts = new(*args) opts.instance_eval(&block) opts end # # Returns an incremented value of +default+ according to +arg+. # def self.inc(arg, default = nil) case arg when Integer arg.nonzero? when nil default.to_i + 1 end end def inc(*args) self.class.inc(*args) end # # Initializes the instance and yields itself if called with a block. # # +banner+:: Banner message. # +width+:: Summary width. # +indent+:: Summary indent. # def initialize(banner = nil, width = 32, indent = ' ' * 4) @stack = [DefaultList, List.new, List.new] @program_name = nil @banner = banner @summary_width = width @summary_indent = indent @default_argv = ARGV @require_exact = false @raise_unknown = true add_officious yield self if block_given? end def add_officious # :nodoc: list = base() Officious.each do |opt, block| list.long[opt] ||= block.call(self) end end # # Terminates option parsing. Optional parameter +arg+ is a string pushed # back to be the first non-option argument. # def terminate(arg = nil) self.class.terminate(arg) end def self.terminate(arg = nil) throw :terminate, arg end @stack = [DefaultList] def self.top() DefaultList end # # Directs to accept specified class +t+. The argument string is passed to # the block in which it should be converted to the desired class. # # +t+:: Argument class specifier, any object including Class. # +pat+:: Pattern for argument, defaults to +t+ if it responds to match. # # accept(t, pat, &block) # def accept(*args, &blk) top.accept(*args, &blk) end # # See #accept. # def self.accept(*args, &blk) top.accept(*args, &blk) end # # Directs to reject specified class argument. # # +t+:: Argument class specifier, any object including Class. # # reject(t) # def reject(*args, &blk) top.reject(*args, &blk) end # # See #reject. # def self.reject(*args, &blk) top.reject(*args, &blk) end # # Instance methods # # Heading banner preceding summary. attr_writer :banner # Program name to be emitted in error message and default banner, # defaults to $0. attr_writer :program_name # Width for option list portion of summary. Must be Numeric. attr_accessor :summary_width # Indentation for summary. Must be String (or have + String method). attr_accessor :summary_indent # Strings to be parsed in default. attr_accessor :default_argv # Whether to require that options match exactly (disallows providing # abbreviated long option as short option). attr_accessor :require_exact # Whether to raise at unknown option. attr_accessor :raise_unknown # # Heading banner preceding summary. # def banner unless @banner @banner = +"Usage: #{program_name} [options]" visit(:add_banner, @banner) end @banner end # # Program name to be emitted in error message and default banner, defaults # to $0. # def program_name @program_name || File.basename($0, '.*') end # for experimental cascading :-) alias set_banner banner= alias set_program_name program_name= alias set_summary_width summary_width= alias set_summary_indent summary_indent= # Version attr_writer :version # Release code attr_writer :release # # Version # def version (defined?(@version) && @version) || (defined?(::Version) && ::Version) end # # Release code # def release (defined?(@release) && @release) || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE) end # # Returns version string from program_name, version and release. # def ver if v = version str = +"#{program_name} #{[v].join('.')}" str << " (#{v})" if v = release str end end def warn(mesg = $!) super("#{program_name}: #{mesg}") end def abort(mesg = $!) super("#{program_name}: #{mesg}") end # # Subject of #on / #on_head, #accept / #reject # def top @stack[-1] end # # Subject of #on_tail. # def base @stack[1] end # # Pushes a new List. # def new @stack.push(List.new) if block_given? yield self else self end end # # Removes the last List. # def remove @stack.pop end # # Puts option summary into +to+ and returns +to+. Yields each line if # a block is given. # # +to+:: Output destination, which must have method <<. Defaults to []. # +width+:: Width of left side, defaults to @summary_width. # +max+:: Maximum length allowed for left side, defaults to +width+ - 1. # +indent+:: Indentation, defaults to @summary_indent. # def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) nl = "\n" blk ||= proc {|l| to << (l.index(nl, -1) ? l : l + nl)} visit(:summarize, {}, {}, width, max, indent, &blk) to end # # Returns option summary string. # def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end alias to_s help def pretty_print(q) # :nodoc: q.object_group(self) do first = true if @stack.size > 2 @stack.each_with_index do |s, i| next if i < 2 next if s.list.empty? if first first = false q.text ":" end q.breakable s.pretty_print(q) end end end end def inspect # :nodoc: require 'pp' pretty_print_inspect end # # Returns option summary list. # def to_a; summarize("#{banner}".split(/^/)) end # # Checks if an argument is given twice, in which case an ArgumentError is # raised. Called from Gem::OptionParser#switch only. # # +obj+:: New argument. # +prv+:: Previously specified argument. # +msg+:: Exception message. # def notwice(obj, prv, msg) # :nodoc: unless !prv or prv == obj raise(ArgumentError, "argument #{msg} given twice: #{obj}", ParseError.filter_backtrace(caller(2))) end obj end private :notwice SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: # :call-seq: # make_switch(params, block = nil) # # :include: ../doc/optparse/creates_option.rdoc # def make_switch(opts, block = nil) short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], [] ldesc, sdesc, desc, arg = [], [], [] default_style = Switch::NoArgument default_pattern = nil klass = nil q, a = nil has_arg = false opts.each do |o| # argument class next if search(:atype, o) do |pat, c| klass = notwice(o, klass, 'type') if not_style and not_style != Switch::NoArgument not_pattern, not_conv = pat, c else default_pattern, conv = pat, c end end # directly specified pattern(any object possible to match) if (!(String === o || Symbol === o)) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc else conv = SPLAT_PROC end next end # anything others case o when Proc, Method block = notwice(o, block, 'block') when Array, Hash case pattern when CompletingHash when nil pattern = CompletingHash.new conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert) else raise ArgumentError, "argument pattern given twice" end o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') when /^--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style not_style = (not_style || default_style).guess(arg = a) if a default_style = Switch::NoArgument default_pattern, conv = search(:atype, FalseClass) unless default_pattern ldesc << "--no-#{q}" (q = q.downcase).tr!('_', '-') long << "no-#{q}" nolong << q when /^--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--[no-]#{q}" (o = q.downcase).tr!('_', '-') long << o not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << "no-#{o}" when /^--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--#{q}" (o = q.downcase).tr!('_', '-') long << o when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern else has_arg = true end sdesc << "-#{q}" short << Regexp.new(q) when /^-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end sdesc << "-#{q}" short << q when /^=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else desc.push(o) if o && !o.empty? end end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern if !(short.empty? and long.empty?) if has_arg and default_style == Switch::NoArgument default_style = Switch::RequiredArgument end s = (style || default_style).new(pattern || default_pattern, conv, sdesc, ldesc, arg, desc, block) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) end s = desc else short << pattern s = (style || default_style).new(pattern, conv, nil, nil, arg, desc, block) end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), nolong end # :call-seq: # define(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def define(*opts, &block) top.append(*(sw = make_switch(opts, block))) sw[0] end # :call-seq: # on(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def on(*opts, &block) define(*opts, &block) self end alias def_option define # :call-seq: # define_head(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def define_head(*opts, &block) top.prepend(*(sw = make_switch(opts, block))) sw[0] end # :call-seq: # on_head(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # # The new option is added at the head of the summary. # def on_head(*opts, &block) define_head(*opts, &block) self end alias def_head_option define_head # :call-seq: # define_tail(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def define_tail(*opts, &block) base.append(*(sw = make_switch(opts, block))) sw[0] end # # :call-seq: # on_tail(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # # The new option is added at the tail of the summary. # def on_tail(*opts, &block) define_tail(*opts, &block) self end alias def_tail_option define_tail # # Add separator in summary. # def separator(string) top.append(string, nil, nil) end # # Parses command line arguments +argv+ in order. When a block is given, # each non-option argument is yielded. When optional +into+ keyword # argument is provided, the parsed option values are stored there via # []= method (so it can be Hash, or OpenStruct, or other # similar object). # # Returns the rest of +argv+ left unparsed. # def order(*argv, into: nil, &nonopt) argv = argv[0].dup if argv.size == 1 and Array === argv[0] order!(argv, into: into, &nonopt) end # # Same as #order, but removes switches destructively. # Non-option arguments remain in +argv+. # def order!(argv = default_argv, into: nil, &nonopt) setter = ->(name, val) {into[name.to_sym] = val} if into parse_in_order(argv, setter, &nonopt) end def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: opt, arg, val, rest = nil nonopt ||= proc {|a| throw :terminate, a} argv.unshift(arg) if arg = catch(:terminate) { while arg = argv.shift case arg # long option when /\A--([^=]*)(?:=(.*))?/m opt, rest = $1, $2 opt.tr!('_', '-') begin sw, = complete(:long, opt, true) if require_exact && !sw.long.include?(arg) throw :terminate, arg unless raise_unknown raise InvalidOption, arg end rescue ParseError throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) end begin opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} val = cb.call(val) if cb setter.call(sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, rest) end # short option when /\A-(.)((=).*|.+)?/m eq, rest, opt = $3, $2, $1 has_arg, val = eq, rest begin sw, = search(:short, opt) unless sw begin sw, = complete(:short, opt) # short option matched. val = arg.delete_prefix('-') has_arg = true rescue InvalidOption raise if require_exact # if no short options match, try completion with long # options. sw, = complete(:long, opt) eq ||= !rest end end rescue ParseError throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) end begin opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} rescue ParseError raise $!.set_option(arg, arg.length > 2) else raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" end begin argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') val = cb.call(val) if cb setter.call(sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, arg.length > 2) end # non-option argument else catch(:prune) do visit(:each_option) do |sw0| sw = sw0 sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg) end nonopt.call(arg) end end end nil } visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern} argv end private :parse_in_order # # Parses command line arguments +argv+ in permutation mode and returns # list of non-option arguments. When optional +into+ keyword # argument is provided, the parsed option values are stored there via # []= method (so it can be Hash, or OpenStruct, or other # similar object). # def permute(*argv, into: nil) argv = argv[0].dup if argv.size == 1 and Array === argv[0] permute!(argv, into: into) end # # Same as #permute, but removes switches destructively. # Non-option arguments remain in +argv+. # def permute!(argv = default_argv, into: nil) nonopts = [] order!(argv, into: into, &nonopts.method(:<<)) argv[0, 0] = nonopts argv end # # Parses command line arguments +argv+ in order when environment variable # POSIXLY_CORRECT is set, and in permutation mode otherwise. # When optional +into+ keyword argument is provided, the parsed option # values are stored there via []= method (so it can be Hash, # or OpenStruct, or other similar object). # def parse(*argv, into: nil) argv = argv[0].dup if argv.size == 1 and Array === argv[0] parse!(argv, into: into) end # # Same as #parse, but removes switches destructively. # Non-option arguments remain in +argv+. # def parse!(argv = default_argv, into: nil) if ENV.include?('POSIXLY_CORRECT') order!(argv, into: into) else permute!(argv, into: into) end end # # Wrapper method for getopts.rb. # # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option") # # params["a"] = true # -a # # params["b"] = "1" # -b1 # # params["foo"] = "1" # --foo # # params["bar"] = "x" # --bar x # # params["zot"] = "z" # --zot Z # # Option +symbolize_names+ (boolean) specifies whether returned Hash keys should be Symbols; defaults to +false+ (use Strings). # # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option", symbolize_names: true) # # params[:a] = true # -a # # params[:b] = "1" # -b1 # # params[:foo] = "1" # --foo # # params[:bar] = "x" # --bar x # # params[:zot] = "z" # --zot Z # def getopts(*args, symbolize_names: false) argv = Array === args.first ? args.shift : default_argv single_options, *long_options = *args result = {} single_options.scan(/(.)(:)?/) do |opt, val| if val result[opt] = nil define("-#{opt} VAL") else result[opt] = false define("-#{opt}") end end if single_options long_options.each do |arg| arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val result[opt] = val.empty? ? nil : val define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else result[opt] = false define("--#{opt}", *[desc].compact) end end parse_in_order(argv, result.method(:[]=)) symbolize_names ? result.transform_keys(&:to_sym) : result end # # See #getopts. # def self.getopts(*args, symbolize_names: false) new.getopts(*args, symbolize_names: symbolize_names) end # # Traverses @stack, sending each element method +id+ with +args+ and # +block+. # def visit(id, *args, &block) # :nodoc: @stack.reverse_each do |el| el.__send__(id, *args, &block) end nil end private :visit # # Searches +key+ in @stack for +id+ hash and returns or yields the result. # def search(id, key) # :nodoc: block_given = block_given? visit(:search, id, key) do |k| return block_given ? yield(k) : k end end private :search # # Completes shortened long style option switch and returns pair of # canonical switch and switch descriptor Gem::OptionParser::Switch. # # +typ+:: Searching table. # +opt+:: Searching key. # +icase+:: Search case insensitive if true. # +pat+:: Optional pattern for completion. # def complete(typ, opt, icase = false, *pat) # :nodoc: if pat.empty? search(typ, opt) {|sw| return [sw, opt]} # exact match or... end ambiguous = catch(:ambiguous) { visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) end private :complete # # Returns additional info. # def additional_message(typ, opt) return unless typ and opt and defined?(DidYouMean::SpellChecker) all_candidates = [] visit(:get_candidates, typ) do |candidates| all_candidates.concat(candidates) end all_candidates.select! {|cand| cand.is_a?(String) } checker = DidYouMean::SpellChecker.new(dictionary: all_candidates) DidYouMean.formatter.message_for(all_candidates & checker.correct(opt)) end def candidate(word) list = [] case word when '-' long = short = true when /\A--/ word, arg = word.split(/=/, 2) argpat = Completion.regexp(arg, false) if arg and !arg.empty? long = true when /\A-/ short = true end pat = Completion.regexp(word, long) visit(:each_option) do |opt| next unless Switch === opt opts = (long ? opt.long : []) + (short ? opt.short : []) opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat if /\A=/ =~ opt.arg opts.map! {|sw| sw + "="} if arg and CompletingHash === opt.pattern if opts = opt.pattern.candidate(arg, false, argpat) opts.map!(&:last) end end end list.concat(opts) end list end # # Loads options from file names as +filename+. Does nothing when the file # is not present. Returns whether successfully loaded. # # +filename+ defaults to basename of the program without suffix in a # directory ~/.options, then the basename with '.options' suffix # under XDG and Haiku standard places. # # The optional +into+ keyword argument works exactly like that accepted in # method #parse. # def load(filename = nil, into: nil) unless filename basename = File.basename($0, '.*') return true if load(File.expand_path(basename, '~/.options'), into: into) rescue nil basename << ".options" return [ # XDG ENV['XDG_CONFIG_HOME'], '~/.config', *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), # Haiku '~/config/settings', ].any? {|dir| next if !dir or dir.empty? load(File.expand_path(basename, dir), into: into) rescue nil } end begin parse(*File.readlines(filename, chomp: true), into: into) true rescue Errno::ENOENT, Errno::ENOTDIR false end end # # Parses environment variable +env+ or its uppercase with splitting like a # shell. # # +env+ defaults to the basename of the program. # def environment(env = File.basename($0, '.*')) env = ENV[env] || ENV[env.upcase] or return require 'shellwords' parse(*Shellwords.shellwords(env)) end # # Acceptable argument classes # # # Any string and no conversion. This is fall-back. # accept(Object) {|s,|s or s.nil?} accept(NilClass) {|s,|s} # # Any non-empty string, and no conversion. # accept(String, /.+/m) {|s,*|s} # # Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal # for 0x, and decimal for others; with optional sign prefix. Converts to # Integer. # decimal = '\d+(?:_\d+)*' binary = 'b[01]+(?:_[01]+)*' hex = 'x[\da-f]+(?:_[\da-f]+)*' octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?" integer = "#{octal}|#{decimal}" accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,| begin Integer(s) rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Float number format, and converts to Float. # float = "(?:#{decimal}(?=(.)?)(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?" floatpat = %r"\A[-+]?#{float}\z"io accept(Float, floatpat) {|s,| s.to_f if s} # # Generic numeric format, converts to Integer for integer format, Float # for float format, and Rational for rational format. # real = "[-+]?(?:#{octal}|#{float})" accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, f, n,| if n Rational(d, n) elsif f Float(s) else Integer(s) end } # # Decimal integer format, to be converted to Integer. # DecimalInteger = /\A[-+]?#{decimal}\z/io accept(DecimalInteger, DecimalInteger) {|s,| begin Integer(s, 10) rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Ruby/C like octal/hexadecimal/binary integer format, to be converted to # Integer. # OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io accept(OctalInteger, OctalInteger) {|s,| begin Integer(s, 8) rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Decimal integer/float number format, to be converted to Integer for # integer format, Float for float format. # DecimalNumeric = floatpat # decimal integer is allowed as float also. accept(DecimalNumeric, floatpat) {|s, f| begin if f Float(s) else Integer(s) end rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Boolean switch, which means whether it is present or not, whether it is # absent or not with prefix no-, or it takes an argument # yes/no/true/false/+/-. # yesno = CompletingHash.new %w[- no false].each {|el| yesno[el] = false} %w[+ yes true].each {|el| yesno[el] = true} yesno['nil'] = false # should be nil? accept(TrueClass, yesno) {|arg, val| val == nil or val} # # Similar to TrueClass, but defaults to false. # accept(FalseClass, yesno) {|arg, val| val != nil and val} # # List of strings separated by ",". # accept(Array) do |s, | if s s = s.split(',').collect {|ss| ss unless ss.empty?} end s end # # Regular expression with options. # accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o| f = 0 if o f |= Regexp::IGNORECASE if /i/ =~ o f |= Regexp::MULTILINE if /m/ =~ o f |= Regexp::EXTENDED if /x/ =~ o case o = o.delete("imx") when "" when "u" s = s.encode(Encoding::UTF_8) when "e" s = s.encode(Encoding::EUC_JP) when "s" s = s.encode(Encoding::SJIS) when "n" f |= Regexp::NOENCODING else raise Gem::OptionParser::InvalidArgument, "unknown regexp option - #{o}" end else s ||= all end Regexp.new(s, f) end # # Exceptions # # # Base class of exceptions from Gem::OptionParser. # class ParseError < RuntimeError # Reason which caused the error. Reason = 'parse error' def initialize(*args, additional: nil) @additional = additional @arg0, = args @args = args @reason = nil end attr_reader :args attr_writer :reason attr_accessor :additional # # Pushes back erred argument(s) to +argv+. # def recover(argv) argv[0, 0] = @args argv end def self.filter_backtrace(array) unless $DEBUG array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) end array end def set_backtrace(array) super(self.class.filter_backtrace(array)) end def set_option(opt, eq) if eq @args[0] = opt else @args.unshift(opt) end self end # # Returns error reason. Override this for I18N. # def reason @reason || self.class::Reason end def inspect "#<#{self.class}: #{args.join(' ')}>" end # # Default stringizing method to emit standard error message. # def message "#{reason}: #{args.join(' ')}#{additional[@arg0] if additional}" end alias to_s message end # # Raises when ambiguously completable string is encountered. # class AmbiguousOption < ParseError const_set(:Reason, 'ambiguous option') end # # Raises when there is an argument for a switch which takes no argument. # class NeedlessArgument < ParseError const_set(:Reason, 'needless argument') end # # Raises when a switch with mandatory argument has no argument. # class MissingArgument < ParseError const_set(:Reason, 'missing argument') end # # Raises when switch is undefined. # class InvalidOption < ParseError const_set(:Reason, 'invalid option') end # # Raises when the given argument does not match required format. # class InvalidArgument < ParseError const_set(:Reason, 'invalid argument') end # # Raises when the given argument word can't be completed uniquely. # class AmbiguousArgument < InvalidArgument const_set(:Reason, 'ambiguous argument') end # # Miscellaneous # # # Extends command line arguments array (ARGV) to parse itself. # module Arguable # # Sets Gem::OptionParser object, when +opt+ is +false+ or +nil+, methods # Gem::OptionParser::Arguable#options and Gem::OptionParser::Arguable#options= are # undefined. Thus, there is no ways to access the Gem::OptionParser object # via the receiver object. # def options=(opt) unless @optparse = opt class << self undef_method(:options) undef_method(:options=) end end end # # Actual Gem::OptionParser object, automatically created if nonexistent. # # If called with a block, yields the Gem::OptionParser object and returns the # result of the block. If an Gem::OptionParser::ParseError exception occurs # in the block, it is rescued, a error message printed to STDERR and # +nil+ returned. # def options @optparse ||= Gem::OptionParser.new @optparse.default_argv = self block_given? or return @optparse begin yield @optparse rescue ParseError @optparse.warn $! nil end end # # Parses +self+ destructively in order and returns +self+ containing the # rest arguments left unparsed. # def order!(&blk) options.order!(self, &blk) end # # Parses +self+ destructively in permutation mode and returns +self+ # containing the rest arguments left unparsed. # def permute!() options.permute!(self) end # # Parses +self+ destructively and returns +self+ containing the # rest arguments left unparsed. # def parse!() options.parse!(self) end # # Substitution of getopts is possible as follows. Also see # Gem::OptionParser#getopts. # # def getopts(*args) # ($OPT = ARGV.getopts(*args)).each do |opt, val| # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val" # end # rescue Gem::OptionParser::ParseError # end # def getopts(*args, symbolize_names: false) options.getopts(self, *args, symbolize_names: symbolize_names) end # # Initializes instance variable. # def self.extend_object(obj) super obj.instance_eval {@optparse = nil} end def initialize(*args) super @optparse = nil end end # # Acceptable argument classes. Now contains DecimalInteger, OctalInteger # and DecimalNumeric. See Acceptable argument classes (in source code). # module Acceptables const_set(:DecimalInteger, Gem::OptionParser::DecimalInteger) const_set(:OctalInteger, Gem::OptionParser::OctalInteger) const_set(:DecimalNumeric, Gem::OptionParser::DecimalNumeric) end end # ARGV is arguable by Gem::OptionParser ARGV.extend(Gem::OptionParser::Arguable) PK!_k#"")rubygems/vendor/net-http/lib/net/https.rbnu[# frozen_string_literal: true =begin = net/https -- SSL/TLS enhancement for Gem::Net::HTTP. This file has been merged with net/http. There is no longer any need to require_relative 'https' to use HTTPS. See Gem::Net::HTTP for details on how to make HTTPS connections. == Info 'OpenSSL for Ruby 2' project Copyright (C) 2001 GOTOU Yuuzou All rights reserved. == Licence This program is licensed under the same licence as Ruby. (See the file 'LICENCE'.) =end require_relative 'http' require 'openssl' PK!$SS(rubygems/vendor/net-http/lib/net/http.rbnu[# frozen_string_literal: true # # = net/http.rb # # Copyright (c) 1999-2007 Yukihiro Matsumoto # Copyright (c) 1999-2007 Minero Aoki # Copyright (c) 2001 GOTOU Yuuzou # # Written and maintained by Minero Aoki . # HTTPS support added by GOTOU Yuuzou . # # This file is derived from "http-access.rb". # # Documented by Minero Aoki; converted to RDoc by William Webber. # # This program is free software. You can re-distribute and/or # modify this program under the same terms of ruby itself --- # Ruby Distribution License or GNU General Public License. # # See Gem::Net::HTTP for an overview and examples. # require_relative '../../../net-protocol/lib/net/protocol' require_relative '../../../uri/lib/uri' require_relative '../../../resolv/lib/resolv' autoload :OpenSSL, 'openssl' module Gem::Net #:nodoc: # :stopdoc: class HTTPBadResponse < StandardError; end class HTTPHeaderSyntaxError < StandardError; end # :startdoc: # \Class \Gem::Net::HTTP provides a rich library that implements the client # in a client-server model that uses the \HTTP request-response protocol. # For information about \HTTP, see: # # - {Hypertext Transfer Protocol}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol]. # - {Technical overview}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technical_overview]. # # == About the Examples # # :include: doc/net-http/examples.rdoc # # == Strategies # # - If you will make only a few GET requests, # consider using {OpenURI}[rdoc-ref:OpenURI]. # - If you will make only a few requests of all kinds, # consider using the various singleton convenience methods in this class. # Each of the following methods automatically starts and finishes # a {session}[rdoc-ref:Gem::Net::HTTP@Sessions] that sends a single request: # # # Return string response body. # Gem::Net::HTTP.get(hostname, path) # Gem::Net::HTTP.get(uri) # # # Write string response body to $stdout. # Gem::Net::HTTP.get_print(hostname, path) # Gem::Net::HTTP.get_print(uri) # # # Return response as Gem::Net::HTTPResponse object. # Gem::Net::HTTP.get_response(hostname, path) # Gem::Net::HTTP.get_response(uri) # data = '{"title": "foo", "body": "bar", "userId": 1}' # Gem::Net::HTTP.post(uri, data) # params = {title: 'foo', body: 'bar', userId: 1} # Gem::Net::HTTP.post_form(uri, params) # # - If performance is important, consider using sessions, which lower request overhead. # This {session}[rdoc-ref:Gem::Net::HTTP@Sessions] has multiple requests for # {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods] # and {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: # # Gem::Net::HTTP.start(hostname) do |http| # # Session started automatically before block execution. # http.get(path) # http.head(path) # body = 'Some text' # http.post(path, body) # Can also have a block. # http.put(path, body) # http.delete(path) # http.options(path) # http.trace(path) # http.patch(path, body) # Can also have a block. # http.copy(path) # http.lock(path, body) # http.mkcol(path, body) # http.move(path) # http.propfind(path, body) # http.proppatch(path, body) # http.unlock(path, body) # # Session finished automatically at block exit. # end # # The methods cited above are convenience methods that, via their few arguments, # allow minimal control over the requests. # For greater control, consider using {request objects}[rdoc-ref:Gem::Net::HTTPRequest]. # # == URIs # # On the internet, a URI # ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier]) # is a string that identifies a particular resource. # It consists of some or all of: scheme, hostname, path, query, and fragment; # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. # # A Ruby {Gem::URI::Generic}[rdoc-ref:Gem::URI::Generic] object # represents an internet URI. # It provides, among others, methods # +scheme+, +hostname+, +path+, +query+, and +fragment+. # # === Schemes # # An internet \Gem::URI has # a {scheme}[https://en.wikipedia.org/wiki/List_of_URI_schemes]. # # The two schemes supported in \Gem::Net::HTTP are 'https' and 'http': # # uri.scheme # => "https" # Gem::URI('http://example.com').scheme # => "http" # # === Hostnames # # A hostname identifies a server (host) to which requests may be sent: # # hostname = uri.hostname # => "jsonplaceholder.typicode.com" # Gem::Net::HTTP.start(hostname) do |http| # # Some HTTP stuff. # end # # === Paths # # A host-specific path identifies a resource on the host: # # _uri = uri.dup # _uri.path = '/todos/1' # hostname = _uri.hostname # path = _uri.path # Gem::Net::HTTP.get(hostname, path) # # === Queries # # A host-specific query adds name/value pairs to the URI: # # _uri = uri.dup # params = {userId: 1, completed: false} # _uri.query = Gem::URI.encode_www_form(params) # _uri # => # # Gem::Net::HTTP.get(_uri) # # === Fragments # # A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect # in \Gem::Net::HTTP; # the same data is returned, regardless of whether a fragment is included. # # == Request Headers # # Request headers may be used to pass additional information to the host, # similar to arguments passed in a method call; # each header is a name/value pair. # # Each of the \Gem::Net::HTTP methods that sends a request to the host # has optional argument +headers+, # where the headers are expressed as a hash of field-name/value pairs: # # headers = {Accept: 'application/json', Connection: 'Keep-Alive'} # Gem::Net::HTTP.get(uri, headers) # # See lists of both standard request fields and common request fields at # {Request Fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. # A host may also accept other custom fields. # # == \HTTP Sessions # # A _session_ is a connection between a server (host) and a client that: # # - Is begun by instance method Gem::Net::HTTP#start. # - May contain any number of requests. # - Is ended by instance method Gem::Net::HTTP#finish. # # See example sessions at {Strategies}[rdoc-ref:Gem::Net::HTTP@Strategies]. # # === Session Using \Gem::Net::HTTP.start # # If you have many requests to make to a single host (and port), # consider using singleton method Gem::Net::HTTP.start with a block; # the method handles the session automatically by: # # - Calling #start before block execution. # - Executing the block. # - Calling #finish after block execution. # # In the block, you can use these instance methods, # each of which that sends a single request: # # - {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]: # # - #get, #request_get: GET. # - #head, #request_head: HEAD. # - #post, #request_post: POST. # - #delete: DELETE. # - #options: OPTIONS. # - #trace: TRACE. # - #patch: PATCH. # # - {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: # # - #copy: COPY. # - #lock: LOCK. # - #mkcol: MKCOL. # - #move: MOVE. # - #propfind: PROPFIND. # - #proppatch: PROPPATCH. # - #unlock: UNLOCK. # # === Session Using \Gem::Net::HTTP.start and \Gem::Net::HTTP.finish # # You can manage a session manually using methods #start and #finish: # # http = Gem::Net::HTTP.new(hostname) # http.start # http.get('/todos/1') # http.get('/todos/2') # http.delete('/posts/1') # http.finish # Needed to free resources. # # === Single-Request Session # # Certain convenience methods automatically handle a session by: # # - Creating an \HTTP object # - Starting a session. # - Sending a single request. # - Finishing the session. # - Destroying the object. # # Such methods that send GET requests: # # - ::get: Returns the string response body. # - ::get_print: Writes the string response body to $stdout. # - ::get_response: Returns a Gem::Net::HTTPResponse object. # # Such methods that send POST requests: # # - ::post: Posts data to the host. # - ::post_form: Posts form data to the host. # # == \HTTP Requests and Responses # # Many of the methods above are convenience methods, # each of which sends a request and returns a string # without directly using \Gem::Net::HTTPRequest and \Gem::Net::HTTPResponse objects. # # You can, however, directly create a request object, send the request, # and retrieve the response object; see: # # - Gem::Net::HTTPRequest. # - Gem::Net::HTTPResponse. # # == Following Redirection # # Each returned response is an instance of a subclass of Gem::Net::HTTPResponse. # See the {response class hierarchy}[rdoc-ref:Gem::Net::HTTPResponse@Response+Subclasses]. # # In particular, class Gem::Net::HTTPRedirection is the parent # of all redirection classes. # This allows you to craft a case statement to handle redirections properly: # # def fetch(uri, limit = 10) # # You should choose a better exception. # raise ArgumentError, 'Too many HTTP redirects' if limit == 0 # # res = Gem::Net::HTTP.get_response(Gem::URI(uri)) # case res # when Gem::Net::HTTPSuccess # Any success class. # res # when Gem::Net::HTTPRedirection # Any redirection class. # location = res['Location'] # warn "Redirected to #{location}" # fetch(location, limit - 1) # else # Any other class. # res.value # end # end # # fetch(uri) # # == Basic Authentication # # Basic authentication is performed according to # {RFC2617}[http://www.ietf.org/rfc/rfc2617.txt]: # # req = Gem::Net::HTTP::Get.new(uri) # req.basic_auth('user', 'pass') # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # == Streaming Response Bodies # # By default \Gem::Net::HTTP reads an entire response into memory. If you are # handling large files or wish to implement a progress bar you can instead # stream the body directly to an IO. # # Gem::Net::HTTP.start(hostname) do |http| # req = Gem::Net::HTTP::Get.new(uri) # http.request(req) do |res| # open('t.tmp', 'w') do |f| # res.read_body do |chunk| # f.write chunk # end # end # end # end # # == HTTPS # # HTTPS is enabled for an \HTTP connection by Gem::Net::HTTP#use_ssl=: # # Gem::Net::HTTP.start(hostname, :use_ssl => true) do |http| # req = Gem::Net::HTTP::Get.new(uri) # res = http.request(req) # end # # Or if you simply want to make a GET request, you may pass in a URI # object that has an \HTTPS URL. \Gem::Net::HTTP automatically turns on TLS # verification if the URI object has a 'https' :URI scheme: # # uri # => # # Gem::Net::HTTP.get(uri) # # == Proxy Server # # An \HTTP object can have # a {proxy server}[https://en.wikipedia.org/wiki/Proxy_server]. # # You can create an \HTTP object with a proxy server # using method Gem::Net::HTTP.new or method Gem::Net::HTTP.start. # # The proxy may be defined either by argument +p_addr+ # or by environment variable 'http_proxy'. # # === Proxy Using Argument +p_addr+ as a \String # # When argument +p_addr+ is a string hostname, # the returned +http+ has the given host as its proxy: # # http = Gem::Net::HTTP.new(hostname, nil, 'proxy.example') # http.proxy? # => true # http.proxy_from_env? # => false # http.proxy_address # => "proxy.example" # # These use default values. # http.proxy_port # => 80 # http.proxy_user # => nil # http.proxy_pass # => nil # # The port, username, and password for the proxy may also be given: # # http = Gem::Net::HTTP.new(hostname, nil, 'proxy.example', 8000, 'pname', 'ppass') # # => # # http.proxy? # => true # http.proxy_from_env? # => false # http.proxy_address # => "proxy.example" # http.proxy_port # => 8000 # http.proxy_user # => "pname" # http.proxy_pass # => "ppass" # # === Proxy Using 'ENV['http_proxy']' # # When environment variable 'http_proxy' # is set to a \Gem::URI string, # the returned +http+ will have the server at that URI as its proxy; # note that the \Gem::URI string must have a protocol # such as 'http' or 'https': # # ENV['http_proxy'] = 'http://example.com' # http = Gem::Net::HTTP.new(hostname) # http.proxy? # => true # http.proxy_from_env? # => true # http.proxy_address # => "example.com" # # These use default values. # http.proxy_port # => 80 # http.proxy_user # => nil # http.proxy_pass # => nil # # The \Gem::URI string may include proxy username, password, and port number: # # ENV['http_proxy'] = 'http://pname:ppass@example.com:8000' # http = Gem::Net::HTTP.new(hostname) # http.proxy? # => true # http.proxy_from_env? # => true # http.proxy_address # => "example.com" # http.proxy_port # => 8000 # http.proxy_user # => "pname" # http.proxy_pass # => "ppass" # # === Filtering Proxies # # With method Gem::Net::HTTP.new (but not Gem::Net::HTTP.start), # you can use argument +p_no_proxy+ to filter proxies: # # - Reject a certain address: # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example') # http.proxy_address # => nil # # - Reject certain domains or subdomains: # # http = Gem::Net::HTTP.new('example.com', nil, 'my.proxy.example', 8000, 'pname', 'ppass', 'proxy.example') # http.proxy_address # => nil # # - Reject certain addresses and port combinations: # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:1234') # http.proxy_address # => "proxy.example" # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:8000') # http.proxy_address # => nil # # - Reject a list of the types above delimited using a comma: # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000') # http.proxy_address # => nil # # http = Gem::Net::HTTP.new('example.com', nil, 'my.proxy', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000') # http.proxy_address # => nil # # == Compression and Decompression # # \Gem::Net::HTTP does not compress the body of a request before sending. # # By default, \Gem::Net::HTTP adds header 'Accept-Encoding' # to a new {request object}[rdoc-ref:Gem::Net::HTTPRequest]: # # Gem::Net::HTTP::Get.new(uri)['Accept-Encoding'] # # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" # # This requests the server to zip-encode the response body if there is one; # the server is not required to do so. # # \Gem::Net::HTTP does not automatically decompress a response body # if the response has header 'Content-Range'. # # Otherwise decompression (or not) depends on the value of header # {Content-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-encoding-response-header]: # # - 'deflate', 'gzip', or 'x-gzip': # decompresses the body and deletes the header. # - 'none' or 'identity': # does not decompress the body, but deletes the header. # - Any other value: # leaves the body and header unchanged. # # == What's Here # # This is a categorized summary of methods and attributes. # # === \Gem::Net::HTTP Objects # # - {::new}[rdoc-ref:Gem::Net::HTTP.new]: # Creates a new instance. # - {#inspect}[rdoc-ref:Gem::Net::HTTP#inspect]: # Returns a string representation of +self+. # # === Sessions # # - {::start}[rdoc-ref:Gem::Net::HTTP.start]: # Begins a new session in a new \Gem::Net::HTTP object. # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?] # (aliased as {#active?}[rdoc-ref:Gem::Net::HTTP#active?]): # Returns whether in a session. # - {#finish}[rdoc-ref:Gem::Net::HTTP#finish]: # Ends an active session. # - {#start}[rdoc-ref:Gem::Net::HTTP#start]: # Begins a new session in an existing \Gem::Net::HTTP object (+self+). # # === Connections # # - {:continue_timeout}[rdoc-ref:Gem::Net::HTTP#continue_timeout]: # Returns the continue timeout. # - {#continue_timeout=}[rdoc-ref:Gem::Net::HTTP#continue_timeout=]: # Sets the continue timeout seconds. # - {:keep_alive_timeout}[rdoc-ref:Gem::Net::HTTP#keep_alive_timeout]: # Returns the keep-alive timeout. # - {:keep_alive_timeout=}[rdoc-ref:Gem::Net::HTTP#keep_alive_timeout=]: # Sets the keep-alive timeout. # - {:max_retries}[rdoc-ref:Gem::Net::HTTP#max_retries]: # Returns the maximum retries. # - {#max_retries=}[rdoc-ref:Gem::Net::HTTP#max_retries=]: # Sets the maximum retries. # - {:open_timeout}[rdoc-ref:Gem::Net::HTTP#open_timeout]: # Returns the open timeout. # - {:open_timeout=}[rdoc-ref:Gem::Net::HTTP#open_timeout=]: # Sets the open timeout. # - {:read_timeout}[rdoc-ref:Gem::Net::HTTP#read_timeout]: # Returns the open timeout. # - {:read_timeout=}[rdoc-ref:Gem::Net::HTTP#read_timeout=]: # Sets the read timeout. # - {:ssl_timeout}[rdoc-ref:Gem::Net::HTTP#ssl_timeout]: # Returns the ssl timeout. # - {:ssl_timeout=}[rdoc-ref:Gem::Net::HTTP#ssl_timeout=]: # Sets the ssl timeout. # - {:write_timeout}[rdoc-ref:Gem::Net::HTTP#write_timeout]: # Returns the write timeout. # - {write_timeout=}[rdoc-ref:Gem::Net::HTTP#write_timeout=]: # Sets the write timeout. # # === Requests # # - {::get}[rdoc-ref:Gem::Net::HTTP.get]: # Sends a GET request and returns the string response body. # - {::get_print}[rdoc-ref:Gem::Net::HTTP.get_print]: # Sends a GET request and write the string response body to $stdout. # - {::get_response}[rdoc-ref:Gem::Net::HTTP.get_response]: # Sends a GET request and returns a response object. # - {::post_form}[rdoc-ref:Gem::Net::HTTP.post_form]: # Sends a POST request with form data and returns a response object. # - {::post}[rdoc-ref:Gem::Net::HTTP.post]: # Sends a POST request with data and returns a response object. # - {#copy}[rdoc-ref:Gem::Net::HTTP#copy]: # Sends a COPY request and returns a response object. # - {#delete}[rdoc-ref:Gem::Net::HTTP#delete]: # Sends a DELETE request and returns a response object. # - {#get}[rdoc-ref:Gem::Net::HTTP#get]: # Sends a GET request and returns a response object. # - {#head}[rdoc-ref:Gem::Net::HTTP#head]: # Sends a HEAD request and returns a response object. # - {#lock}[rdoc-ref:Gem::Net::HTTP#lock]: # Sends a LOCK request and returns a response object. # - {#mkcol}[rdoc-ref:Gem::Net::HTTP#mkcol]: # Sends a MKCOL request and returns a response object. # - {#move}[rdoc-ref:Gem::Net::HTTP#move]: # Sends a MOVE request and returns a response object. # - {#options}[rdoc-ref:Gem::Net::HTTP#options]: # Sends a OPTIONS request and returns a response object. # - {#patch}[rdoc-ref:Gem::Net::HTTP#patch]: # Sends a PATCH request and returns a response object. # - {#post}[rdoc-ref:Gem::Net::HTTP#post]: # Sends a POST request and returns a response object. # - {#propfind}[rdoc-ref:Gem::Net::HTTP#propfind]: # Sends a PROPFIND request and returns a response object. # - {#proppatch}[rdoc-ref:Gem::Net::HTTP#proppatch]: # Sends a PROPPATCH request and returns a response object. # - {#put}[rdoc-ref:Gem::Net::HTTP#put]: # Sends a PUT request and returns a response object. # - {#request}[rdoc-ref:Gem::Net::HTTP#request]: # Sends a request and returns a response object. # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get] # (aliased as {#get2}[rdoc-ref:Gem::Net::HTTP#get2]): # Sends a GET request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head] # (aliased as {#head2}[rdoc-ref:Gem::Net::HTTP#head2]): # Sends a HEAD request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post] # (aliased as {#post2}[rdoc-ref:Gem::Net::HTTP#post2]): # Sends a POST request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. # - {#send_request}[rdoc-ref:Gem::Net::HTTP#send_request]: # Sends a request and returns a response object. # - {#trace}[rdoc-ref:Gem::Net::HTTP#trace]: # Sends a TRACE request and returns a response object. # - {#unlock}[rdoc-ref:Gem::Net::HTTP#unlock]: # Sends an UNLOCK request and returns a response object. # # === Responses # # - {:close_on_empty_response}[rdoc-ref:Gem::Net::HTTP#close_on_empty_response]: # Returns whether to close connection on empty response. # - {:close_on_empty_response=}[rdoc-ref:Gem::Net::HTTP#close_on_empty_response=]: # Sets whether to close connection on empty response. # - {:ignore_eof}[rdoc-ref:Gem::Net::HTTP#ignore_eof]: # Returns whether to ignore end-of-file when reading a response body # with Content-Length headers. # - {:ignore_eof=}[rdoc-ref:Gem::Net::HTTP#ignore_eof=]: # Sets whether to ignore end-of-file when reading a response body # with Content-Length headers. # - {:response_body_encoding}[rdoc-ref:Gem::Net::HTTP#response_body_encoding]: # Returns the encoding to use for the response body. # - {#response_body_encoding=}[rdoc-ref:Gem::Net::HTTP#response_body_encoding=]: # Sets the response body encoding. # # === Proxies # # - {:proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]: # Returns the proxy address. # - {:proxy_address=}[rdoc-ref:Gem::Net::HTTP#proxy_address=]: # Sets the proxy address. # - {::proxy_class?}[rdoc-ref:Gem::Net::HTTP.proxy_class?]: # Returns whether +self+ is a proxy class. # - {#proxy?}[rdoc-ref:Gem::Net::HTTP#proxy?]: # Returns whether +self+ has a proxy. # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address] # (aliased as {#proxyaddr}[rdoc-ref:Gem::Net::HTTP#proxyaddr]): # Returns the proxy address. # - {#proxy_from_env?}[rdoc-ref:Gem::Net::HTTP#proxy_from_env?]: # Returns whether the proxy is taken from an environment variable. # - {:proxy_from_env=}[rdoc-ref:Gem::Net::HTTP#proxy_from_env=]: # Sets whether the proxy is to be taken from an environment variable. # - {:proxy_pass}[rdoc-ref:Gem::Net::HTTP#proxy_pass]: # Returns the proxy password. # - {:proxy_pass=}[rdoc-ref:Gem::Net::HTTP#proxy_pass=]: # Sets the proxy password. # - {:proxy_port}[rdoc-ref:Gem::Net::HTTP#proxy_port]: # Returns the proxy port. # - {:proxy_port=}[rdoc-ref:Gem::Net::HTTP#proxy_port=]: # Sets the proxy port. # - {#proxy_user}[rdoc-ref:Gem::Net::HTTP#proxy_user]: # Returns the proxy user name. # - {:proxy_user=}[rdoc-ref:Gem::Net::HTTP#proxy_user=]: # Sets the proxy user. # # === Security # # - {:ca_file}[rdoc-ref:Gem::Net::HTTP#ca_file]: # Returns the path to a CA certification file. # - {:ca_file=}[rdoc-ref:Gem::Net::HTTP#ca_file=]: # Sets the path to a CA certification file. # - {:ca_path}[rdoc-ref:Gem::Net::HTTP#ca_path]: # Returns the path of to CA directory containing certification files. # - {:ca_path=}[rdoc-ref:Gem::Net::HTTP#ca_path=]: # Sets the path of to CA directory containing certification files. # - {:cert}[rdoc-ref:Gem::Net::HTTP#cert]: # Returns the OpenSSL::X509::Certificate object to be used for client certification. # - {:cert=}[rdoc-ref:Gem::Net::HTTP#cert=]: # Sets the OpenSSL::X509::Certificate object to be used for client certification. # - {:cert_store}[rdoc-ref:Gem::Net::HTTP#cert_store]: # Returns the X509::Store to be used for verifying peer certificate. # - {:cert_store=}[rdoc-ref:Gem::Net::HTTP#cert_store=]: # Sets the X509::Store to be used for verifying peer certificate. # - {:ciphers}[rdoc-ref:Gem::Net::HTTP#ciphers]: # Returns the available SSL ciphers. # - {:ciphers=}[rdoc-ref:Gem::Net::HTTP#ciphers=]: # Sets the available SSL ciphers. # - {:extra_chain_cert}[rdoc-ref:Gem::Net::HTTP#extra_chain_cert]: # Returns the extra X509 certificates to be added to the certificate chain. # - {:extra_chain_cert=}[rdoc-ref:Gem::Net::HTTP#extra_chain_cert=]: # Sets the extra X509 certificates to be added to the certificate chain. # - {:key}[rdoc-ref:Gem::Net::HTTP#key]: # Returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. # - {:key=}[rdoc-ref:Gem::Net::HTTP#key=]: # Sets the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. # - {:max_version}[rdoc-ref:Gem::Net::HTTP#max_version]: # Returns the maximum SSL version. # - {:max_version=}[rdoc-ref:Gem::Net::HTTP#max_version=]: # Sets the maximum SSL version. # - {:min_version}[rdoc-ref:Gem::Net::HTTP#min_version]: # Returns the minimum SSL version. # - {:min_version=}[rdoc-ref:Gem::Net::HTTP#min_version=]: # Sets the minimum SSL version. # - {#peer_cert}[rdoc-ref:Gem::Net::HTTP#peer_cert]: # Returns the X509 certificate chain for the session's socket peer. # - {:ssl_version}[rdoc-ref:Gem::Net::HTTP#ssl_version]: # Returns the SSL version. # - {:ssl_version=}[rdoc-ref:Gem::Net::HTTP#ssl_version=]: # Sets the SSL version. # - {#use_ssl=}[rdoc-ref:Gem::Net::HTTP#use_ssl=]: # Sets whether a new session is to use Transport Layer Security. # - {#use_ssl?}[rdoc-ref:Gem::Net::HTTP#use_ssl?]: # Returns whether +self+ uses SSL. # - {:verify_callback}[rdoc-ref:Gem::Net::HTTP#verify_callback]: # Returns the callback for the server certification verification. # - {:verify_callback=}[rdoc-ref:Gem::Net::HTTP#verify_callback=]: # Sets the callback for the server certification verification. # - {:verify_depth}[rdoc-ref:Gem::Net::HTTP#verify_depth]: # Returns the maximum depth for the certificate chain verification. # - {:verify_depth=}[rdoc-ref:Gem::Net::HTTP#verify_depth=]: # Sets the maximum depth for the certificate chain verification. # - {:verify_hostname}[rdoc-ref:Gem::Net::HTTP#verify_hostname]: # Returns the flags for server the certification verification at the beginning of the SSL/TLS session. # - {:verify_hostname=}[rdoc-ref:Gem::Net::HTTP#verify_hostname=]: # Sets he flags for server the certification verification at the beginning of the SSL/TLS session. # - {:verify_mode}[rdoc-ref:Gem::Net::HTTP#verify_mode]: # Returns the flags for server the certification verification at the beginning of the SSL/TLS session. # - {:verify_mode=}[rdoc-ref:Gem::Net::HTTP#verify_mode=]: # Sets the flags for server the certification verification at the beginning of the SSL/TLS session. # # === Addresses and Ports # # - {:address}[rdoc-ref:Gem::Net::HTTP#address]: # Returns the string host name or host IP. # - {::default_port}[rdoc-ref:Gem::Net::HTTP.default_port]: # Returns integer 80, the default port to use for HTTP requests. # - {::http_default_port}[rdoc-ref:Gem::Net::HTTP.http_default_port]: # Returns integer 80, the default port to use for HTTP requests. # - {::https_default_port}[rdoc-ref:Gem::Net::HTTP.https_default_port]: # Returns integer 443, the default port to use for HTTPS requests. # - {#ipaddr}[rdoc-ref:Gem::Net::HTTP#ipaddr]: # Returns the IP address for the connection. # - {#ipaddr=}[rdoc-ref:Gem::Net::HTTP#ipaddr=]: # Sets the IP address for the connection. # - {:local_host}[rdoc-ref:Gem::Net::HTTP#local_host]: # Returns the string local host used to establish the connection. # - {:local_host=}[rdoc-ref:Gem::Net::HTTP#local_host=]: # Sets the string local host used to establish the connection. # - {:local_port}[rdoc-ref:Gem::Net::HTTP#local_port]: # Returns the integer local port used to establish the connection. # - {:local_port=}[rdoc-ref:Gem::Net::HTTP#local_port=]: # Sets the integer local port used to establish the connection. # - {:port}[rdoc-ref:Gem::Net::HTTP#port]: # Returns the integer port number. # # === \HTTP Version # # - {::version_1_2?}[rdoc-ref:Gem::Net::HTTP.version_1_2?] # (aliased as {::is_version_1_2?}[rdoc-ref:Gem::Net::HTTP.is_version_1_2?] # and {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]): # Returns true; retained for compatibility. # # === Debugging # # - {#set_debug_output}[rdoc-ref:Gem::Net::HTTP#set_debug_output]: # Sets the output stream for debugging. # class HTTP < Protocol # :stopdoc: VERSION = "0.4.1" HTTPVersion = '1.1' begin require 'zlib' HAVE_ZLIB=true rescue LoadError HAVE_ZLIB=false end # :startdoc: # Returns +true+; retained for compatibility. def HTTP.version_1_2 true end # Returns +true+; retained for compatibility. def HTTP.version_1_2? true end # Returns +false+; retained for compatibility. def HTTP.version_1_1? #:nodoc: false end class << HTTP alias is_version_1_1? version_1_1? #:nodoc: alias is_version_1_2? version_1_2? #:nodoc: end # :call-seq: # Gem::Net::HTTP.get_print(hostname, path, port = 80) -> nil # Gem::Net::HTTP:get_print(uri, headers = {}, port = uri.port) -> nil # # Like Gem::Net::HTTP.get, but writes the returned body to $stdout; # returns +nil+. def HTTP.get_print(uri_or_host, path_or_headers = nil, port = nil) get_response(uri_or_host, path_or_headers, port) {|res| res.read_body do |chunk| $stdout.print chunk end } nil end # :call-seq: # Gem::Net::HTTP.get(hostname, path, port = 80) -> body # Gem::Net::HTTP:get(uri, headers = {}, port = uri.port) -> body # # Sends a GET request and returns the \HTTP response body as a string. # # With string arguments +hostname+ and +path+: # # hostname = 'jsonplaceholder.typicode.com' # path = '/todos/1' # puts Gem::Net::HTTP.get(hostname, path) # # Output: # # { # "userId": 1, # "id": 1, # "title": "delectus aut autem", # "completed": false # } # # With URI object +uri+ and optional hash argument +headers+: # # uri = Gem::URI('https://jsonplaceholder.typicode.com/todos/1') # headers = {'Content-type' => 'application/json; charset=UTF-8'} # Gem::Net::HTTP.get(uri, headers) # # Related: # # - Gem::Net::HTTP::Get: request class for \HTTP method +GET+. # - Gem::Net::HTTP#get: convenience method for \HTTP method +GET+. # def HTTP.get(uri_or_host, path_or_headers = nil, port = nil) get_response(uri_or_host, path_or_headers, port).body end # :call-seq: # Gem::Net::HTTP.get_response(hostname, path, port = 80) -> http_response # Gem::Net::HTTP:get_response(uri, headers = {}, port = uri.port) -> http_response # # Like Gem::Net::HTTP.get, but returns a Gem::Net::HTTPResponse object # instead of the body string. def HTTP.get_response(uri_or_host, path_or_headers = nil, port = nil, &block) if path_or_headers && !path_or_headers.is_a?(Hash) host = uri_or_host path = path_or_headers new(host, port || HTTP.default_port).start {|http| return http.request_get(path, &block) } else uri = uri_or_host headers = path_or_headers start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') {|http| return http.request_get(uri, headers, &block) } end end # Posts data to a host; returns a Gem::Net::HTTPResponse object. # # Argument +url+ must be a URL; # argument +data+ must be a string: # # _uri = uri.dup # _uri.path = '/posts' # data = '{"title": "foo", "body": "bar", "userId": 1}' # headers = {'content-type': 'application/json'} # res = Gem::Net::HTTP.post(_uri, data, headers) # => # # puts res.body # # Output: # # { # "title": "foo", # "body": "bar", # "userId": 1, # "id": 101 # } # # Related: # # - Gem::Net::HTTP::Post: request class for \HTTP method +POST+. # - Gem::Net::HTTP#post: convenience method for \HTTP method +POST+. # def HTTP.post(url, data, header = nil) start(url.hostname, url.port, :use_ssl => url.scheme == 'https' ) {|http| http.post(url, data, header) } end # Posts data to a host; returns a Gem::Net::HTTPResponse object. # # Argument +url+ must be a URI; # argument +data+ must be a hash: # # _uri = uri.dup # _uri.path = '/posts' # data = {title: 'foo', body: 'bar', userId: 1} # res = Gem::Net::HTTP.post_form(_uri, data) # => # # puts res.body # # Output: # # { # "title": "foo", # "body": "bar", # "userId": "1", # "id": 101 # } # def HTTP.post_form(url, params) req = Post.new(url) req.form_data = params req.basic_auth url.user, url.password if url.user start(url.hostname, url.port, :use_ssl => url.scheme == 'https' ) {|http| http.request(req) } end # # \HTTP session management # # Returns integer +80+, the default port to use for \HTTP requests: # # Gem::Net::HTTP.default_port # => 80 # def HTTP.default_port http_default_port() end # Returns integer +80+, the default port to use for \HTTP requests: # # Gem::Net::HTTP.http_default_port # => 80 # def HTTP.http_default_port 80 end # Returns integer +443+, the default port to use for HTTPS requests: # # Gem::Net::HTTP.https_default_port # => 443 # def HTTP.https_default_port 443 end def HTTP.socket_type #:nodoc: obsolete BufferedIO end # :call-seq: # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) -> http # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) {|http| ... } -> object # # Creates a new \Gem::Net::HTTP object, +http+, via \Gem::Net::HTTP.new: # # - For arguments +address+ and +port+, see Gem::Net::HTTP.new. # - For proxy-defining arguments +p_addr+ through +p_pass+, # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. # - For argument +opts+, see below. # # With no block given: # # - Calls http.start with no block (see #start), # which opens a TCP connection and \HTTP session. # - Returns +http+. # - The caller should call #finish to close the session: # # http = Gem::Net::HTTP.start(hostname) # http.started? # => true # http.finish # http.started? # => false # # With a block given: # # - Calls http.start with the block (see #start), which: # # - Opens a TCP connection and \HTTP session. # - Calls the block, # which may make any number of requests to the host. # - Closes the \HTTP session and TCP connection on block exit. # - Returns the block's value +object+. # # - Returns +object+. # # Example: # # hostname = 'jsonplaceholder.typicode.com' # Gem::Net::HTTP.start(hostname) do |http| # puts http.get('/todos/1').body # puts http.get('/todos/2').body # end # # Output: # # { # "userId": 1, # "id": 1, # "title": "delectus aut autem", # "completed": false # } # { # "userId": 1, # "id": 2, # "title": "quis ut nam facilis et officia qui", # "completed": false # } # # If the last argument given is a hash, it is the +opts+ hash, # where each key is a method or accessor to be called, # and its value is the value to be set. # # The keys may include: # # - #ca_file # - #ca_path # - #cert # - #cert_store # - #ciphers # - #close_on_empty_response # - +ipaddr+ (calls #ipaddr=) # - #keep_alive_timeout # - #key # - #open_timeout # - #read_timeout # - #ssl_timeout # - #ssl_version # - +use_ssl+ (calls #use_ssl=) # - #verify_callback # - #verify_depth # - #verify_mode # - #write_timeout # # Note: If +port+ is +nil+ and opts[:use_ssl] is a truthy value, # the value passed to +new+ is Gem::Net::HTTP.https_default_port, not +port+. # def HTTP.start(address, *arg, &block) # :yield: +http+ arg.pop if opt = Hash.try_convert(arg[-1]) port, p_addr, p_port, p_user, p_pass = *arg p_addr = :ENV if arg.size < 2 port = https_default_port if !port && opt && opt[:use_ssl] http = new(address, port, p_addr, p_port, p_user, p_pass) http.ipaddr = opt[:ipaddr] if opt && opt[:ipaddr] if opt if opt[:use_ssl] opt = {verify_mode: OpenSSL::SSL::VERIFY_PEER}.update(opt) end http.methods.grep(/\A(\w+)=\z/) do |meth| key = $1.to_sym opt.key?(key) or next http.__send__(meth, opt[key]) end end http.start(&block) end class << HTTP alias newobj new # :nodoc: end # Returns a new \Gem::Net::HTTP object +http+ # (but does not open a TCP connection or \HTTP session). # # With only string argument +address+ given # (and ENV['http_proxy'] undefined or +nil+), # the returned +http+: # # - Has the given address. # - Has the default port number, Gem::Net::HTTP.default_port (80). # - Has no proxy. # # Example: # # http = Gem::Net::HTTP.new(hostname) # # => # # http.address # => "jsonplaceholder.typicode.com" # http.port # => 80 # http.proxy? # => false # # With integer argument +port+ also given, # the returned +http+ has the given port: # # http = Gem::Net::HTTP.new(hostname, 8000) # # => # # http.port # => 8000 # # For proxy-defining arguments +p_addr+ through +p_no_proxy+, # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. # def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil) http = super address, port if proxy_class? then # from Gem::Net::HTTP::Proxy() http.proxy_from_env = @proxy_from_env http.proxy_address = @proxy_address http.proxy_port = @proxy_port http.proxy_user = @proxy_user http.proxy_pass = @proxy_pass elsif p_addr == :ENV then http.proxy_from_env = true else if p_addr && p_no_proxy && !Gem::URI::Generic.use_proxy?(address, address, port, p_no_proxy) p_addr = nil p_port = nil end http.proxy_address = p_addr http.proxy_port = p_port || default_port http.proxy_user = p_user http.proxy_pass = p_pass end http end # Creates a new \Gem::Net::HTTP object for the specified server address, # without opening the TCP connection or initializing the \HTTP session. # The +address+ should be a DNS hostname or IP address. def initialize(address, port = nil) # :nodoc: @address = address @port = (port || HTTP.default_port) @ipaddr = nil @local_host = nil @local_port = nil @curr_http_version = HTTPVersion @keep_alive_timeout = 2 @last_communicated = nil @close_on_empty_response = false @socket = nil @started = false @open_timeout = 60 @read_timeout = 60 @write_timeout = 60 @continue_timeout = nil @max_retries = 1 @debug_output = nil @response_body_encoding = false @ignore_eof = true @proxy_from_env = false @proxy_uri = nil @proxy_address = nil @proxy_port = nil @proxy_user = nil @proxy_pass = nil @use_ssl = false @ssl_context = nil @ssl_session = nil @sspi_enabled = false SSL_IVNAMES.each do |ivname| instance_variable_set ivname, nil end end # Returns a string representation of +self+: # # Gem::Net::HTTP.new(hostname).inspect # # => "#" # def inspect "#<#{self.class} #{@address}:#{@port} open=#{started?}>" end # *WARNING* This method opens a serious security hole. # Never use this method in production code. # # Sets the output stream for debugging: # # http = Gem::Net::HTTP.new(hostname) # File.open('t.tmp', 'w') do |file| # http.set_debug_output(file) # http.start # http.get('/nosuch/1') # http.finish # end # puts File.read('t.tmp') # # Output: # # opening connection to jsonplaceholder.typicode.com:80... # opened # <- "GET /nosuch/1 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: jsonplaceholder.typicode.com\r\n\r\n" # -> "HTTP/1.1 404 Not Found\r\n" # -> "Date: Mon, 12 Dec 2022 21:14:11 GMT\r\n" # -> "Content-Type: application/json; charset=utf-8\r\n" # -> "Content-Length: 2\r\n" # -> "Connection: keep-alive\r\n" # -> "X-Powered-By: Express\r\n" # -> "X-Ratelimit-Limit: 1000\r\n" # -> "X-Ratelimit-Remaining: 999\r\n" # -> "X-Ratelimit-Reset: 1670879660\r\n" # -> "Vary: Origin, Accept-Encoding\r\n" # -> "Access-Control-Allow-Credentials: true\r\n" # -> "Cache-Control: max-age=43200\r\n" # -> "Pragma: no-cache\r\n" # -> "Expires: -1\r\n" # -> "X-Content-Type-Options: nosniff\r\n" # -> "Etag: W/\"2-vyGp6PvFo4RvsFtPoIWeCReyIC8\"\r\n" # -> "Via: 1.1 vegur\r\n" # -> "CF-Cache-Status: MISS\r\n" # -> "Server-Timing: cf-q-config;dur=1.3000000762986e-05\r\n" # -> "Report-To: {\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=yOr40jo%2BwS1KHzhTlVpl54beJ5Wx2FcG4gGV0XVrh3X9OlR5q4drUn2dkt5DGO4GDcE%2BVXT7CNgJvGs%2BZleIyMu8CLieFiDIvOviOY3EhHg94m0ZNZgrEdpKD0S85S507l1vsEwEHkoTm%2Ff19SiO\"}],\"group\":\"cf-nel\",\"max_age\":604800}\r\n" # -> "NEL: {\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}\r\n" # -> "Server: cloudflare\r\n" # -> "CF-RAY: 778977dc484ce591-DFW\r\n" # -> "alt-svc: h3=\":443\"; ma=86400, h3-29=\":443\"; ma=86400\r\n" # -> "\r\n" # reading 2 bytes... # -> "{}" # read 2 bytes # Conn keep-alive # def set_debug_output(output) warn 'Gem::Net::HTTP#set_debug_output called after HTTP started', uplevel: 1 if started? @debug_output = output end # Returns the string host name or host IP given as argument +address+ in ::new. attr_reader :address # Returns the integer port number given as argument +port+ in ::new. attr_reader :port # Sets or returns the string local host used to establish the connection; # initially +nil+. attr_accessor :local_host # Sets or returns the integer local port used to establish the connection; # initially +nil+. attr_accessor :local_port # Returns the encoding to use for the response body; # see #response_body_encoding=. attr_reader :response_body_encoding # Sets the encoding to be used for the response body; # returns the encoding. # # The given +value+ may be: # # - An Encoding object. # - The name of an encoding. # - An alias for an encoding name. # # See {Encoding}[rdoc-ref:Encoding]. # # Examples: # # http = Gem::Net::HTTP.new(hostname) # http.response_body_encoding = Encoding::US_ASCII # => # # http.response_body_encoding = 'US-ASCII' # => "US-ASCII" # http.response_body_encoding = 'ASCII' # => "ASCII" # def response_body_encoding=(value) value = Encoding.find(value) if value.is_a?(String) @response_body_encoding = value end # Sets whether to determine the proxy from environment variable # 'ENV['http_proxy']'; # see {Proxy Using ENV['http_proxy']}[rdoc-ref:Gem::Net::HTTP@Proxy+Using+-27ENV-5B-27http_proxy-27-5D-27]. attr_writer :proxy_from_env # Sets the proxy address; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_address # Sets the proxy port; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_port # Sets the proxy user; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_user # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_pass # Returns the IP address for the connection. # # If the session has not been started, # returns the value set by #ipaddr=, # or +nil+ if it has not been set: # # http = Gem::Net::HTTP.new(hostname) # http.ipaddr # => nil # http.ipaddr = '172.67.155.76' # http.ipaddr # => "172.67.155.76" # # If the session has been started, # returns the IP address from the socket: # # http = Gem::Net::HTTP.new(hostname) # http.start # http.ipaddr # => "172.67.155.76" # http.finish # def ipaddr started? ? @socket.io.peeraddr[3] : @ipaddr end # Sets the IP address for the connection: # # http = Gem::Net::HTTP.new(hostname) # http.ipaddr # => nil # http.ipaddr = '172.67.155.76' # http.ipaddr # => "172.67.155.76" # # The IP address may not be set if the session has been started. def ipaddr=(addr) raise IOError, "ipaddr value changed, but session already started" if started? @ipaddr = addr end # Sets or returns the numeric (\Integer or \Float) number of seconds # to wait for a connection to open; # initially 60. # If the connection is not made in the given interval, # an exception is raised. attr_accessor :open_timeout # Returns the numeric (\Integer or \Float) number of seconds # to wait for one block to be read (via one read(2) call); # see #read_timeout=. attr_reader :read_timeout # Returns the numeric (\Integer or \Float) number of seconds # to wait for one block to be written (via one write(2) call); # see #write_timeout=. attr_reader :write_timeout # Sets the maximum number of times to retry an idempotent request in case of # \Gem::Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET, # Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError, # Gem::Timeout::Error. # The initial value is 1. # # Argument +retries+ must be a non-negative numeric value: # # http = Gem::Net::HTTP.new(hostname) # http.max_retries = 2 # => 2 # http.max_retries # => 2 # def max_retries=(retries) retries = retries.to_int if retries < 0 raise ArgumentError, 'max_retries should be non-negative integer number' end @max_retries = retries end # Returns the maximum number of times to retry an idempotent request; # see #max_retries=. attr_reader :max_retries # Sets the read timeout, in seconds, for +self+ to integer +sec+; # the initial value is 60. # # Argument +sec+ must be a non-negative numeric value: # # http = Gem::Net::HTTP.new(hostname) # http.read_timeout # => 60 # http.get('/todos/1') # => # # http.read_timeout = 0 # http.get('/todos/1') # Raises Gem::Net::ReadTimeout. # def read_timeout=(sec) @socket.read_timeout = sec if @socket @read_timeout = sec end # Sets the write timeout, in seconds, for +self+ to integer +sec+; # the initial value is 60. # # Argument +sec+ must be a non-negative numeric value: # # _uri = uri.dup # _uri.path = '/posts' # body = 'bar' * 200000 # data = < 60 # http.post(_uri.path, data, headers) # # => # # http.write_timeout = 0 # http.post(_uri.path, data, headers) # Raises Gem::Net::WriteTimeout. # def write_timeout=(sec) @socket.write_timeout = sec if @socket @write_timeout = sec end # Returns the continue timeout value; # see continue_timeout=. attr_reader :continue_timeout # Sets the continue timeout value, # which is the number of seconds to wait for an expected 100 Continue response. # If the \HTTP object does not receive a response in this many seconds # it sends the request body. def continue_timeout=(sec) @socket.continue_timeout = sec if @socket @continue_timeout = sec end # Sets or returns the numeric (\Integer or \Float) number of seconds # to keep the connection open after a request is sent; # initially 2. # If a new request is made during the given interval, # the still-open connection is used; # otherwise the connection will have been closed # and a new connection is opened. attr_accessor :keep_alive_timeout # Sets or returns whether to ignore end-of-file when reading a response body # with Content-Length headers; # initially +true+. attr_accessor :ignore_eof # Returns +true+ if the \HTTP session has been started: # # http = Gem::Net::HTTP.new(hostname) # http.started? # => false # http.start # http.started? # => true # http.finish # => nil # http.started? # => false # # Gem::Net::HTTP.start(hostname) do |http| # http.started? # end # => true # http.started? # => false # def started? @started end alias active? started? #:nodoc: obsolete # Sets or returns whether to close the connection when the response is empty; # initially +false+. attr_accessor :close_on_empty_response # Returns +true+ if +self+ uses SSL, +false+ otherwise. # See Gem::Net::HTTP#use_ssl=. def use_ssl? @use_ssl end # Sets whether a new session is to use # {Transport Layer Security}[https://en.wikipedia.org/wiki/Transport_Layer_Security]: # # Raises IOError if attempting to change during a session. # # Raises OpenSSL::SSL::SSLError if the port is not an HTTPS port. def use_ssl=(flag) flag = flag ? true : false if started? and @use_ssl != flag raise IOError, "use_ssl value changed, but session already started" end @use_ssl = flag end SSL_IVNAMES = [ :@ca_file, :@ca_path, :@cert, :@cert_store, :@ciphers, :@extra_chain_cert, :@key, :@ssl_timeout, :@ssl_version, :@min_version, :@max_version, :@verify_callback, :@verify_depth, :@verify_mode, :@verify_hostname, ] # :nodoc: SSL_ATTRIBUTES = [ :ca_file, :ca_path, :cert, :cert_store, :ciphers, :extra_chain_cert, :key, :ssl_timeout, :ssl_version, :min_version, :max_version, :verify_callback, :verify_depth, :verify_mode, :verify_hostname, ] # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file # Sets or returns the path of to CA directory # containing certification files in PEM format. attr_accessor :ca_path # Sets or returns the OpenSSL::X509::Certificate object # to be used for client certification. attr_accessor :cert # Sets or returns the X509::Store to be used for verifying peer certificate. attr_accessor :cert_store # Sets or returns the available SSL ciphers. # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D]. attr_accessor :ciphers # Sets or returns the extra X509 certificates to be added to the certificate chain. # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate]. attr_accessor :extra_chain_cert # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. attr_accessor :key # Sets or returns the SSL timeout seconds. attr_accessor :ssl_timeout # Sets or returns the SSL version. # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D]. attr_accessor :ssl_version # Sets or returns the minimum SSL version. # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D]. attr_accessor :min_version # Sets or returns the maximum SSL version. # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D]. attr_accessor :max_version # Sets or returns the callback for the server certification verification. attr_accessor :verify_callback # Sets or returns the maximum depth for the certificate chain verification. attr_accessor :verify_depth # Sets or returns the flags for server the certification verification # at the beginning of the SSL/TLS session. # OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable. attr_accessor :verify_mode # Sets or returns whether to verify that the server certificate is valid # for the hostname. # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode]. attr_accessor :verify_hostname # Returns the X509 certificate chain (an array of strings) # for the session's socket peer, # or +nil+ if none. def peer_cert if not use_ssl? or not @socket return nil end @socket.io.peer_cert end # Starts an \HTTP session. # # Without a block, returns +self+: # # http = Gem::Net::HTTP.new(hostname) # # => # # http.start # # => # # http.started? # => true # http.finish # # With a block, calls the block with +self+, # finishes the session when the block exits, # and returns the block's value: # # http.start do |http| # http # end # # => # # http.started? # => false # def start # :yield: http raise IOError, 'HTTP session already opened' if @started if block_given? begin do_start return yield(self) ensure do_finish end end do_start self end def do_start connect @started = true end private :do_start def connect if use_ssl? # reference early to load OpenSSL before connecting, # as OpenSSL may take time to load. @ssl_context = OpenSSL::SSL::SSLContext.new end if proxy? then conn_addr = proxy_address conn_port = proxy_port else conn_addr = conn_address conn_port = port end debug "opening connection to #{conn_addr}:#{conn_port}..." s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { begin TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) rescue => e raise e, "Failed to open TCP connection to " + "#{conn_addr}:#{conn_port} (#{e.message})" end } s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? if proxy? plain_sock = BufferedIO.new(s, read_timeout: @read_timeout, write_timeout: @write_timeout, continue_timeout: @continue_timeout, debug_output: @debug_output) buf = +"CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n" \ "Host: #{@address}:#{@port}\r\n" if proxy_user credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0') buf << "Proxy-Authorization: Basic #{credential}\r\n" end buf << "\r\n" plain_sock.write(buf) HTTPResponse.read_new(plain_sock).value # assuming nothing left in buffers after successful CONNECT response end ssl_parameters = Hash.new iv_list = instance_variables SSL_IVNAMES.each_with_index do |ivname, i| if iv_list.include?(ivname) value = instance_variable_get(ivname) unless value.nil? ssl_parameters[SSL_ATTRIBUTES[i]] = value end end end @ssl_context.set_params(ssl_parameters) unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby @ssl_context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE end if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } end # Still do the post_connection_check below even if connecting # to IP address verify_hostname = @ssl_context.verify_hostname # Server Name Indication (SNI) RFC 3546/6066 case @address when Gem::Resolv::IPv4::Regex, Gem::Resolv::IPv6::Regex # don't set SNI, as IP addresses in SNI is not valid # per RFC 6066, section 3. # Avoid openssl warning @ssl_context.verify_hostname = false else ssl_host_address = @address end debug "starting SSL for #{conn_addr}:#{conn_port}..." s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) s.sync_close = true s.hostname = ssl_host_address if s.respond_to?(:hostname=) && ssl_host_address if @ssl_session and Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout s.session = @ssl_session end ssl_socket_connect(s, @open_timeout) if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && verify_hostname s.post_connection_check(@address) end debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}" end @socket = BufferedIO.new(s, read_timeout: @read_timeout, write_timeout: @write_timeout, continue_timeout: @continue_timeout, debug_output: @debug_output) @last_communicated = nil on_connect rescue => exception if s debug "Conn close because of connect error #{exception}" s.close end raise end private :connect def on_connect end private :on_connect # Finishes the \HTTP session: # # http = Gem::Net::HTTP.new(hostname) # http.start # http.started? # => true # http.finish # => nil # http.started? # => false # # Raises IOError if not in a session. def finish raise IOError, 'HTTP session not yet started' unless started? do_finish end def do_finish @started = false @socket.close if @socket @socket = nil end private :do_finish # # proxy # public # no proxy @is_proxy_class = false @proxy_from_env = false @proxy_addr = nil @proxy_port = nil @proxy_user = nil @proxy_pass = nil # Creates an \HTTP proxy class which behaves like \Gem::Net::HTTP, but # performs all access via the specified proxy. # # This class is obsolete. You may pass these same parameters directly to # \Gem::Net::HTTP.new. See Gem::Net::HTTP.new for details of the arguments. def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc: return self unless p_addr Class.new(self) { @is_proxy_class = true if p_addr == :ENV then @proxy_from_env = true @proxy_address = nil @proxy_port = nil else @proxy_from_env = false @proxy_address = p_addr @proxy_port = p_port || default_port end @proxy_user = p_user @proxy_pass = p_pass } end class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? defined?(@is_proxy_class) ? @is_proxy_class : false end # Returns the address of the proxy host, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_address # Returns the port number of the proxy host, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_port # Returns the user name for accessing the proxy, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_user # Returns the password for accessing the proxy, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_pass end # Returns +true+ if a proxy server is defined, +false+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy? !!(@proxy_from_env ? proxy_uri : @proxy_address) end # Returns +true+ if the proxy server is defined in the environment, # +false+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_from_env? @proxy_from_env end # The proxy URI determined from the environment for this connection. def proxy_uri # :nodoc: return if @proxy_uri == false @proxy_uri ||= Gem::URI::HTTP.new( "http", nil, address, port, nil, nil, nil, nil, nil ).find_proxy || false @proxy_uri || nil end # Returns the address of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_address if @proxy_from_env then proxy_uri&.hostname else @proxy_address end end # Returns the port number of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_port if @proxy_from_env then proxy_uri&.port else @proxy_port end end # Returns the user name of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_user if @proxy_from_env user = proxy_uri&.user unescape(user) if user else @proxy_user end end # Returns the password of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_pass if @proxy_from_env pass = proxy_uri&.password unescape(pass) if pass else @proxy_pass end end alias proxyaddr proxy_address #:nodoc: obsolete alias proxyport proxy_port #:nodoc: obsolete private def unescape(value) require 'cgi/util' CGI.unescape(value) end # without proxy, obsolete def conn_address # :nodoc: @ipaddr || address() end def conn_port # :nodoc: port() end def edit_path(path) if proxy? if path.start_with?("ftp://") || use_ssl? path else "http://#{addr_port}#{path}" end else path end end # # HTTP operations # public # :call-seq: # get(path, initheader = nil) {|res| ... } # # Sends a GET request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Get object # created from string +path+ and initial headers hash +initheader+. # # With a block given, calls the block with the response body: # # http = Gem::Net::HTTP.new(hostname) # http.get('/todos/1') do |res| # p res # end # => # # # Output: # # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}" # # With no block given, simply returns the response object: # # http.get('/') # => # # # Related: # # - Gem::Net::HTTP::Get: request class for \HTTP method GET. # - Gem::Net::HTTP.get: sends GET request, returns response body. # def get(path, initheader = nil, dest = nil, &block) # :yield: +body_segment+ res = nil request(Get.new(path, initheader)) {|r| r.read_body dest, &block res = r } res end # Sends a HEAD request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Head object # created from string +path+ and initial headers hash +initheader+: # # res = http.head('/todos/1') # => # # res.body # => nil # res.to_hash.take(3) # # => # [["date", ["Wed, 15 Feb 2023 15:25:42 GMT"]], # ["content-type", ["application/json; charset=utf-8"]], # ["connection", ["close"]]] # def head(path, initheader = nil) request(Head.new(path, initheader)) end # :call-seq: # post(path, data, initheader = nil) {|res| ... } # # Sends a POST request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Post object # created from string +path+, string +data+, and initial headers hash +initheader+. # # With a block given, calls the block with the response body: # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.post('/todos', data) do |res| # p res # end # => # # # Output: # # "{\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\",\n \"id\": 201\n}" # # With no block given, simply returns the response object: # # http.post('/todos', data) # => # # # Related: # # - Gem::Net::HTTP::Post: request class for \HTTP method POST. # - Gem::Net::HTTP.post: sends POST request, returns response body. # def post(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+ send_entity(path, data, initheader, dest, Post, &block) end # :call-seq: # patch(path, data, initheader = nil) {|res| ... } # # Sends a PATCH request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Patch object # created from string +path+, string +data+, and initial headers hash +initheader+. # # With a block given, calls the block with the response body: # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.patch('/todos/1', data) do |res| # p res # end # => # # # Output: # # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false,\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\"\n}" # # With no block given, simply returns the response object: # # http.patch('/todos/1', data) # => # # def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+ send_entity(path, data, initheader, dest, Patch, &block) end # Sends a PUT request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Put object # created from string +path+, string +data+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.put('/todos/1', data) # => # # def put(path, data, initheader = nil) request(Put.new(path, initheader), data) end # Sends a PROPPATCH request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Proppatch object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.proppatch('/todos/1', data) # def proppatch(path, body, initheader = nil) request(Proppatch.new(path, initheader), body) end # Sends a LOCK request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Lock object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.lock('/todos/1', data) # def lock(path, body, initheader = nil) request(Lock.new(path, initheader), body) end # Sends an UNLOCK request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Unlock object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.unlock('/todos/1', data) # def unlock(path, body, initheader = nil) request(Unlock.new(path, initheader), body) end # Sends an Options request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Options object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.options('/') # def options(path, initheader = nil) request(Options.new(path, initheader)) end # Sends a PROPFIND request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Propfind object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.propfind('/todos/1', data) # def propfind(path, body = nil, initheader = {'Depth' => '0'}) request(Propfind.new(path, initheader), body) end # Sends a DELETE request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Delete object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.delete('/todos/1') # def delete(path, initheader = {'Depth' => 'Infinity'}) request(Delete.new(path, initheader)) end # Sends a MOVE request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Move object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.move('/todos/1') # def move(path, initheader = nil) request(Move.new(path, initheader)) end # Sends a COPY request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Copy object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.copy('/todos/1') # def copy(path, initheader = nil) request(Copy.new(path, initheader)) end # Sends a MKCOL request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Mkcol object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http.mkcol('/todos/1', data) # http = Gem::Net::HTTP.new(hostname) # def mkcol(path, body = nil, initheader = nil) request(Mkcol.new(path, initheader), body) end # Sends a TRACE request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Trace object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.trace('/todos/1') # def trace(path, initheader = nil) request(Trace.new(path, initheader)) end # Sends a GET request to the server; # forms the response into a Gem::Net::HTTPResponse object. # # The request is based on the Gem::Net::HTTP::Get object # created from string +path+ and initial headers hash +initheader+. # # With no block given, returns the response object: # # http = Gem::Net::HTTP.new(hostname) # http.request_get('/todos') # => # # # With a block given, calls the block with the response object # and returns the response object: # # http.request_get('/todos') do |res| # p res # end # => # # # Output: # # # # def request_get(path, initheader = nil, &block) # :yield: +response+ request(Get.new(path, initheader), &block) end # Sends a HEAD request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Head object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.head('/todos/1') # => # # def request_head(path, initheader = nil, &block) request(Head.new(path, initheader), &block) end # Sends a POST request to the server; # forms the response into a Gem::Net::HTTPResponse object. # # The request is based on the Gem::Net::HTTP::Post object # created from string +path+, string +data+, and initial headers hash +initheader+. # # With no block given, returns the response object: # # http = Gem::Net::HTTP.new(hostname) # http.post('/todos', 'xyzzy') # # => # # # With a block given, calls the block with the response body # and returns the response object: # # http.post('/todos', 'xyzzy') do |res| # p res # end # => # # # Output: # # "{\n \"xyzzy\": \"\",\n \"id\": 201\n}" # def request_post(path, data, initheader = nil, &block) # :yield: +response+ request Post.new(path, initheader), data, &block end # Sends a PUT request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Put object # created from string +path+, string +data+, and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.put('/todos/1', 'xyzzy') # # => # # def request_put(path, data, initheader = nil, &block) #:nodoc: request Put.new(path, initheader), data, &block end alias get2 request_get #:nodoc: obsolete alias head2 request_head #:nodoc: obsolete alias post2 request_post #:nodoc: obsolete alias put2 request_put #:nodoc: obsolete # Sends an \HTTP request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTPRequest object # created from string +path+, string +data+, and initial headers hash +header+. # That object is an instance of the # {subclass of Gem::Net::HTTPRequest}[rdoc-ref:Gem::Net::HTTPRequest@Request+Subclasses], # that corresponds to the given uppercase string +name+, # which must be # an {HTTP request method}[https://en.wikipedia.org/wiki/HTTP#Request_methods] # or a {WebDAV request method}[https://en.wikipedia.org/wiki/WebDAV#Implementation]. # # Examples: # # http = Gem::Net::HTTP.new(hostname) # http.send_request('GET', '/todos/1') # # => # # http.send_request('POST', '/todos', 'xyzzy') # # => # # def send_request(name, path, data = nil, header = nil) has_response_body = name != 'HEAD' r = HTTPGenericRequest.new(name,(data ? true : false),has_response_body,path,header) request r, data end # Sends the given request +req+ to the server; # forms the response into a Gem::Net::HTTPResponse object. # # The given +req+ must be an instance of a # {subclass of Gem::Net::HTTPRequest}[rdoc-ref:Gem::Net::HTTPRequest@Request+Subclasses]. # Argument +body+ should be given only if needed for the request. # # With no block given, returns the response object: # # http = Gem::Net::HTTP.new(hostname) # # req = Gem::Net::HTTP::Get.new('/todos/1') # http.request(req) # # => # # # req = Gem::Net::HTTP::Post.new('/todos') # http.request(req, 'xyzzy') # # => # # # With a block given, calls the block with the response and returns the response: # # req = Gem::Net::HTTP::Get.new('/todos/1') # http.request(req) do |res| # p res # end # => # # # Output: # # # # def request(req, body = nil, &block) # :yield: +response+ unless started? start { req['connection'] ||= 'close' return request(req, body, &block) } end if proxy_user() req.proxy_basic_auth proxy_user(), proxy_pass() unless use_ssl? end req.set_body_internal body res = transport_request(req, &block) if sspi_auth?(res) sspi_auth(req) res = transport_request(req, &block) end res end private # Executes a request which uses a representation # and returns its body. def send_entity(path, data, initheader, dest, type, &block) res = nil request(type.new(path, initheader), data) {|r| r.read_body dest, &block res = r } res end IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: def transport_request(req) count = 0 begin begin_transport req res = catch(:response) { begin req.exec @socket, @curr_http_version, edit_path(req.path) rescue Errno::EPIPE # Failure when writing full request, but we can probably # still read the received response. end begin res = HTTPResponse.read_new(@socket) res.decode_content = req.decode_content res.body_encoding = @response_body_encoding res.ignore_eof = @ignore_eof end while res.kind_of?(HTTPInformation) res.uri = req.uri res } res.reading_body(@socket, req.response_body_permitted?) { yield res if block_given? } rescue Gem::Net::OpenTimeout raise rescue Gem::Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, Errno::ETIMEDOUT, # avoid a dependency on OpenSSL defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : IOError, Gem::Timeout::Error => exception if count < max_retries && IDEMPOTENT_METHODS_.include?(req.method) count += 1 @socket.close if @socket debug "Conn close because of error #{exception}, and retry" retry end debug "Conn close because of error #{exception}" @socket.close if @socket raise end end_transport req, res res rescue => exception debug "Conn close because of error #{exception}" @socket.close if @socket raise exception end def begin_transport(req) if @socket.closed? connect elsif @last_communicated if @last_communicated + @keep_alive_timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC) debug 'Conn close because of keep_alive_timeout' @socket.close connect elsif @socket.io.to_io.wait_readable(0) && @socket.eof? debug "Conn close because of EOF" @socket.close connect end end if not req.response_body_permitted? and @close_on_empty_response req['connection'] ||= 'close' end req.update_uri address, port, use_ssl? req['host'] ||= addr_port() end def end_transport(req, res) @curr_http_version = res.http_version @last_communicated = nil if @socket.closed? debug 'Conn socket closed' elsif not res.body and @close_on_empty_response debug 'Conn close' @socket.close elsif keep_alive?(req, res) debug 'Conn keep-alive' @last_communicated = Process.clock_gettime(Process::CLOCK_MONOTONIC) else debug 'Conn close' @socket.close end end def keep_alive?(req, res) return false if req.connection_close? if @curr_http_version <= '1.0' res.connection_keep_alive? else # HTTP/1.1 or later not res.connection_close? end end def sspi_auth?(res) return false unless @sspi_enabled if res.kind_of?(HTTPProxyAuthenticationRequired) and proxy? and res["Proxy-Authenticate"].include?("Negotiate") begin require 'win32/sspi' true rescue LoadError false end else false end end def sspi_auth(req) n = Win32::SSPI::NegotiateAuth.new req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}" # Some versions of ISA will close the connection if this isn't present. req["Connection"] = "Keep-Alive" req["Proxy-Connection"] = "Keep-Alive" res = transport_request(req) authphrase = res["Proxy-Authenticate"] or return res req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication(authphrase)}" rescue => err raise HTTPAuthenticationError.new('HTTP authentication failed', err) end # # utils # private def addr_port addr = address addr = "[#{addr}]" if addr.include?(":") default_port = use_ssl? ? HTTP.https_default_port : HTTP.http_default_port default_port == port ? addr : "#{addr}:#{port}" end # Adds a message to debugging output def debug(msg) return unless @debug_output @debug_output << msg @debug_output << "\n" end alias_method :D, :debug end end require_relative 'http/exceptions' require_relative 'http/header' require_relative 'http/generic_request' require_relative 'http/request' require_relative 'http/requests' require_relative 'http/response' require_relative 'http/responses' require_relative 'http/proxy_delta' require_relative 'http/backward' PK!  0rubygems/vendor/net-http/lib/net/http/request.rbnu[# frozen_string_literal: true # This class is the base class for \Gem::Net::HTTP request classes. # The class should not be used directly; # instead you should use its subclasses, listed below. # # == Creating a Request # # An request object may be created with either a Gem::URI or a string hostname: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('https://jsonplaceholder.typicode.com/') # req = Gem::Net::HTTP::Get.new(uri) # => # # req = Gem::Net::HTTP::Get.new(uri.hostname) # => # # # And with any of the subclasses: # # req = Gem::Net::HTTP::Head.new(uri) # => # # req = Gem::Net::HTTP::Post.new(uri) # => # # req = Gem::Net::HTTP::Put.new(uri) # => # # # ... # # The new instance is suitable for use as the argument to Gem::Net::HTTP#request. # # == Request Headers # # A new request object has these header fields by default: # # req.to_hash # # => # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], # "accept"=>["*/*"], # "user-agent"=>["Ruby"], # "host"=>["jsonplaceholder.typicode.com"]} # # See: # # - {Request header Accept-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Accept-Encoding] # and {Compression and Decompression}[rdoc-ref:Gem::Net::HTTP@Compression+and+Decompression]. # - {Request header Accept}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#accept-request-header]. # - {Request header User-Agent}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#user-agent-request-header]. # - {Request header Host}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#host-request-header]. # # You can add headers or override default headers: # # # res = Gem::Net::HTTP::Get.new(uri, {'foo' => '0', 'bar' => '1'}) # # This class (and therefore its subclasses) also includes (indirectly) # module Gem::Net::HTTPHeader, which gives access to its # {methods for setting headers}[rdoc-ref:Gem::Net::HTTPHeader@Setters]. # # == Request Subclasses # # Subclasses for HTTP requests: # # - Gem::Net::HTTP::Get # - Gem::Net::HTTP::Head # - Gem::Net::HTTP::Post # - Gem::Net::HTTP::Put # - Gem::Net::HTTP::Delete # - Gem::Net::HTTP::Options # - Gem::Net::HTTP::Trace # - Gem::Net::HTTP::Patch # # Subclasses for WebDAV requests: # # - Gem::Net::HTTP::Propfind # - Gem::Net::HTTP::Proppatch # - Gem::Net::HTTP::Mkcol # - Gem::Net::HTTP::Copy # - Gem::Net::HTTP::Move # - Gem::Net::HTTP::Lock # - Gem::Net::HTTP::Unlock # class Gem::Net::HTTPRequest < Gem::Net::HTTPGenericRequest # Creates an HTTP request object for +path+. # # +initheader+ are the default headers to use. Gem::Net::HTTP adds # Accept-Encoding to enable compression of the response body unless # Accept-Encoding or Range are supplied in +initheader+. def initialize(path, initheader = nil) super self.class::METHOD, self.class::REQUEST_HAS_BODY, self.class::RESPONSE_HAS_BODY, path, initheader end end PK!?sDII1rubygems/vendor/net-http/lib/net/http/backward.rbnu[# frozen_string_literal: true # for backward compatibility # :enddoc: class Gem::Net::HTTP ProxyMod = ProxyDelta deprecate_constant :ProxyMod end module Gem::Net::NetPrivate HTTPRequest = ::Gem::Net::HTTPRequest deprecate_constant :HTTPRequest end module Gem::Net HTTPSession = HTTP HTTPInformationCode = HTTPInformation HTTPSuccessCode = HTTPSuccess HTTPRedirectionCode = HTTPRedirection HTTPRetriableCode = HTTPRedirection HTTPClientErrorCode = HTTPClientError HTTPFatalErrorCode = HTTPClientError HTTPServerErrorCode = HTTPServerError HTTPResponseReceiver = HTTPResponse HTTPResponceReceiver = HTTPResponse # Typo since 2001 deprecate_constant :HTTPSession, :HTTPInformationCode, :HTTPSuccessCode, :HTTPRedirectionCode, :HTTPRetriableCode, :HTTPClientErrorCode, :HTTPFatalErrorCode, :HTTPServerErrorCode, :HTTPResponseReceiver, :HTTPResponceReceiver end PK!M?i4rubygems/vendor/net-http/lib/net/http/proxy_delta.rbnu[# frozen_string_literal: true module Gem::Net::HTTP::ProxyDelta #:nodoc: internal use only private def conn_address proxy_address() end def conn_port proxy_port() end def edit_path(path) use_ssl? ? path : "http://#{addr_port()}#{path}" end end PK!!.1.18rubygems/vendor/net-http/lib/net/http/generic_request.rbnu[# frozen_string_literal: true # # \HTTPGenericRequest is the parent of the Gem::Net::HTTPRequest class. # # Do not use this directly; instead, use a subclass of Gem::Net::HTTPRequest. # # == About the Examples # # :include: doc/net-http/examples.rdoc # class Gem::Net::HTTPGenericRequest include Gem::Net::HTTPHeader def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: @method = m @request_has_body = reqbody @response_has_body = resbody if Gem::URI === uri_or_path then raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path hostname = uri_or_path.hostname raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup host = @uri.hostname.dup host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup end @decode_content = false if Gem::Net::HTTP::HAVE_ZLIB then if !initheader || !initheader.keys.any? { |k| %w[accept-encoding range].include? k.downcase } then @decode_content = true if @response_has_body initheader = initheader ? initheader.dup : {} initheader["accept-encoding"] = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" end end initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' self['Host'] ||= host if host @body = nil @body_stream = nil @body_data = nil end # Returns the string method name for the request: # # Gem::Net::HTTP::Get.new(uri).method # => "GET" # Gem::Net::HTTP::Post.new(uri).method # => "POST" # attr_reader :method # Returns the string path for the request: # # Gem::Net::HTTP::Get.new(uri).path # => "/" # Gem::Net::HTTP::Post.new('example.com').path # => "example.com" # attr_reader :path # Returns the Gem::URI object for the request, or +nil+ if none: # # Gem::Net::HTTP::Get.new(uri).uri # # => # # Gem::Net::HTTP::Get.new('example.com').uri # => nil # attr_reader :uri # Returns +false+ if the request's header 'Accept-Encoding' # has been set manually or deleted # (indicating that the user intends to handle encoding in the response), # +true+ otherwise: # # req = Gem::Net::HTTP::Get.new(uri) # => # # req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" # req.decode_content # => true # req['Accept-Encoding'] = 'foo' # req.decode_content # => false # req.delete('Accept-Encoding') # req.decode_content # => false # attr_reader :decode_content # Returns a string representation of the request: # # Gem::Net::HTTP::Post.new(uri).inspect # => "#" # def inspect "\#<#{self.class} #{@method}>" end ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. def []=(key, val) # :nodoc: @decode_content = false if key.downcase == 'accept-encoding' super key, val end # Returns whether the request may have a body: # # Gem::Net::HTTP::Post.new(uri).request_body_permitted? # => true # Gem::Net::HTTP::Get.new(uri).request_body_permitted? # => false # def request_body_permitted? @request_has_body end # Returns whether the response may have a body: # # Gem::Net::HTTP::Post.new(uri).response_body_permitted? # => true # Gem::Net::HTTP::Head.new(uri).response_body_permitted? # => false # def response_body_permitted? @response_has_body end def body_exist? # :nodoc: warn "Gem::Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE response_body_permitted? end # Returns the string body for the request, or +nil+ if there is none: # # req = Gem::Net::HTTP::Post.new(uri) # req.body # => nil # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" # attr_reader :body # Sets the body for the request: # # req = Gem::Net::HTTP::Post.new(uri) # req.body # => nil # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" # def body=(str) @body = str @body_stream = nil @body_data = nil str end # Returns the body stream object for the request, or +nil+ if there is none: # # req = Gem::Net::HTTP::Post.new(uri) # => # # req.body_stream # => nil # require 'stringio' # req.body_stream = StringIO.new('xyzzy') # => # # req.body_stream # => # # attr_reader :body_stream # Sets the body stream for the request: # # req = Gem::Net::HTTP::Post.new(uri) # => # # req.body_stream # => nil # require 'stringio' # req.body_stream = StringIO.new('xyzzy') # => # # req.body_stream # => # # def body_stream=(input) @body = nil @body_stream = input @body_data = nil input end def set_body_internal(str) #:nodoc: internal use only raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream) self.body = str if str if @body.nil? && @body_stream.nil? && @body_data.nil? && request_body_permitted? self.body = '' end end # # write # def exec(sock, ver, path) #:nodoc: internal use only if @body send_request_with_body sock, ver, path, @body elsif @body_stream send_request_with_body_stream sock, ver, path, @body_stream elsif @body_data send_request_with_body_data sock, ver, path, @body_data else write_header sock, ver, path end end def update_uri(addr, port, ssl) # :nodoc: internal use only # reflect the connection and @path to @uri return unless @uri if ssl scheme = 'https' klass = Gem::URI::HTTPS else scheme = 'http' klass = Gem::URI::HTTP end if host = self['host'] host.sub!(/:.*/m, '') elsif host = @uri.host else host = addr end # convert the class of the Gem::URI if @uri.is_a?(klass) @uri.host = host @uri.port = port else @uri = klass.new( scheme, @uri.userinfo, host, port, nil, @uri.path, nil, @uri.query, nil) end end private class Chunker #:nodoc: def initialize(sock) @sock = sock @prev = nil end def write(buf) # avoid memcpy() of buf, buf can huge and eat memory bandwidth rv = buf.bytesize @sock.write("#{rv.to_s(16)}\r\n", buf, "\r\n") rv end def finish @sock.write("0\r\n\r\n") end end def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body end def send_request_with_body_stream(sock, ver, path, f) unless content_length() or chunked? raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? chunker = Chunker.new(sock) IO.copy_stream(f, chunker) chunker.finish else IO.copy_stream(f, sock) end end def send_request_with_body_data(sock, ver, path, params) if /\Amultipart\/form-data\z/i !~ self.content_type self.content_type = 'application/x-www-form-urlencoded' return send_request_with_body(sock, ver, path, Gem::URI.encode_www_form(params)) end opt = @form_option.dup require 'securerandom' unless defined?(SecureRandom) opt[:boundary] ||= SecureRandom.urlsafe_base64(40) self.set_content_type(self.content_type, boundary: opt[:boundary]) if chunked? write_header sock, ver, path encode_multipart_form_data(sock, params, opt) else require 'tempfile' file = Tempfile.new('multipart') file.binmode encode_multipart_form_data(file, params, opt) file.rewind self.content_length = file.size write_header sock, ver, path IO.copy_stream(file, sock) file.close(true) end end def encode_multipart_form_data(out, params, opt) charset = opt[:charset] boundary = opt[:boundary] require 'securerandom' unless defined?(SecureRandom) boundary ||= SecureRandom.urlsafe_base64(40) chunked_p = chunked? buf = +'' params.each do |key, value, h={}| key = quote_string(key, charset) filename = h.key?(:filename) ? h[:filename] : value.respond_to?(:to_path) ? File.basename(value.to_path) : nil buf << "--#{boundary}\r\n" if filename filename = quote_string(filename, charset) type = h[:content_type] || 'application/octet-stream' buf << "Content-Disposition: form-data; " \ "name=\"#{key}\"; filename=\"#{filename}\"\r\n" \ "Content-Type: #{type}\r\n\r\n" if !out.respond_to?(:write) || !value.respond_to?(:read) # if +out+ is not an IO or +value+ is not an IO buf << (value.respond_to?(:read) ? value.read : value) elsif value.respond_to?(:size) && chunked_p # if +out+ is an IO and +value+ is a File, use IO.copy_stream flush_buffer(out, buf, chunked_p) out << "%x\r\n" % value.size if chunked_p IO.copy_stream(value, out) out << "\r\n" if chunked_p else # +out+ is an IO, and +value+ is not a File but an IO flush_buffer(out, buf, chunked_p) 1 while flush_buffer(out, value.read(4096), chunked_p) end else # non-file field: # HTML5 says, "The parts of the generated multipart/form-data # resource that correspond to non-file fields must not have a # Content-Type header specified." buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n" buf << (value.respond_to?(:read) ? value.read : value) end buf << "\r\n" end buf << "--#{boundary}--\r\n" flush_buffer(out, buf, chunked_p) out << "0\r\n\r\n" if chunked_p end def quote_string(str, charset) str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset str.gsub(/[\\"]/, '\\\\\&') end def flush_buffer(out, buf, chunked_p) return unless buf out << "%x\r\n"%buf.bytesize if chunked_p out << buf out << "\r\n" if chunked_p buf.clear end def supply_default_content_type return if content_type() warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE set_content_type 'application/x-www-form-urlencoded' end ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. def wait_for_continue(sock, ver) if ver >= '1.1' and @header['expect'] and @header['expect'].include?('100-continue') if sock.io.to_io.wait_readable(sock.continue_timeout) res = Gem::Net::HTTPResponse.read_new(sock) unless res.kind_of?(Gem::Net::HTTPContinue) res.decode_content = @decode_content throw :response, res end end end end def write_header(sock, ver, path) reqline = "#{@method} #{path} HTTP/#{ver}" if /[\r\n]/ =~ reqline raise ArgumentError, "A Request-Line must not contain CR or LF" end buf = +'' buf << reqline << "\r\n" each_capitalized do |k,v| buf << "#{k}: #{v}\r\n" end buf << "\r\n" sock.write buf end end PK!ebo7o71rubygems/vendor/net-http/lib/net/http/requests.rbnu[# frozen_string_literal: true # HTTP/1.1 methods --- RFC2616 # \Class for representing # {HTTP method GET}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#GET_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Get.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. # # Related: # # - Gem::Net::HTTP.get: sends +GET+ request, returns response body. # - Gem::Net::HTTP#get: sends +GET+ request, returns response object. # class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method HEAD}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#HEAD_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Head.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: no. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. # # Related: # # - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object. # class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false end # \Class for representing # {HTTP method POST}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#POST_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts' # req = Gem::Net::HTTP::Post.new(uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: yes. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. # # Related: # # - Gem::Net::HTTP.post: sends +POST+ request, returns response object. # - Gem::Net::HTTP#post: sends +POST+ request, returns response object. # class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method PUT}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PUT_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts' # req = Gem::Net::HTTP::Put.new(uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: yes. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method DELETE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#DELETE_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts/1' # req = Gem::Net::HTTP::Delete.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method OPTIONS}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#OPTIONS_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Options.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method TRACE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#TRACE_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Trace.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: no. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method PATCH}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PATCH_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts' # req = Gem::Net::HTTP::Patch.new(uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: yes. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # # WebDAV methods --- RFC2518 # # \Class for representing # {WebDAV method PROPFIND}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Propfind.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method PROPPATCH}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Proppatch.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method MKCOL}[http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Mkcol.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method COPY}[http://www.webdav.org/specs/rfc4918.html#METHOD_COPY]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Copy.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object. # class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method MOVE}[http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Move.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object. # class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method LOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Lock.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method UNLOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Unlock.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end PK!`9d /rubygems/vendor/net-http/lib/net/http/status.rbnu[# frozen_string_literal: true require_relative '../http' if $0 == __FILE__ require 'open-uri' File.foreach(__FILE__) do |line| puts line break if line.start_with?('end') end puts puts "Gem::Net::HTTP::STATUS_CODES = {" url = "https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv" Gem::URI(url).read.each_line do |line| code, mes, = line.split(',') next if ['(Unused)', 'Unassigned', 'Description'].include?(mes) puts " #{code} => '#{mes}'," end puts "} # :nodoc:" end Gem::Net::HTTP::STATUS_CODES = { 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 103 => 'Early Hints', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Content Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 421 => 'Misdirected Request', 422 => 'Unprocessable Content', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Too Early', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended (OBSOLETED)', 511 => 'Network Authentication Required', } # :nodoc: PK! TT3rubygems/vendor/net-http/lib/net/http/exceptions.rbnu[# frozen_string_literal: true module Gem::Net # Gem::Net::HTTP exception class. # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use # its subclasses. module HTTPExceptions def initialize(msg, res) #:nodoc: super msg @response = res end attr_reader :response alias data response #:nodoc: obsolete end class HTTPError < ProtocolError include HTTPExceptions end class HTTPRetriableError < ProtoRetriableError include HTTPExceptions end class HTTPClientException < ProtoServerError include HTTPExceptions end class HTTPFatalError < ProtoFatalError include HTTPExceptions end # We cannot use the name "HTTPServerError", it is the name of the response. HTTPServerException = HTTPClientException # :nodoc: deprecate_constant(:HTTPServerException) end PK!qt!a/rubygems/vendor/net-http/lib/net/http/header.rbnu[# frozen_string_literal: true # # The \HTTPHeader module provides access to \HTTP headers. # # The module is included in: # # - Gem::Net::HTTPGenericRequest (and therefore Gem::Net::HTTPRequest). # - Gem::Net::HTTPResponse. # # The headers are a hash-like collection of key/value pairs called _fields_. # # == Request and Response Fields # # Headers may be included in: # # - A Gem::Net::HTTPRequest object: # the object's headers will be sent with the request. # Any fields may be defined in the request; # see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters]. # - A Gem::Net::HTTPResponse object: # the objects headers are usually those returned from the host. # Fields may be retrieved from the object; # see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters] # and {Iterators}[rdoc-ref:Gem::Net::HTTPHeader@Iterators]. # # Exactly which fields should be sent or expected depends on the host; # see: # # - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. # - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields]. # # == About the Examples # # :include: doc/net-http/examples.rdoc # # == Fields # # A header field is a key/value pair. # # === Field Keys # # A field key may be: # # - A string: Key 'Accept' is treated as if it were # 'Accept'.downcase; i.e., 'accept'. # - A symbol: Key :Accept is treated as if it were # :Accept.to_s.downcase; i.e., 'accept'. # # Examples: # # req = Gem::Net::HTTP::Get.new(uri) # req[:accept] # => "*/*" # req['Accept'] # => "*/*" # req['ACCEPT'] # => "*/*" # # req['accept'] = 'text/html' # req[:accept] = 'text/html' # req['ACCEPT'] = 'text/html' # # === Field Values # # A field value may be returned as an array of strings or as a string: # # - These methods return field values as arrays: # # - #get_fields: Returns the array value for the given key, # or +nil+ if it does not exist. # - #to_hash: Returns a hash of all header fields: # each key is a field name; its value is the array value for the field. # # - These methods return field values as string; # the string value for a field is equivalent to # self[key.downcase.to_s].join(', ')): # # - #[]: Returns the string value for the given key, # or +nil+ if it does not exist. # - #fetch: Like #[], but accepts a default value # to be returned if the key does not exist. # # The field value may be set: # # - #[]=: Sets the value for the given key; # the given value may be a string, a symbol, an array, or a hash. # - #add_field: Adds a given value to a value for the given key # (not overwriting the existing value). # - #delete: Deletes the field for the given key. # # Example field values: # # - \String: # # req['Accept'] = 'text/html' # => "text/html" # req['Accept'] # => "text/html" # req.get_fields('Accept') # => ["text/html"] # # - \Symbol: # # req['Accept'] = :text # => :text # req['Accept'] # => "text" # req.get_fields('Accept') # => ["text"] # # - Simple array: # # req[:foo] = %w[bar baz bat] # req[:foo] # => "bar, baz, bat" # req.get_fields(:foo) # => ["bar", "baz", "bat"] # # - Simple hash: # # req[:foo] = {bar: 0, baz: 1, bat: 2} # req[:foo] # => "bar, 0, baz, 1, bat, 2" # req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"] # # - Nested: # # req[:foo] = [%w[bar baz], {bat: 0, bam: 1}] # req[:foo] # => "bar, baz, bat, 0, bam, 1" # req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"] # # req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}} # req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1" # req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"] # # == Convenience Methods # # Various convenience methods retrieve values, set values, query values, # set form values, or iterate over fields. # # === Setters # # \Method #[]= can set any field, but does little to validate the new value; # some of the other setter methods provide some validation: # # - #[]=: Sets the string or array value for the given key. # - #add_field: Creates or adds to the array value for the given key. # - #basic_auth: Sets the string authorization header for 'Authorization'. # - #content_length=: Sets the integer length for field 'Content-Length. # - #content_type=: Sets the string value for field 'Content-Type'. # - #proxy_basic_auth: Sets the string authorization header for 'Proxy-Authorization'. # - #set_range: Sets the value for field 'Range'. # # === Form Setters # # - #set_form: Sets an HTML form data set. # - #set_form_data: Sets header fields and a body from HTML form data. # # === Getters # # \Method #[] can retrieve the value of any field that exists, # but always as a string; # some of the other getter methods return something different # from the simple string value: # # - #[]: Returns the string field value for the given key. # - #content_length: Returns the integer value of field 'Content-Length'. # - #content_range: Returns the Range value of field 'Content-Range'. # - #content_type: Returns the string value of field 'Content-Type'. # - #fetch: Returns the string field value for the given key. # - #get_fields: Returns the array field value for the given +key+. # - #main_type: Returns first part of the string value of field 'Content-Type'. # - #sub_type: Returns second part of the string value of field 'Content-Type'. # - #range: Returns an array of Range objects of field 'Range', or +nil+. # - #range_length: Returns the integer length of the range given in field 'Content-Range'. # - #type_params: Returns the string parameters for 'Content-Type'. # # === Queries # # - #chunked?: Returns whether field 'Transfer-Encoding' is set to 'chunked'. # - #connection_close?: Returns whether field 'Connection' is set to 'close'. # - #connection_keep_alive?: Returns whether field 'Connection' is set to 'keep-alive'. # - #key?: Returns whether a given key exists. # # === Iterators # # - #each_capitalized: Passes each field capitalized-name/value pair to the block. # - #each_capitalized_name: Passes each capitalized field name to the block. # - #each_header: Passes each field name/value pair to the block. # - #each_name: Passes each field name to the block. # - #each_value: Passes each string field value to the block. # module Gem::Net::HTTPHeader MAX_KEY_LENGTH = 1024 MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @header = {} return unless initheader initheader.each do |key, value| warn "net/http: duplicated HTTP header: #{key}", uplevel: 3 if key?(key) and $VERBOSE if value.nil? warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE else value = value.strip # raise error for invalid byte sequences if key.to_s.bytesize > MAX_KEY_LENGTH raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..." end if value.to_s.bytesize > MAX_FIELD_LENGTH raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}" end if value.count("\r\n") > 0 raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF" end @header[key.downcase.to_s] = [value] end end end def size #:nodoc: obsolete @header.size end alias length size #:nodoc: obsolete # Returns the string field value for the case-insensitive field +key+, # or +nil+ if there is no such key; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Connection'] # => "keep-alive" # res['Nosuch'] # => nil # # Note that some field values may be retrieved via convenience methods; # see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters]. def [](key) a = @header[key.downcase.to_s] or return nil a.join(', ') end # Sets the value for the case-insensitive +key+ to +val+, # overwriting the previous value if the field exists; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # req = Gem::Net::HTTP::Get.new(uri) # req['Accept'] # => "*/*" # req['Accept'] = 'text/html' # req['Accept'] # => "text/html" # # Note that some field values may be set via convenience methods; # see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters]. def []=(key, val) unless val @header.delete key.downcase.to_s return val end set_field(key, val) end # Adds value +val+ to the value array for field +key+ if the field exists; # creates the field with the given +key+ and +val+ if it does not exist. # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # req = Gem::Net::HTTP::Get.new(uri) # req.add_field('Foo', 'bar') # req['Foo'] # => "bar" # req.add_field('Foo', 'baz') # req['Foo'] # => "bar, baz" # req.add_field('Foo', %w[baz bam]) # req['Foo'] # => "bar, baz, baz, bam" # req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"] # def add_field(key, val) stringified_downcased_key = key.downcase.to_s if @header.key?(stringified_downcased_key) append_field_value(@header[stringified_downcased_key], val) else set_field(key, val) end end private def set_field(key, val) case val when Enumerable ary = [] append_field_value(ary, val) @header[key.downcase.to_s] = ary else val = val.to_s # for compatibility use to_s instead of to_str if val.b.count("\r\n") > 0 raise ArgumentError, 'header field value cannot include CR/LF' end @header[key.downcase.to_s] = [val] end end private def append_field_value(ary, val) case val when Enumerable val.each{|x| append_field_value(ary, x)} else val = val.to_s if /[\r\n]/n.match?(val.b) raise ArgumentError, 'header field value cannot include CR/LF' end ary.push val end end # Returns the array field value for the given +key+, # or +nil+ if there is no such field; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.get_fields('Connection') # => ["keep-alive"] # res.get_fields('Nosuch') # => nil # def get_fields(key) stringified_downcased_key = key.downcase.to_s return nil unless @header[stringified_downcased_key] @header[stringified_downcased_key].dup end # call-seq: # fetch(key, default_val = nil) {|key| ... } -> object # fetch(key, default_val = nil) -> value or default_val # # With a block, returns the string value for +key+ if it exists; # otherwise returns the value of the block; # ignores the +default_val+; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # # # Field exists; block not called. # res.fetch('Connection') do |value| # fail 'Cannot happen' # end # => "keep-alive" # # # Field does not exist; block called. # res.fetch('Nosuch') do |value| # value.downcase # end # => "nosuch" # # With no block, returns the string value for +key+ if it exists; # otherwise, returns +default_val+ if it was given; # otherwise raises an exception: # # res.fetch('Connection', 'Foo') # => "keep-alive" # res.fetch('Nosuch', 'Foo') # => "Foo" # res.fetch('Nosuch') # Raises KeyError. # def fetch(key, *args, &block) #:yield: +key+ a = @header.fetch(key.downcase.to_s, *args, &block) a.kind_of?(Array) ? a.join(', ') : a end # Calls the block with each key/value pair: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_header do |key, value| # p [key, value] if key.start_with?('c') # end # # Output: # # ["content-type", "application/json; charset=utf-8"] # ["connection", "keep-alive"] # ["cache-control", "max-age=43200"] # ["cf-cache-status", "HIT"] # ["cf-ray", "771d17e9bc542cf5-ORD"] # # Returns an enumerator if no block is given. # # Gem::Net::HTTPHeader#each is an alias for Gem::Net::HTTPHeader#each_header. def each_header #:yield: +key+, +value+ block_given? or return enum_for(__method__) { @header.size } @header.each do |k,va| yield k, va.join(', ') end end alias each each_header # Calls the block with each field key: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_key do |key| # p key if key.start_with?('c') # end # # Output: # # "content-type" # "connection" # "cache-control" # "cf-cache-status" # "cf-ray" # # Returns an enumerator if no block is given. # # Gem::Net::HTTPHeader#each_name is an alias for Gem::Net::HTTPHeader#each_key. def each_name(&block) #:yield: +key+ block_given? or return enum_for(__method__) { @header.size } @header.each_key(&block) end alias each_key each_name # Calls the block with each capitalized field name: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_capitalized_name do |key| # p key if key.start_with?('C') # end # # Output: # # "Content-Type" # "Connection" # "Cache-Control" # "Cf-Cache-Status" # "Cf-Ray" # # The capitalization is system-dependent; # see {Case Mapping}[https://docs.ruby-lang.org/en/master/case_mapping_rdoc.html]. # # Returns an enumerator if no block is given. def each_capitalized_name #:yield: +key+ block_given? or return enum_for(__method__) { @header.size } @header.each_key do |k| yield capitalize(k) end end # Calls the block with each string field value: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_value do |value| # p value if value.start_with?('c') # end # # Output: # # "chunked" # "cf-q-config;dur=6.0000002122251e-06" # "cloudflare" # # Returns an enumerator if no block is given. def each_value #:yield: +value+ block_given? or return enum_for(__method__) { @header.size } @header.each_value do |va| yield va.join(', ') end end # Removes the header for the given case-insensitive +key+ # (see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]); # returns the deleted value, or +nil+ if no such field exists: # # req = Gem::Net::HTTP::Get.new(uri) # req.delete('Accept') # => ["*/*"] # req.delete('Nosuch') # => nil # def delete(key) @header.delete(key.downcase.to_s) end # Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise: # # req = Gem::Net::HTTP::Get.new(uri) # req.key?('Accept') # => true # req.key?('Nosuch') # => false # def key?(key) @header.key?(key.downcase.to_s) end # Returns a hash of the key/value pairs: # # req = Gem::Net::HTTP::Get.new(uri) # req.to_hash # # => # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], # "accept"=>["*/*"], # "user-agent"=>["Ruby"], # "host"=>["jsonplaceholder.typicode.com"]} # def to_hash @header.dup end # Like #each_header, but the keys are returned in capitalized form. # # Gem::Net::HTTPHeader#canonical_each is an alias for Gem::Net::HTTPHeader#each_capitalized. def each_capitalized block_given? or return enum_for(__method__) { @header.size } @header.each do |k,v| yield capitalize(k), v.join(', ') end end alias canonical_each each_capitalized def capitalize(name) name.to_s.split(/-/).map {|s| s.capitalize }.join('-') end private :capitalize # Returns an array of Range objects that represent # the value of field 'Range', # or +nil+ if there is no such field; # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: # # req = Gem::Net::HTTP::Get.new(uri) # req['Range'] = 'bytes=0-99,200-299,400-499' # req.range # => [0..99, 200..299, 400..499] # req.delete('Range') # req.range # # => nil # def range return nil unless @header['range'] value = self['Range'] # byte-range-set = *( "," OWS ) ( byte-range-spec / suffix-byte-range-spec ) # *( OWS "," [ OWS ( byte-range-spec / suffix-byte-range-spec ) ] ) # corrected collected ABNF # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#section-5.4.1 # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#appendix-C # http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-19#section-3.2.5 unless /\Abytes=((?:,[ \t]*)*(?:\d+-\d*|-\d+)(?:[ \t]*,(?:[ \t]*\d+-\d*|-\d+)?)*)\z/ =~ value raise Gem::Net::HTTPHeaderSyntaxError, "invalid syntax for byte-ranges-specifier: '#{value}'" end byte_range_set = $1 result = byte_range_set.split(/,/).map {|spec| m = /(\d+)?\s*-\s*(\d+)?/i.match(spec) or raise Gem::Net::HTTPHeaderSyntaxError, "invalid byte-range-spec: '#{spec}'" d1 = m[1].to_i d2 = m[2].to_i if m[1] and m[2] if d1 > d2 raise Gem::Net::HTTPHeaderSyntaxError, "last-byte-pos MUST greater than or equal to first-byte-pos but '#{spec}'" end d1..d2 elsif m[1] d1..-1 elsif m[2] -d2..-1 else raise Gem::Net::HTTPHeaderSyntaxError, 'range is not specified' end } # if result.empty? # byte-range-set must include at least one byte-range-spec or suffix-byte-range-spec # but above regexp already denies it. if result.size == 1 && result[0].begin == 0 && result[0].end == -1 raise Gem::Net::HTTPHeaderSyntaxError, 'only one suffix-byte-range-spec with zero suffix-length' end result end # call-seq: # set_range(length) -> length # set_range(offset, length) -> range # set_range(begin..length) -> range # # Sets the value for field 'Range'; # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: # # With argument +length+: # # req = Gem::Net::HTTP::Get.new(uri) # req.set_range(100) # => 100 # req['Range'] # => "bytes=0-99" # # With arguments +offset+ and +length+: # # req.set_range(100, 100) # => 100...200 # req['Range'] # => "bytes=100-199" # # With argument +range+: # # req.set_range(100..199) # => 100..199 # req['Range'] # => "bytes=100-199" # # Gem::Net::HTTPHeader#range= is an alias for Gem::Net::HTTPHeader#set_range. def set_range(r, e = nil) unless r @header.delete 'range' return r end r = (r...r+e) if e case r when Numeric n = r.to_i rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}") when Range first = r.first last = r.end last -= 1 if r.exclude_end? if last == -1 rangestr = (first > 0 ? "#{first}-" : "-#{-first}") else raise Gem::Net::HTTPHeaderSyntaxError, 'range.first is negative' if first < 0 raise Gem::Net::HTTPHeaderSyntaxError, 'range.last is negative' if last < 0 raise Gem::Net::HTTPHeaderSyntaxError, 'must be .first < .last' if first > last rangestr = "#{first}-#{last}" end else raise TypeError, 'Range/Integer is required' end @header['range'] = ["bytes=#{rangestr}"] r end alias range= set_range # Returns the value of field 'Content-Length' as an integer, # or +nil+ if there is no such field; # see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/nosuch/1') # res.content_length # => 2 # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.content_length # => nil # def content_length return nil unless key?('Content-Length') len = self['Content-Length'].slice(/\d+/) or raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Length format' len.to_i end # Sets the value of field 'Content-Length' to the given numeric; # see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]: # # _uri = uri.dup # hostname = _uri.hostname # => "jsonplaceholder.typicode.com" # _uri.path = '/posts' # => "/posts" # req = Gem::Net::HTTP::Post.new(_uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_length = req.body.size # => 42 # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # => # # def content_length=(len) unless len @header.delete 'content-length' return nil end @header['content-length'] = [len.to_i.to_s] end # Returns +true+ if field 'Transfer-Encoding' # exists and has value 'chunked', # +false+ otherwise; # see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Transfer-Encoding'] # => "chunked" # res.chunked? # => true # def chunked? return false unless @header['transfer-encoding'] field = self['Transfer-Encoding'] (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false end # Returns a Range object representing the value of field # 'Content-Range', or +nil+ if no such field exists; # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Content-Range'] # => nil # res['Content-Range'] = 'bytes 0-499/1000' # res['Content-Range'] # => "bytes 0-499/1000" # res.content_range # => 0..499 # def content_range return nil unless @header['content-range'] m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Range format' return unless m[1] == 'bytes' m[2].to_i .. m[3].to_i end # Returns the integer representing length of the value of field # 'Content-Range', or +nil+ if no such field exists; # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Content-Range'] # => nil # res['Content-Range'] = 'bytes 0-499/1000' # res.range_length # => 500 # def range_length r = content_range() or return nil r.end - r.begin + 1 end # Returns the {media type}[https://en.wikipedia.org/wiki/Media_type] # from the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.content_type # => "application/json" # def content_type main = main_type() return nil unless main sub = sub_type() if sub "#{main}/#{sub}" else main end end # Returns the leading ('type') part of the # {media type}[https://en.wikipedia.org/wiki/Media_type] # from the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.main_type # => "application" # def main_type return nil unless @header['content-type'] self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip end # Returns the trailing ('subtype') part of the # {media type}[https://en.wikipedia.org/wiki/Media_type] # from the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.sub_type # => "json" # def sub_type return nil unless @header['content-type'] _, sub = *self['Content-Type'].split(';').first.to_s.split('/') return nil unless sub sub.strip end # Returns the trailing ('parameters') part of the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.type_params # => {"charset"=>"utf-8"} # def type_params result = {} list = self['Content-Type'].to_s.split(';') list.shift list.each do |param| k, v = *param.split('=', 2) result[k.strip] = v.strip end result end # Sets the value of field 'Content-Type'; # returns the new value; # see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]: # # req = Gem::Net::HTTP::Get.new(uri) # req.set_content_type('application/json') # => ["application/json"] # # Gem::Net::HTTPHeader#content_type= is an alias for Gem::Net::HTTPHeader#set_content_type. def set_content_type(type, params = {}) @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')] end alias content_type= set_content_type # Sets the request body to a URL-encoded string derived from argument +params+, # and sets request header field 'Content-Type' # to 'application/x-www-form-urlencoded'. # # The resulting request is suitable for HTTP request +POST+ or +PUT+. # # Argument +params+ must be suitable for use as argument +enum+ to # {Gem::URI.encode_www_form}[https://docs.ruby-lang.org/en/master/Gem::URI.html#method-c-encode_www_form]. # # With only argument +params+ given, # sets the body to a URL-encoded string with the default separator '&': # # req = Gem::Net::HTTP::Post.new('example.com') # # req.set_form_data(q: 'ruby', lang: 'en') # req.body # => "q=ruby&lang=en" # req['Content-Type'] # => "application/x-www-form-urlencoded" # # req.set_form_data([['q', 'ruby'], ['lang', 'en']]) # req.body # => "q=ruby&lang=en" # # req.set_form_data(q: ['ruby', 'perl'], lang: 'en') # req.body # => "q=ruby&q=perl&lang=en" # # req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']]) # req.body # => "q=ruby&q=perl&lang=en" # # With string argument +sep+ also given, # uses that string as the separator: # # req.set_form_data({q: 'ruby', lang: 'en'}, '|') # req.body # => "q=ruby|lang=en" # # Gem::Net::HTTPHeader#form_data= is an alias for Gem::Net::HTTPHeader#set_form_data. def set_form_data(params, sep = '&') query = Gem::URI.encode_www_form(params) query.gsub!(/&/, sep) if sep != '&' self.body = query self.content_type = 'application/x-www-form-urlencoded' end alias form_data= set_form_data # Stores form data to be used in a +POST+ or +PUT+ request. # # The form data given in +params+ consists of zero or more fields; # each field is: # # - A scalar value. # - A name/value pair. # - An IO stream opened for reading. # # Argument +params+ should be an # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes] # (method params.map will be called), # and is often an array or hash. # # First, we set up a request: # # _uri = uri.dup # _uri.path ='/posts' # req = Gem::Net::HTTP::Post.new(_uri) # # Argument +params+ As an Array # # When +params+ is an array, # each of its elements is a subarray that defines a field; # the subarray may contain: # # - One string: # # req.set_form([['foo'], ['bar'], ['baz']]) # # - Two strings: # # req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]]) # # - When argument +enctype+ (see below) is given as # 'multipart/form-data': # # - A string name and an IO stream opened for reading: # # require 'stringio' # req.set_form([['file', StringIO.new('Ruby is cool.')]]) # # - A string name, an IO stream opened for reading, # and an options hash, which may contain these entries: # # - +:filename+: The name of the file to use. # - +:content_type+: The content type of the uploaded file. # # Example: # # req.set_form([['file', file, {filename: "other-filename.foo"}]] # # The various forms may be mixed: # # req.set_form(['foo', %w[bar 1], ['file', file]]) # # Argument +params+ As a Hash # # When +params+ is a hash, # each of its entries is a name/value pair that defines a field: # # - The name is a string. # - The value may be: # # - +nil+. # - Another string. # - An IO stream opened for reading # (only when argument +enctype+ -- see below -- is given as # 'multipart/form-data'). # # Examples: # # # Nil-valued fields. # req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil}) # # # String-valued fields. # req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2}) # # # IO-valued field. # require 'stringio' # req.set_form({'file' => StringIO.new('Ruby is cool.')}) # # # Mixture of fields. # req.set_form({'foo' => nil, 'bar' => 1, 'file' => file}) # # Optional argument +enctype+ specifies the value to be given # to field 'Content-Type', and must be one of: # # - 'application/x-www-form-urlencoded' (the default). # - 'multipart/form-data'; # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578]. # # Optional argument +formopt+ is a hash of options # (applicable only when argument +enctype+ # is 'multipart/form-data') # that may include the following entries: # # - +:boundary+: The value is the boundary string for the multipart message. # If not given, the boundary is a random string. # See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1]. # - +:charset+: Value is the character set for the form submission. # Field names and values of non-file fields should be encoded with this charset. # def set_form(params, enctype='application/x-www-form-urlencoded', formopt={}) @body_data = params @body = nil @body_stream = nil @form_option = formopt case enctype when /\Aapplication\/x-www-form-urlencoded\z/i, /\Amultipart\/form-data\z/i self.content_type = enctype else raise ArgumentError, "invalid enctype: #{enctype}" end end # Sets header 'Authorization' using the given # +account+ and +password+ strings: # # req.basic_auth('my_account', 'my_password') # req['Authorization'] # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" # def basic_auth(account, password) @header['authorization'] = [basic_encode(account, password)] end # Sets header 'Proxy-Authorization' using the given # +account+ and +password+ strings: # # req.proxy_basic_auth('my_account', 'my_password') # req['Proxy-Authorization'] # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" # def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end def basic_encode(account, password) 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @header['proxy-connection']&.grep(token) {return true} false end # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @header['proxy-connection']&.grep(token) {return true} false end end PK!sNtt2rubygems/vendor/net-http/lib/net/http/responses.rbnu[# frozen_string_literal: true #-- # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml module Gem::Net class HTTPUnknownResponse < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPError # end # Parent class for informational (1xx) HTTP response classes. # # An informational response indicates that the request was received and understood. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.1xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse HAS_BODY = false EXCEPTION_TYPE = HTTPError # end # Parent class for success (2xx) HTTP response classes. # # A success response indicates the action requested by the client # was received, understood, and accepted. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.2xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPError # end # Parent class for redirection (3xx) HTTP response classes. # # A redirection response indicates the client must take additional action # to complete the request. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.3xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end # Parent class for client error (4xx) HTTP response classes. # # A client error response indicates that the client may have caused an error. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.4xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end # Parent class for server error (5xx) HTTP response classes. # # A server error response indicates that the server failed to fulfill a request. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.5xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end # Response class for +Continue+ responses (status code 100). # # A +Continue+ response indicates that the server has received the request headers. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-100-continue]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation HAS_BODY = false end # Response class for Switching Protocol responses (status code 101). # # The Switching Protocol response indicates that the server has received # a request to switch protocols, and has agreed to do so. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation HAS_BODY = false end # Response class for +Processing+ responses (status code 102). # # The +Processing+ response indicates that the server has received # and is processing the request, but no response is available yet. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 2518}[https://www.rfc-editor.org/rfc/rfc2518#section-10.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation HAS_BODY = false end # Response class for Early Hints responses (status code 103). # # The Early Hints indicates that the server has received # and is processing the request, and contains certain headers; # the final response is not available yet. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103]. # - {RFC 8297}[https://www.rfc-editor.org/rfc/rfc8297.html#section-2]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation HAS_BODY = false end # Response class for +OK+ responses (status code 200). # # The +OK+ response indicates that the server has received # a request and has responded successfully. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess HAS_BODY = true end # Response class for +Created+ responses (status code 201). # # The +Created+ response indicates that the server has received # and has fulfilled a request to create a new resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-201-created]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess HAS_BODY = true end # Response class for +Accepted+ responses (status code 202). # # The +Accepted+ response indicates that the server has received # and is processing a request, but the processing has not yet been completed. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-202-accepted]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess HAS_BODY = true end # Response class for Non-Authoritative Information responses (status code 203). # # The Non-Authoritative Information response indicates that the server # is a transforming proxy (such as a Web accelerator) # that received a 200 OK response from its origin, # and is returning a modified version of the origin's response. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/203]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-203-non-authoritative-infor]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess HAS_BODY = true end # Response class for No Content responses (status code 204). # # The No Content response indicates that the server # successfully processed the request, and is not returning any content. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-204-no-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess HAS_BODY = false end # Response class for Reset Content responses (status code 205). # # The Reset Content response indicates that the server # successfully processed the request, # asks that the client reset its document view, and is not returning any content. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/205]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-205-reset-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess HAS_BODY = false end # Response class for Partial Content responses (status code 206). # # The Partial Content response indicates that the server is delivering # only part of the resource (byte serving) # due to a Range header in the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-206-partial-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess HAS_BODY = true end # Response class for Multi-Status (WebDAV) responses (status code 207). # # The Multi-Status (WebDAV) response indicates that the server # has received the request, # and that the message body can contain a number of separate response codes. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 4818}[https://www.rfc-editor.org/rfc/rfc4918#section-11.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess HAS_BODY = true end # Response class for Already Reported (WebDAV) responses (status code 208). # # The Already Reported (WebDAV) response indicates that the server # has received the request, # and that the members of a DAV binding have already been enumerated # in a preceding part of the (multi-status) response, # and are not being included again. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 5842}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess HAS_BODY = true end # Response class for IM Used responses (status code 226). # # The IM Used response indicates that the server has fulfilled a request # for the resource, and the response is a representation of the result # of one or more instance-manipulations applied to the current instance. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 3229}[https://www.rfc-editor.org/rfc/rfc3229.html#section-10.4.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess HAS_BODY = true end # Response class for Multiple Choices responses (status code 300). # # The Multiple Choices response indicates that the server # offers multiple options for the resource from which the client may choose. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/300]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-300-multiple-choices]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices # Response class for Moved Permanently responses (status code 301). # # The Moved Permanently response indicates that links or records # returning this response should be updated to use the given URL. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-301-moved-permanently]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection HAS_BODY = true end # Response class for Found responses (status code 302). # # The Found response indicates that the client # should look at (browse to) another URL. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-302-found]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection HAS_BODY = true end HTTPMovedTemporarily = HTTPFound # Response class for See Other responses (status code 303). # # The response to the request can be found under another Gem::URI using the GET method. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-303-see-other]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection HAS_BODY = true end # Response class for Not Modified responses (status code 304). # # Indicates that the resource has not been modified since the version # specified by the request headers. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection HAS_BODY = false end # Response class for Use Proxy responses (status code 305). # # The requested resource is available only through a proxy, # whose address is provided in the response. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-305-use-proxy]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection HAS_BODY = false end # Response class for Temporary Redirect responses (status code 307). # # The request should be repeated with another Gem::URI; # however, future requests should still use the original Gem::URI. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-307-temporary-redirect]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection HAS_BODY = true end # Response class for Permanent Redirect responses (status code 308). # # This and all future requests should be directed to the given Gem::URI. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-308-permanent-redirect]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection HAS_BODY = true end # Response class for Bad Request responses (status code 400). # # The server cannot or will not process the request due to an apparent client error. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError HAS_BODY = true end # Response class for Unauthorized responses (status code 401). # # Authentication is required, but either was not provided or failed. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-401-unauthorized]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError HAS_BODY = true end # Response class for Payment Required responses (status code 402). # # Reserved for future use. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-402-payment-required]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError HAS_BODY = true end # Response class for Forbidden responses (status code 403). # # The request contained valid data and was understood by the server, # but the server is refusing action. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError HAS_BODY = true end # Response class for Not Found responses (status code 404). # # The requested resource could not be found but may be available in the future. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-404-not-found]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError HAS_BODY = true end # Response class for Method Not Allowed responses (status code 405). # # The request method is not supported for the requested resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-405-method-not-allowed]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError HAS_BODY = true end # Response class for Not Acceptable responses (status code 406). # # The requested resource is capable of generating only content # that not acceptable according to the Accept headers sent in the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-406-not-acceptable]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError HAS_BODY = true end # Response class for Proxy Authentication Required responses (status code 407). # # The client must first authenticate itself with the proxy. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-407-proxy-authentication-re]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError HAS_BODY = true end # Response class for Request Gem::Timeout responses (status code 408). # # The server timed out waiting for the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-408-request-timeout]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout # Response class for Conflict responses (status code 409). # # The request could not be processed because of conflict in the current state of the resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError HAS_BODY = true end # Response class for Gone responses (status code 410). # # The resource requested was previously in use but is no longer available # and will not be available again. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-410-gone]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError HAS_BODY = true end # Response class for Length Required responses (status code 411). # # The request did not specify the length of its content, # which is required by the requested resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-411-length-required]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError HAS_BODY = true end # Response class for Precondition Failed responses (status code 412). # # The server does not meet one of the preconditions # specified in the request headers. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-412-precondition-failed]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError HAS_BODY = true end # Response class for Payload Too Large responses (status code 413). # # The request is larger than the server is willing or able to process. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge # Response class for Gem::URI Too Long responses (status code 414). # # The Gem::URI provided was too long for the server to process. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-414-uri-too-long]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong HTTPRequestURITooLarge = HTTPRequestURITooLong # Response class for Unsupported Media Type responses (status code 415). # # The request entity has a media type which the server or resource does not support. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-415-unsupported-media-type]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError HAS_BODY = true end # Response class for Range Not Satisfiable responses (status code 416). # # The request entity has a media type which the server or resource does not support. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-416-range-not-satisfiable]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable # Response class for Expectation Failed responses (status code 417). # # The server cannot meet the requirements of the Expect request-header field. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-417-expectation-failed]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError HAS_BODY = true end # 418 I'm a teapot - RFC 2324; a joke RFC # See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#418. # 420 Enhance Your Calm - Twitter # Response class for Misdirected Request responses (status code 421). # # The request was directed at a server that is not able to produce a response. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-421-misdirected-request]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError HAS_BODY = true end # Response class for Unprocessable Entity responses (status code 422). # # The request was well-formed but had semantic errors. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-422-unprocessable-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError HAS_BODY = true end # Response class for Locked (WebDAV) responses (status code 423). # # The requested resource is locked. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.3]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError HAS_BODY = true end # Response class for Failed Dependency (WebDAV) responses (status code 424). # # The request failed because it depended on another request and that request failed. # See {424 Failed Dependency (WebDAV)}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.4]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError HAS_BODY = true end # 425 Too Early # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#425. # Response class for Upgrade Required responses (status code 426). # # The client should switch to the protocol given in the Upgrade header field. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-426-upgrade-required]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError HAS_BODY = true end # Response class for Precondition Required responses (status code 428). # # The origin server requires the request to be conditional. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/428]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-3]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError HAS_BODY = true end # Response class for Too Many Requests responses (status code 429). # # The user has sent too many requests in a given amount of time. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-4]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError HAS_BODY = true end # Response class for Request Header Fields Too Large responses (status code 431). # # An individual header field is too large, # or all the header fields collectively, are too large. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-5]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError HAS_BODY = true end # Response class for Unavailable For Legal Reasons responses (status code 451). # # A server operator has received a legal demand to deny access to a resource or to a set of resources # that includes the requested resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/451]. # - {RFC 7725}[https://www.rfc-editor.org/rfc/rfc7725.html#section-3]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError HAS_BODY = true end # 444 No Response - Nginx # 449 Retry With - Microsoft # 450 Blocked by Windows Parental Controls - Microsoft # 499 Client Closed Request - Nginx # Response class for Internal Server Error responses (status code 500). # # An unexpected condition was encountered and no more specific message is suitable. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-500-internal-server-error]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError HAS_BODY = true end # Response class for Not Implemented responses (status code 501). # # The server either does not recognize the request method, # or it lacks the ability to fulfil the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-501-not-implemented]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError HAS_BODY = true end # Response class for Bad Gateway responses (status code 502). # # The server was acting as a gateway or proxy # and received an invalid response from the upstream server. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError HAS_BODY = true end # Response class for Service Unavailable responses (status code 503). # # The server cannot handle the request # (because it is overloaded or down for maintenance). # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError HAS_BODY = true end # Response class for Gateway Gem::Timeout responses (status code 504). # # The server was acting as a gateway or proxy # and did not receive a timely response from the upstream server. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout # Response class for HTTP Version Not Supported responses (status code 505). # # The server does not support the HTTP version used in the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-505-http-version-not-suppor]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError HAS_BODY = true end # Response class for Variant Also Negotiates responses (status code 506). # # Transparent content negotiation for the request results in a circular reference. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/506]. # - {RFC 2295}[https://www.rfc-editor.org/rfc/rfc2295#section-8.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError HAS_BODY = true end # Response class for Insufficient Storage (WebDAV) responses (status code 507). # # The server is unable to store the representation needed to complete the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/507]. # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.5]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError HAS_BODY = true end # Response class for Loop Detected (WebDAV) responses (status code 508). # # The server detected an infinite loop while processing the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508]. # - {RFC 5942}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.2]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension # Response class for Not Extended responses (status code 510). # # Further extensions to the request are required for the server to fulfill it. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/510]. # - {RFC 2774}[https://www.rfc-editor.org/rfc/rfc2774.html#section-7]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError HAS_BODY = true end # Response class for Network Authentication Required responses (status code 511). # # The client needs to authenticate to gain network access. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-6]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError HAS_BODY = true end end class Gem::Net::HTTPResponse CODE_CLASS_TO_OBJ = { '1' => Gem::Net::HTTPInformation, '2' => Gem::Net::HTTPSuccess, '3' => Gem::Net::HTTPRedirection, '4' => Gem::Net::HTTPClientError, '5' => Gem::Net::HTTPServerError } CODE_TO_OBJ = { '100' => Gem::Net::HTTPContinue, '101' => Gem::Net::HTTPSwitchProtocol, '102' => Gem::Net::HTTPProcessing, '103' => Gem::Net::HTTPEarlyHints, '200' => Gem::Net::HTTPOK, '201' => Gem::Net::HTTPCreated, '202' => Gem::Net::HTTPAccepted, '203' => Gem::Net::HTTPNonAuthoritativeInformation, '204' => Gem::Net::HTTPNoContent, '205' => Gem::Net::HTTPResetContent, '206' => Gem::Net::HTTPPartialContent, '207' => Gem::Net::HTTPMultiStatus, '208' => Gem::Net::HTTPAlreadyReported, '226' => Gem::Net::HTTPIMUsed, '300' => Gem::Net::HTTPMultipleChoices, '301' => Gem::Net::HTTPMovedPermanently, '302' => Gem::Net::HTTPFound, '303' => Gem::Net::HTTPSeeOther, '304' => Gem::Net::HTTPNotModified, '305' => Gem::Net::HTTPUseProxy, '307' => Gem::Net::HTTPTemporaryRedirect, '308' => Gem::Net::HTTPPermanentRedirect, '400' => Gem::Net::HTTPBadRequest, '401' => Gem::Net::HTTPUnauthorized, '402' => Gem::Net::HTTPPaymentRequired, '403' => Gem::Net::HTTPForbidden, '404' => Gem::Net::HTTPNotFound, '405' => Gem::Net::HTTPMethodNotAllowed, '406' => Gem::Net::HTTPNotAcceptable, '407' => Gem::Net::HTTPProxyAuthenticationRequired, '408' => Gem::Net::HTTPRequestTimeout, '409' => Gem::Net::HTTPConflict, '410' => Gem::Net::HTTPGone, '411' => Gem::Net::HTTPLengthRequired, '412' => Gem::Net::HTTPPreconditionFailed, '413' => Gem::Net::HTTPPayloadTooLarge, '414' => Gem::Net::HTTPURITooLong, '415' => Gem::Net::HTTPUnsupportedMediaType, '416' => Gem::Net::HTTPRangeNotSatisfiable, '417' => Gem::Net::HTTPExpectationFailed, '421' => Gem::Net::HTTPMisdirectedRequest, '422' => Gem::Net::HTTPUnprocessableEntity, '423' => Gem::Net::HTTPLocked, '424' => Gem::Net::HTTPFailedDependency, '426' => Gem::Net::HTTPUpgradeRequired, '428' => Gem::Net::HTTPPreconditionRequired, '429' => Gem::Net::HTTPTooManyRequests, '431' => Gem::Net::HTTPRequestHeaderFieldsTooLarge, '451' => Gem::Net::HTTPUnavailableForLegalReasons, '500' => Gem::Net::HTTPInternalServerError, '501' => Gem::Net::HTTPNotImplemented, '502' => Gem::Net::HTTPBadGateway, '503' => Gem::Net::HTTPServiceUnavailable, '504' => Gem::Net::HTTPGatewayTimeout, '505' => Gem::Net::HTTPVersionNotSupported, '506' => Gem::Net::HTTPVariantAlsoNegotiates, '507' => Gem::Net::HTTPInsufficientStorage, '508' => Gem::Net::HTTPLoopDetected, '510' => Gem::Net::HTTPNotExtended, '511' => Gem::Net::HTTPNetworkAuthenticationRequired, } end PK!T,u`NN1rubygems/vendor/net-http/lib/net/http/response.rbnu[# frozen_string_literal: true # This class is the base class for \Gem::Net::HTTP response classes. # # == About the Examples # # :include: doc/net-http/examples.rdoc # # == Returned Responses # # \Method Gem::Net::HTTP.get_response returns # an instance of one of the subclasses of \Gem::Net::HTTPResponse: # # Gem::Net::HTTP.get_response(uri) # # => # # Gem::Net::HTTP.get_response(hostname, '/nosuch') # # => # # # As does method Gem::Net::HTTP#request: # # req = Gem::Net::HTTP::Get.new(uri) # Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # => # # # \Class \Gem::Net::HTTPResponse includes module Gem::Net::HTTPHeader, # which provides access to response header values via (among others): # # - \Hash-like method []. # - Specific reader methods, such as +content_type+. # # Examples: # # res = Gem::Net::HTTP.get_response(uri) # => # # res['Content-Type'] # => "text/html; charset=UTF-8" # res.content_type # => "text/html" # # == Response Subclasses # # \Class \Gem::Net::HTTPResponse has a subclass for each # {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes]. # You can look up the response class for a given code: # # Gem::Net::HTTPResponse::CODE_TO_OBJ['200'] # => Gem::Net::HTTPOK # Gem::Net::HTTPResponse::CODE_TO_OBJ['400'] # => Gem::Net::HTTPBadRequest # Gem::Net::HTTPResponse::CODE_TO_OBJ['404'] # => Gem::Net::HTTPNotFound # # And you can retrieve the status code for a response object: # # Gem::Net::HTTP.get_response(uri).code # => "200" # Gem::Net::HTTP.get_response(hostname, '/nosuch').code # => "404" # # The response subclasses (indentation shows class hierarchy): # # - Gem::Net::HTTPUnknownResponse (for unhandled \HTTP extensions). # # - Gem::Net::HTTPInformation: # # - Gem::Net::HTTPContinue (100) # - Gem::Net::HTTPSwitchProtocol (101) # - Gem::Net::HTTPProcessing (102) # - Gem::Net::HTTPEarlyHints (103) # # - Gem::Net::HTTPSuccess: # # - Gem::Net::HTTPOK (200) # - Gem::Net::HTTPCreated (201) # - Gem::Net::HTTPAccepted (202) # - Gem::Net::HTTPNonAuthoritativeInformation (203) # - Gem::Net::HTTPNoContent (204) # - Gem::Net::HTTPResetContent (205) # - Gem::Net::HTTPPartialContent (206) # - Gem::Net::HTTPMultiStatus (207) # - Gem::Net::HTTPAlreadyReported (208) # - Gem::Net::HTTPIMUsed (226) # # - Gem::Net::HTTPRedirection: # # - Gem::Net::HTTPMultipleChoices (300) # - Gem::Net::HTTPMovedPermanently (301) # - Gem::Net::HTTPFound (302) # - Gem::Net::HTTPSeeOther (303) # - Gem::Net::HTTPNotModified (304) # - Gem::Net::HTTPUseProxy (305) # - Gem::Net::HTTPTemporaryRedirect (307) # - Gem::Net::HTTPPermanentRedirect (308) # # - Gem::Net::HTTPClientError: # # - Gem::Net::HTTPBadRequest (400) # - Gem::Net::HTTPUnauthorized (401) # - Gem::Net::HTTPPaymentRequired (402) # - Gem::Net::HTTPForbidden (403) # - Gem::Net::HTTPNotFound (404) # - Gem::Net::HTTPMethodNotAllowed (405) # - Gem::Net::HTTPNotAcceptable (406) # - Gem::Net::HTTPProxyAuthenticationRequired (407) # - Gem::Net::HTTPRequestTimeOut (408) # - Gem::Net::HTTPConflict (409) # - Gem::Net::HTTPGone (410) # - Gem::Net::HTTPLengthRequired (411) # - Gem::Net::HTTPPreconditionFailed (412) # - Gem::Net::HTTPRequestEntityTooLarge (413) # - Gem::Net::HTTPRequestURITooLong (414) # - Gem::Net::HTTPUnsupportedMediaType (415) # - Gem::Net::HTTPRequestedRangeNotSatisfiable (416) # - Gem::Net::HTTPExpectationFailed (417) # - Gem::Net::HTTPMisdirectedRequest (421) # - Gem::Net::HTTPUnprocessableEntity (422) # - Gem::Net::HTTPLocked (423) # - Gem::Net::HTTPFailedDependency (424) # - Gem::Net::HTTPUpgradeRequired (426) # - Gem::Net::HTTPPreconditionRequired (428) # - Gem::Net::HTTPTooManyRequests (429) # - Gem::Net::HTTPRequestHeaderFieldsTooLarge (431) # - Gem::Net::HTTPUnavailableForLegalReasons (451) # # - Gem::Net::HTTPServerError: # # - Gem::Net::HTTPInternalServerError (500) # - Gem::Net::HTTPNotImplemented (501) # - Gem::Net::HTTPBadGateway (502) # - Gem::Net::HTTPServiceUnavailable (503) # - Gem::Net::HTTPGatewayTimeOut (504) # - Gem::Net::HTTPVersionNotSupported (505) # - Gem::Net::HTTPVariantAlsoNegotiates (506) # - Gem::Net::HTTPInsufficientStorage (507) # - Gem::Net::HTTPLoopDetected (508) # - Gem::Net::HTTPNotExtended (510) # - Gem::Net::HTTPNetworkAuthenticationRequired (511) # # There is also the Gem::Net::HTTPBadResponse exception which is raised when # there is a protocol error. # class Gem::Net::HTTPResponse class << self # true if the response has a body. def body_permitted? self::HAS_BODY end def exception_type # :nodoc: internal use only self::EXCEPTION_TYPE end def read_new(sock) #:nodoc: internal use only httpv, code, msg = read_status_line(sock) res = response_class(code).new(httpv, code, msg) each_response_header(sock) do |k,v| res.add_field k, v end res end private def read_status_line(sock) str = sock.readline m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\z/in.match(str) or raise Gem::Net::HTTPBadResponse, "wrong status line: #{str.dump}" m.captures end def response_class(code) CODE_TO_OBJ[code] or CODE_CLASS_TO_OBJ[code[0,1]] or Gem::Net::HTTPUnknownResponse end def each_response_header(sock) key = value = nil while true line = sock.readuntil("\n", true).sub(/\s+\z/, '') break if line.empty? if line[0] == ?\s or line[0] == ?\t and value value << ' ' unless value.empty? value << line.strip else yield key, value if key key, value = line.strip.split(/\s*:\s*/, 2) raise Gem::Net::HTTPBadResponse, 'wrong header line format' if value.nil? end end yield key, value if key end end # next is to fix bug in RDoc, where the private inside class << self # spills out. public include Gem::Net::HTTPHeader def initialize(httpv, code, msg) #:nodoc: internal use only @http_version = httpv @code = code @message = msg initialize_http_header nil @body = nil @read = false @uri = nil @decode_content = false @body_encoding = false @ignore_eof = true end # The HTTP version supported by the server. attr_reader :http_version # The HTTP result code string. For example, '302'. You can also # determine the response type by examining which response subclass # the response object is an instance of. attr_reader :code # The HTTP result message sent by the server. For example, 'Not Found'. attr_reader :message alias msg message # :nodoc: obsolete # The Gem::URI used to fetch this response. The response Gem::URI is only available # if a Gem::URI was used to create the request. attr_reader :uri # Set to true automatically when the request did not contain an # Accept-Encoding header from the user. attr_accessor :decode_content # Returns the value set by body_encoding=, or +false+ if none; # see #body_encoding=. attr_reader :body_encoding # Sets the encoding that should be used when reading the body: # # - If the given value is an Encoding object, that encoding will be used. # - Otherwise if the value is a string, the value of # {Encoding#find(value)}[https://docs.ruby-lang.org/en/master/Encoding.html#method-c-find] # will be used. # - Otherwise an encoding will be deduced from the body itself. # # Examples: # # http = Gem::Net::HTTP.new(hostname) # req = Gem::Net::HTTP::Get.new('/') # # http.request(req) do |res| # p res.body.encoding # => # # end # # http.request(req) do |res| # res.body_encoding = "UTF-8" # p res.body.encoding # => # # end # def body_encoding=(value) value = Encoding.find(value) if value.is_a?(String) @body_encoding = value end # Whether to ignore EOF when reading bodies with a specified Content-Length # header. attr_accessor :ignore_eof def inspect "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end # # response <-> exception relationship # def code_type #:nodoc: self.class end def error! #:nodoc: message = @code message = "#{message} #{@message.dump}" if @message raise error_type().new(message, self) end def error_type #:nodoc: self.class::EXCEPTION_TYPE end # Raises an HTTP error if the response is not 2xx (success). def value error! unless self.kind_of?(Gem::Net::HTTPSuccess) end def uri= uri # :nodoc: @uri = uri.dup if uri end # # header (for backward compatibility only; DO NOT USE) # def response #:nodoc: warn "Gem::Net::HTTPResponse#response is obsolete", uplevel: 1 if $VERBOSE self end def header #:nodoc: warn "Gem::Net::HTTPResponse#header is obsolete", uplevel: 1 if $VERBOSE self end def read_header #:nodoc: warn "Gem::Net::HTTPResponse#read_header is obsolete", uplevel: 1 if $VERBOSE self end # # body # def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only @socket = sock @body_exist = reqmethodallowbody && self.class.body_permitted? begin yield self.body # ensure to read body ensure @socket = nil end end # Gets the entity body returned by the remote HTTP server. # # If a block is given, the body is passed to the block, and # the body is provided in fragments, as it is read in from the socket. # # If +dest+ argument is given, response is read into that variable, # with dest#<< method (it could be String or IO, or any # other object responding to <<). # # Calling this method a second or subsequent time for the same # HTTPResponse object will return the value already read. # # http.request_get('/index.html') {|res| # puts res.read_body # } # # http.request_get('/index.html') {|res| # p res.read_body.object_id # 538149362 # p res.read_body.object_id # 538149362 # } # # # using iterator # http.request_get('/index.html') {|res| # res.read_body do |segment| # print segment # end # } # def read_body(dest = nil, &block) if @read raise IOError, "#{self.class}\#read_body called twice" if dest or block return @body end to = procdest(dest, block) stream_check if @body_exist read_body_0 to @body = to else @body = nil end @read = true return if @body.nil? case enc = @body_encoding when Encoding, false, nil # Encoding: force given encoding # false/nil: do not force encoding else # other value: detect encoding from body enc = detect_encoding(@body) end @body.force_encoding(enc) if enc @body end # Returns the string response body; # note that repeated calls for the unmodified body return a cached string: # # path = '/todos/1' # Gem::Net::HTTP.start(hostname) do |http| # res = http.get(path) # p res.body # p http.head(path).body # No body. # end # # Output: # # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}" # nil # def body read_body() end # Sets the body of the response to the given value. def body=(value) @body = value end alias entity body #:nodoc: obsolete private # :nodoc: def detect_encoding(str, encoding=nil) if encoding elsif encoding = type_params['charset'] elsif encoding = check_bom(str) else encoding = case content_type&.downcase when %r{text/x(?:ht)?ml|application/(?:[^+]+\+)?xml} /\A' ss.getch return nil end name = ss.scan(/[^=\t\n\f\r \/>]*/) name.downcase! raise if name.empty? ss.skip(/[\t\n\f\r ]*/) if ss.getch != '=' value = '' return [name, value] end ss.skip(/[\t\n\f\r ]*/) case ss.peek(1) when '"' ss.getch value = ss.scan(/[^"]+/) value.downcase! ss.getch when "'" ss.getch value = ss.scan(/[^']+/) value.downcase! ss.getch when '>' value = '' else value = ss.scan(/[^\t\n\f\r >]+/) value.downcase! end [name, value] end def extracting_encodings_from_meta_elements(value) # http://dev.w3.org/html5/spec/fetching-resources.html#algorithm-for-extracting-an-encoding-from-a-meta-element if /charset[\t\n\f\r ]*=(?:"([^"]*)"|'([^']*)'|["']|\z|([^\t\n\f\r ;]+))/i =~ value return $1 || $2 || $3 end return nil end ## # Checks for a supported Content-Encoding header and yields an Inflate # wrapper for this response's socket when zlib is present. If the # Content-Encoding is not supported or zlib is missing, the plain socket is # yielded. # # If a Content-Range header is present, a plain socket is yielded as the # bytes in the range may not be a complete deflate block. def inflater # :nodoc: return yield @socket unless Gem::Net::HTTP::HAVE_ZLIB return yield @socket unless @decode_content return yield @socket if self['content-range'] v = self['content-encoding'] case v&.downcase when 'deflate', 'gzip', 'x-gzip' then self.delete 'content-encoding' inflate_body_io = Inflater.new(@socket) begin yield inflate_body_io success = true ensure begin inflate_body_io.finish if self['content-length'] self['content-length'] = inflate_body_io.bytes_inflated.to_s end rescue => err # Ignore #finish's error if there is an exception from yield raise err if success end end when 'none', 'identity' then self.delete 'content-encoding' yield @socket else yield @socket end end def read_body_0(dest) inflater do |inflate_body_io| if chunked? read_chunked dest, inflate_body_io return end @socket = inflate_body_io clen = content_length() if clen @socket.read clen, dest, @ignore_eof return end clen = range_length() if clen @socket.read clen, dest return end @socket.read_all dest end end ## # read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF, # etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip # encoded. # # See RFC 2616 section 3.6.1 for definitions def read_chunked(dest, chunk_data_io) # :nodoc: total = 0 while true line = @socket.readline hexlen = line.slice(/[0-9a-fA-F]+/) or raise Gem::Net::HTTPBadResponse, "wrong chunk size line: #{line}" len = hexlen.hex break if len == 0 begin chunk_data_io.read len, dest ensure total += len @socket.read 2 # \r\n end end until @socket.readline.empty? # none end end def stream_check raise IOError, 'attempt to read body out of block' if @socket.nil? || @socket.closed? end def procdest(dest, block) raise ArgumentError, 'both arg and block given for HTTP method' if dest and block if block Gem::Net::ReadAdapter.new(block) else dest || +'' end end ## # Inflater is a wrapper around Gem::Net::BufferedIO that transparently inflates # zlib and gzip streams. class Inflater # :nodoc: ## # Creates a new Inflater wrapping +socket+ def initialize socket @socket = socket # zlib with automatic gzip detection @inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS) end ## # Finishes the inflate stream. def finish return if @inflate.total_in == 0 @inflate.finish end ## # The number of bytes inflated, used to update the Content-Length of # the response. def bytes_inflated @inflate.total_out end ## # Returns a Gem::Net::ReadAdapter that inflates each read chunk into +dest+. # # This allows a large response body to be inflated without storing the # entire body in memory. def inflate_adapter(dest) if dest.respond_to?(:set_encoding) dest.set_encoding(Encoding::ASCII_8BIT) elsif dest.respond_to?(:force_encoding) dest.force_encoding(Encoding::ASCII_8BIT) end block = proc do |compressed_chunk| @inflate.inflate(compressed_chunk) do |chunk| compressed_chunk.clear dest << chunk end end Gem::Net::ReadAdapter.new(block) end ## # Reads +clen+ bytes from the socket, inflates them, then writes them to # +dest+. +ignore_eof+ is passed down to Gem::Net::BufferedIO#read # # Unlike Gem::Net::BufferedIO#read, this method returns more than +clen+ bytes. # At this time there is no way for a user of Gem::Net::HTTPResponse to read a # specific number of bytes from the HTTP response body, so this internal # API does not return the same number of bytes as were requested. # # See https://bugs.ruby-lang.org/issues/6492 for further discussion. def read clen, dest, ignore_eof = false temp_dest = inflate_adapter(dest) @socket.read clen, temp_dest, ignore_eof end ## # Reads the rest of the socket, inflates it, then writes it to +dest+. def read_all dest temp_dest = inflate_adapter(dest) @socket.read_all temp_dest end end end PK!,QQ*rubygems/vendor/molinillo/lib/molinillo.rbnu[# frozen_string_literal: true require_relative 'molinillo/gem_metadata' require_relative 'molinillo/errors' require_relative 'molinillo/resolver' require_relative 'molinillo/modules/ui' require_relative 'molinillo/modules/specification_provider' # Gem::Molinillo is a generic dependency resolution algorithm. module Gem::Molinillo end PK!ww7rubygems/vendor/molinillo/lib/molinillo/gem_metadata.rbnu[# frozen_string_literal: true module Gem::Molinillo # The version of Gem::Molinillo. VERSION = '0.8.0'.freeze end PK!?pp1rubygems/vendor/molinillo/lib/molinillo/errors.rbnu[# frozen_string_literal: true module Gem::Molinillo # An error that occurred during the resolution process class ResolverError < StandardError; end # An error caused by searching for a dependency that is completely unknown, # i.e. has no versions available whatsoever. class NoSuchDependencyError < ResolverError # @return [Object] the dependency that could not be found attr_accessor :dependency # @return [Array] the specifications that depended upon {#dependency} attr_accessor :required_by # Initializes a new error with the given missing dependency. # @param [Object] dependency @see {#dependency} # @param [Array] required_by @see {#required_by} def initialize(dependency, required_by = []) @dependency = dependency @required_by = required_by.uniq super() end # The error message for the missing dependency, including the specifications # that had this dependency. def message sources = required_by.map { |r| "`#{r}`" }.join(' and ') message = "Unable to find a specification for `#{dependency}`" message += " depended upon by #{sources}" unless sources.empty? message end end # An error caused by attempting to fulfil a dependency that was circular # # @note This exception will be thrown if and only if a {Vertex} is added to a # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an # existing {DependencyGraph::Vertex} class CircularDependencyError < ResolverError # [Set] the dependencies responsible for causing the error attr_reader :dependencies # Initializes a new error with the given circular vertices. # @param [Array] vertices the vertices in the dependency # that caused the error def initialize(vertices) super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set end end # An error caused by conflicts in version class VersionConflict < ResolverError # @return [{String => Resolution::Conflict}] the conflicts that caused # resolution to fail attr_reader :conflicts # @return [SpecificationProvider] the specification provider used during # resolution attr_reader :specification_provider # Initializes a new error with the given version conflicts. # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} # @param [SpecificationProvider] specification_provider see {#specification_provider} def initialize(conflicts, specification_provider) pairs = [] conflicts.values.flat_map(&:requirements).each do |conflicting| conflicting.each do |source, conflict_requirements| conflict_requirements.each do |c| pairs << [c, source] end end end super "Unable to satisfy the following requirements:\n\n" \ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" @conflicts = conflicts @specification_provider = specification_provider end require_relative 'delegates/specification_provider' include Delegates::SpecificationProvider # @return [String] An error message that includes requirement trees, # which is much more detailed & customizable than the default message # @param [Hash] opts the options to create a message with. # @option opts [String] :solver_name The user-facing name of the solver # @option opts [String] :possibility_type The generic name of a possibility # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements # @option opts [Proc] :additional_message_for_conflict A proc that appends additional # messages for each conflict # @option opts [Proc] :version_for_spec A proc that returns the version number for a # possibility def message_with_trees(opts = {}) solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } possibility_type = opts.delete(:possibility_type) { 'possibility named' } reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do proc do |name, _conflict| %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) end end full_message_for_conflict = opts.delete(:full_message_for_conflict) do proc do |name, conflict| o = "\n".dup << incompatible_version_message_for_conflict.call(name, conflict) << "\n" if conflict.locked_requirement o << %( In snapshot (#{name_for_locking_dependency_source}):\n) o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) o << %(\n) end o << %( In #{name_for_explicit_dependency_source}:\n) trees = reduce_trees.call(conflict.requirement_trees) o << trees.map do |tree| t = ''.dup depth = 2 tree.each do |req| t << ' ' * depth << printable_requirement.call(req) unless tree.last == req if spec = conflict.activated_by_name[name_for(req)] t << %( was resolved to #{version_for_spec.call(spec)}, which) end t << %( depends on) end t << %(\n) depth += 1 end t end.join("\n") additional_message_for_conflict.call(o, name, conflict) o end end conflicts.sort.reduce(''.dup) do |o, (name, conflict)| o << full_message_for_conflict.call(name, conflict) end.strip end end end PK!N讟?rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#tag class Tag < Action # @!group Action # (see Action.action_name) def self.action_name :tag end # (see Action#up) def up(graph) end # (see Action#down) def down(graph) end # @!group Tag # @return [Object] An opaque tag attr_reader :tag # Initialize an action to tag a state of a dependency graph # @param [Object] tag an opaque tag def initialize(tag) @tag = tag end end end end PK!19DDBrubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rbnu[# frozen_string_literal: true module Gem::Molinillo class DependencyGraph # A vertex in a {DependencyGraph} that encapsulates a {#name} and a # {#payload} class Vertex # @return [String] the name of the vertex attr_accessor :name # @return [Object] the payload the vertex holds attr_accessor :payload # @return [Array] the explicit requirements that required # this vertex attr_reader :explicit_requirements # @return [Boolean] whether the vertex is considered a root vertex attr_accessor :root alias root? root # Initializes a vertex with the given name and payload. # @param [String] name see {#name} # @param [Object] payload see {#payload} def initialize(name, payload) @name = name.frozen? ? name : name.dup.freeze @payload = payload @explicit_requirements = [] @outgoing_edges = [] @incoming_edges = [] end # @return [Array] all of the requirements that required # this vertex def requirements (incoming_edges.map(&:requirement) + explicit_requirements).uniq end # @return [Array] the edges of {#graph} that have `self` as their # {Edge#origin} attr_accessor :outgoing_edges # @return [Array] the edges of {#graph} that have `self` as their # {Edge#destination} attr_accessor :incoming_edges # @return [Array] the vertices of {#graph} that have an edge with # `self` as their {Edge#destination} def predecessors incoming_edges.map(&:origin) end # @return [Set] the vertices of {#graph} where `self` is a # {#descendent?} def recursive_predecessors _recursive_predecessors end # @param [Set] vertices the set to add the predecessors to # @return [Set] the vertices of {#graph} where `self` is a # {#descendent?} def _recursive_predecessors(vertices = new_vertex_set) incoming_edges.each do |edge| vertex = edge.origin next unless vertices.add?(vertex) vertex._recursive_predecessors(vertices) end vertices end protected :_recursive_predecessors # @return [Array] the vertices of {#graph} that have an edge with # `self` as their {Edge#origin} def successors outgoing_edges.map(&:destination) end # @return [Set] the vertices of {#graph} where `self` is an # {#ancestor?} def recursive_successors _recursive_successors end # @param [Set] vertices the set to add the successors to # @return [Set] the vertices of {#graph} where `self` is an # {#ancestor?} def _recursive_successors(vertices = new_vertex_set) outgoing_edges.each do |edge| vertex = edge.destination next unless vertices.add?(vertex) vertex._recursive_successors(vertices) end vertices end protected :_recursive_successors # @return [String] a string suitable for debugging def inspect "#{self.class}:#{name}(#{payload.inspect})" end # @return [Boolean] whether the two vertices are equal, determined # by a recursive traversal of each {Vertex#successors} def ==(other) return true if equal?(other) shallow_eql?(other) && successors.to_set == other.successors.to_set end # @param [Vertex] other the other vertex to compare to # @return [Boolean] whether the two vertices are equal, determined # solely by {#name} and {#payload} equality def shallow_eql?(other) return true if equal?(other) other && name == other.name && payload == other.payload end alias eql? == # @return [Fixnum] a hash for the vertex based upon its {#name} def hash name.hash end # Is there a path from `self` to `other` following edges in the # dependency graph? # @return whether there is a path following edges within this {#graph} def path_to?(other) _path_to?(other) end alias descendent? path_to? # @param [Vertex] other the vertex to check if there's a path to # @param [Set] visited the vertices of {#graph} that have been visited # @return [Boolean] whether there is a path to `other` from `self` def _path_to?(other, visited = new_vertex_set) return false unless visited.add?(self) return true if equal?(other) successors.any? { |v| v._path_to?(other, visited) } end protected :_path_to? # Is there a path from `other` to `self` following edges in the # dependency graph? # @return whether there is a path following edges within this {#graph} def ancestor?(other) other.path_to?(self) end alias is_reachable_from? ancestor? def new_vertex_set require 'set' Set.new end private :new_vertex_set end end end PK!?__Frubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#add_vertex) class AddVertex < Action # :nodoc: # @!group Action # (see Action.action_name) def self.action_name :add_vertex end # (see Action#up) def up(graph) if existing = graph.vertices[name] @existing_payload = existing.payload @existing_root = existing.root end vertex = existing || Vertex.new(name, payload) graph.vertices[vertex.name] = vertex vertex.payload ||= payload vertex.root ||= root vertex end # (see Action#down) def down(graph) if defined?(@existing_payload) vertex = graph.vertices[name] vertex.payload = @existing_payload vertex.root = @existing_root else graph.vertices.delete(name) end end # @!group AddVertex # @return [String] the name of the vertex attr_reader :name # @return [Object] the payload for the vertex attr_reader :payload # @return [Boolean] whether the vertex is root or not attr_reader :root # Initialize an action to add a vertex to a dependency graph # @param [String] name the name of the vertex # @param [Object] payload the payload for the vertex # @param [Boolean] root whether the vertex is root or not def initialize(name, payload, root) @name = name @payload = payload @root = root end end end end PK!J NBrubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rbnu[# frozen_string_literal: true module Gem::Molinillo class DependencyGraph # An action that modifies a {DependencyGraph} that is reversible. # @abstract class Action # rubocop:disable Lint/UnusedMethodArgument # @return [Symbol] The name of the action. def self.action_name raise 'Abstract' end # Performs the action on the given graph. # @param [DependencyGraph] graph the graph to perform the action on. # @return [Void] def up(graph) raise 'Abstract' end # Reverses the action on the given graph. # @param [DependencyGraph] graph the graph to reverse the action on. # @return [Void] def down(graph) raise 'Abstract' end # @return [Action,Nil] The previous action attr_accessor :previous # @return [Action,Nil] The next action attr_accessor :next end end end PK!8Orubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#detach_vertex_named class DetachVertexNamed < Action # @!group Action # (see Action#name) def self.action_name :add_vertex end # (see Action#up) def up(graph) return [] unless @vertex = graph.vertices.delete(name) removed_vertices = [@vertex] @vertex.outgoing_edges.each do |e| v = e.destination v.incoming_edges.delete(e) if !v.root? && v.incoming_edges.empty? removed_vertices.concat graph.detach_vertex_named(v.name) end end @vertex.incoming_edges.each do |e| v = e.origin v.outgoing_edges.delete(e) end removed_vertices end # (see Action#down) def down(graph) return unless @vertex graph.vertices[@vertex.name] = @vertex @vertex.outgoing_edges.each do |e| e.destination.incoming_edges << e end @vertex.incoming_edges.each do |e| e.origin.outgoing_edges << e end end # @!group DetachVertexNamed # @return [String] the name of the vertex to detach attr_reader :name # Initialize an action to detach a vertex from a dependency graph # @param [String] name the name of the vertex to detach def initialize(name) @name = name end end end end PK!U=Grubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#delete_edge) class DeleteEdge < Action # @!group Action # (see Action.action_name) def self.action_name :delete_edge end # (see Action#up) def up(graph) edge = make_edge(graph) edge.origin.outgoing_edges.delete(edge) edge.destination.incoming_edges.delete(edge) end # (see Action#down) def down(graph) edge = make_edge(graph) edge.origin.outgoing_edges << edge edge.destination.incoming_edges << edge edge end # @!group DeleteEdge # @return [String] the name of the origin of the edge attr_reader :origin_name # @return [String] the name of the destination of the edge attr_reader :destination_name # @return [Object] the requirement that the edge represents attr_reader :requirement # @param [DependencyGraph] graph the graph to find vertices from # @return [Edge] The edge this action adds def make_edge(graph) Edge.new( graph.vertex_named(origin_name), graph.vertex_named(destination_name), requirement ) end # Initialize an action to add an edge to a dependency graph # @param [String] origin_name the name of the origin of the edge # @param [String] destination_name the name of the destination of the edge # @param [Object] requirement the requirement that the edge represents def initialize(origin_name, destination_name, requirement) @origin_name = origin_name @destination_name = destination_name @requirement = requirement end end end end PK!N kkPrubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#add_edge_no_circular) class AddEdgeNoCircular < Action # @!group Action # (see Action.action_name) def self.action_name :add_vertex end # (see Action#up) def up(graph) edge = make_edge(graph) edge.origin.outgoing_edges << edge edge.destination.incoming_edges << edge edge end # (see Action#down) def down(graph) edge = make_edge(graph) delete_first(edge.origin.outgoing_edges, edge) delete_first(edge.destination.incoming_edges, edge) end # @!group AddEdgeNoCircular # @return [String] the name of the origin of the edge attr_reader :origin # @return [String] the name of the destination of the edge attr_reader :destination # @return [Object] the requirement that the edge represents attr_reader :requirement # @param [DependencyGraph] graph the graph to find vertices from # @return [Edge] The edge this action adds def make_edge(graph) Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement) end # Initialize an action to add an edge to a dependency graph # @param [String] origin the name of the origin of the edge # @param [String] destination the name of the destination of the edge # @param [Object] requirement the requirement that the edge represents def initialize(origin, destination, requirement) @origin = origin @destination = destination @requirement = requirement end private def delete_first(array, item) return unless index = array.index(item) array.delete_at(index) end end end end PK!fg?rubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rbnu[# frozen_string_literal: true require_relative 'add_edge_no_circular' require_relative 'add_vertex' require_relative 'delete_edge' require_relative 'detach_vertex_named' require_relative 'set_payload' require_relative 'tag' module Gem::Molinillo class DependencyGraph # A log for dependency graph actions class Log # Initializes an empty log def initialize @current_action = @first_action = nil end # @!macro [new] action # {include:DependencyGraph#$0} # @param [Graph] graph the graph to perform the action on # @param (see DependencyGraph#$0) # @return (see DependencyGraph#$0) # @macro action def tag(graph, tag) push_action(graph, Tag.new(tag)) end # @macro action def add_vertex(graph, name, payload, root) push_action(graph, AddVertex.new(name, payload, root)) end # @macro action def detach_vertex_named(graph, name) push_action(graph, DetachVertexNamed.new(name)) end # @macro action def add_edge_no_circular(graph, origin, destination, requirement) push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) end # {include:DependencyGraph#delete_edge} # @param [Graph] graph the graph to perform the action on # @param [String] origin_name # @param [String] destination_name # @param [Object] requirement # @return (see DependencyGraph#delete_edge) def delete_edge(graph, origin_name, destination_name, requirement) push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) end # @macro action def set_payload(graph, name, payload) push_action(graph, SetPayload.new(name, payload)) end # Pops the most recent action from the log and undoes the action # @param [DependencyGraph] graph # @return [Action] the action that was popped off the log def pop!(graph) return unless action = @current_action unless @current_action = action.previous @first_action = nil end action.down(graph) action end extend Enumerable # @!visibility private # Enumerates each action in the log # @yield [Action] def each return enum_for unless block_given? action = @first_action loop do break unless action yield action action = action.next end self end # @!visibility private # Enumerates each action in the log in reverse order # @yield [Action] def reverse_each return enum_for(:reverse_each) unless block_given? action = @current_action loop do break unless action yield action action = action.previous end self end # @macro action def rewind_to(graph, tag) loop do action = pop!(graph) raise "No tag #{tag.inspect} found" unless action break if action.class.action_name == :tag && action.tag == tag end end private # Adds the given action to the log, running the action # @param [DependencyGraph] graph # @param [Action] action # @return The value returned by `action.up` def push_action(graph, action) action.previous = @current_action @current_action.next = action if @current_action @current_action = action @first_action ||= action action.up(graph) end end end end PK!]SSGrubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#set_payload class SetPayload < Action # :nodoc: # @!group Action # (see Action.action_name) def self.action_name :set_payload end # (see Action#up) def up(graph) vertex = graph.vertex_named(name) @old_payload = vertex.payload vertex.payload = payload end # (see Action#down) def down(graph) graph.vertex_named(name).payload = @old_payload end # @!group SetPayload # @return [String] the name of the vertex attr_reader :name # @return [Object] the payload for the vertex attr_reader :payload # Initialize an action to add set the payload for a vertex in a dependency # graph # @param [String] name the name of the vertex # @param [Object] payload the payload for the vertex def initialize(name, payload) @name = name @payload = payload end end end end PK! 5b b Krubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rbnu[# frozen_string_literal: true module Gem::Molinillo module Delegates # Delegates all {Gem::Molinillo::SpecificationProvider} methods to a # `#specification_provider` property. module SpecificationProvider # (see Gem::Molinillo::SpecificationProvider#search_for) def search_for(dependency) with_no_such_dependency_error_handling do specification_provider.search_for(dependency) end end # (see Gem::Molinillo::SpecificationProvider#dependencies_for) def dependencies_for(specification) with_no_such_dependency_error_handling do specification_provider.dependencies_for(specification) end end # (see Gem::Molinillo::SpecificationProvider#requirement_satisfied_by?) def requirement_satisfied_by?(requirement, activated, spec) with_no_such_dependency_error_handling do specification_provider.requirement_satisfied_by?(requirement, activated, spec) end end # (see Gem::Molinillo::SpecificationProvider#dependencies_equal?) def dependencies_equal?(dependencies, other_dependencies) with_no_such_dependency_error_handling do specification_provider.dependencies_equal?(dependencies, other_dependencies) end end # (see Gem::Molinillo::SpecificationProvider#name_for) def name_for(dependency) with_no_such_dependency_error_handling do specification_provider.name_for(dependency) end end # (see Gem::Molinillo::SpecificationProvider#name_for_explicit_dependency_source) def name_for_explicit_dependency_source with_no_such_dependency_error_handling do specification_provider.name_for_explicit_dependency_source end end # (see Gem::Molinillo::SpecificationProvider#name_for_locking_dependency_source) def name_for_locking_dependency_source with_no_such_dependency_error_handling do specification_provider.name_for_locking_dependency_source end end # (see Gem::Molinillo::SpecificationProvider#sort_dependencies) def sort_dependencies(dependencies, activated, conflicts) with_no_such_dependency_error_handling do specification_provider.sort_dependencies(dependencies, activated, conflicts) end end # (see Gem::Molinillo::SpecificationProvider#allow_missing?) def allow_missing?(dependency) with_no_such_dependency_error_handling do specification_provider.allow_missing?(dependency) end end private # Ensures any raised {NoSuchDependencyError} has its # {NoSuchDependencyError#required_by} set. # @yield def with_no_such_dependency_error_handling yield rescue NoSuchDependencyError => error if state vertex = activated.vertex_named(name_for(error.dependency)) error.required_by += vertex.incoming_edges.map { |e| e.origin.name } error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? end raise end end end end PK!#:1VErubygems/vendor/molinillo/lib/molinillo/delegates/resolution_state.rbnu[# frozen_string_literal: true module Gem::Molinillo # @!visibility private module Delegates # Delegates all {Gem::Molinillo::ResolutionState} methods to a `#state` property. module ResolutionState # (see Gem::Molinillo::ResolutionState#name) def name current_state = state || Gem::Molinillo::ResolutionState.empty current_state.name end # (see Gem::Molinillo::ResolutionState#requirements) def requirements current_state = state || Gem::Molinillo::ResolutionState.empty current_state.requirements end # (see Gem::Molinillo::ResolutionState#activated) def activated current_state = state || Gem::Molinillo::ResolutionState.empty current_state.activated end # (see Gem::Molinillo::ResolutionState#requirement) def requirement current_state = state || Gem::Molinillo::ResolutionState.empty current_state.requirement end # (see Gem::Molinillo::ResolutionState#possibilities) def possibilities current_state = state || Gem::Molinillo::ResolutionState.empty current_state.possibilities end # (see Gem::Molinillo::ResolutionState#depth) def depth current_state = state || Gem::Molinillo::ResolutionState.empty current_state.depth end # (see Gem::Molinillo::ResolutionState#conflicts) def conflicts current_state = state || Gem::Molinillo::ResolutionState.empty current_state.conflicts end # (see Gem::Molinillo::ResolutionState#unused_unwind_options) def unused_unwind_options current_state = state || Gem::Molinillo::ResolutionState.empty current_state.unused_unwind_options end end end end PK!,3rubygems/vendor/molinillo/lib/molinillo/resolver.rbnu[# frozen_string_literal: true require_relative 'dependency_graph' module Gem::Molinillo # This class encapsulates a dependency resolver. # The resolver is responsible for determining which set of dependencies to # activate, with feedback from the {#specification_provider} # # class Resolver require_relative 'resolution' # @return [SpecificationProvider] the specification provider used # in the resolution process attr_reader :specification_provider # @return [UI] the UI module used to communicate back to the user # during the resolution process attr_reader :resolver_ui # Initializes a new resolver. # @param [SpecificationProvider] specification_provider # see {#specification_provider} # @param [UI] resolver_ui # see {#resolver_ui} def initialize(specification_provider, resolver_ui) @specification_provider = specification_provider @resolver_ui = resolver_ui end # Resolves the requested dependencies into a {DependencyGraph}, # locking to the base dependency graph (if specified) # @param [Array] requested an array of 'requested' dependencies that the # {#specification_provider} can understand # @param [DependencyGraph,nil] base the base dependency graph to which # dependencies should be 'locked' def resolve(requested, base = DependencyGraph.new) Resolution.new(specification_provider, resolver_ui, requested, base). resolve end end end PK!Eã5rubygems/vendor/molinillo/lib/molinillo/modules/ui.rbnu[# frozen_string_literal: true module Gem::Molinillo # Conveys information about the resolution process to a user. module UI # The {IO} object that should be used to print output. `STDOUT`, by default. # # @return [IO] def output STDOUT end # Called roughly every {#progress_rate}, this method should convey progress # to the user. # # @return [void] def indicate_progress output.print '.' unless debug? end # How often progress should be conveyed to the user via # {#indicate_progress}, in seconds. A third of a second, by default. # # @return [Float] def progress_rate 0.33 end # Called before resolution begins. # # @return [void] def before_resolution output.print 'Resolving dependencies...' end # Called after resolution ends (either successfully or with an error). # By default, prints a newline. # # @return [void] def after_resolution output.puts end # Conveys debug information to the user. # # @param [Integer] depth the current depth of the resolution process. # @return [void] def debug(depth = 0) if debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } output.puts debug_info end end # Whether or not debug messages should be printed. # By default, whether or not the `MOLINILLO_DEBUG` environment variable is # set. # # @return [Boolean] def debug? return @debug_mode if defined?(@debug_mode) @debug_mode = ENV['MOLINILLO_DEBUG'] end end end PK!օSSIrubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rbnu[# frozen_string_literal: true module Gem::Molinillo # Provides information about specifications and dependencies to the resolver, # allowing the {Resolver} class to remain generic while still providing power # and flexibility. # # This module contains the methods that users of Gem::Molinillo must to implement, # using knowledge of their own model classes. module SpecificationProvider # Search for the specifications that match the given dependency. # The specifications in the returned array will be considered in reverse # order, so the latest version ought to be last. # @note This method should be 'pure', i.e. the return value should depend # only on the `dependency` parameter. # # @param [Object] dependency # @return [Array] the specifications that satisfy the given # `dependency`. def search_for(dependency) [] end # Returns the dependencies of `specification`. # @note This method should be 'pure', i.e. the return value should depend # only on the `specification` parameter. # # @param [Object] specification # @return [Array] the dependencies that are required by the given # `specification`. def dependencies_for(specification) [] end # Determines whether the given `requirement` is satisfied by the given # `spec`, in the context of the current `activated` dependency graph. # # @param [Object] requirement # @param [DependencyGraph] activated the current dependency graph in the # resolution process. # @param [Object] spec # @return [Boolean] whether `requirement` is satisfied by `spec` in the # context of the current `activated` dependency graph. def requirement_satisfied_by?(requirement, activated, spec) true end # Determines whether two arrays of dependencies are equal, and thus can be # grouped. # # @param [Array] dependencies # @param [Array] other_dependencies # @return [Boolean] whether `dependencies` and `other_dependencies` should # be considered equal. def dependencies_equal?(dependencies, other_dependencies) dependencies == other_dependencies end # Returns the name for the given `dependency`. # @note This method should be 'pure', i.e. the return value should depend # only on the `dependency` parameter. # # @param [Object] dependency # @return [String] the name for the given `dependency`. def name_for(dependency) dependency.to_s end # @return [String] the name of the source of explicit dependencies, i.e. # those passed to {Resolver#resolve} directly. def name_for_explicit_dependency_source 'user-specified dependency' end # @return [String] the name of the source of 'locked' dependencies, i.e. # those passed to {Resolver#resolve} directly as the `base` def name_for_locking_dependency_source 'Lockfile' end # Sort dependencies so that the ones that are easiest to resolve are first. # Easiest to resolve is (usually) defined by: # 1) Is this dependency already activated? # 2) How relaxed are the requirements? # 3) Are there any conflicts for this dependency? # 4) How many possibilities are there to satisfy this dependency? # # @param [Array] dependencies # @param [DependencyGraph] activated the current dependency graph in the # resolution process. # @param [{String => Array}] conflicts # @return [Array] a sorted copy of `dependencies`. def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by do |dependency| name = name_for(dependency) [ activated.vertex_named(name).payload ? 0 : 1, conflicts[name] ? 0 : 1, ] end end # Returns whether this dependency, which has no possible matching # specifications, can safely be ignored. # # @param [Object] dependency # @return [Boolean] whether this dependency can safely be skipped. def allow_missing?(dependency) false end end end PK!JT܂5rubygems/vendor/molinillo/lib/molinillo/resolution.rbnu[# frozen_string_literal: true module Gem::Molinillo class Resolver # A specific resolution from a given {Resolver} class Resolution # A conflict that the resolution process encountered # @attr [Object] requirement the requirement that immediately led to the conflict # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict # @attr [Object, nil] existing the existing spec that was in conflict with # the {#possibility} # @attr [Object] possibility_set the set of specs that was unable to be # activated due to a conflict. # @attr [Object] locked_requirement the relevant locking requirement. # @attr [Array>] requirement_trees the different requirement # trees that led to every requirement for the conflicting name. # @attr [{String=>Object}] activated_by_name the already-activated specs. # @attr [Object] underlying_error an error that has occurred during resolution, and # will be raised at the end of it if no resolution is found. Conflict = Struct.new( :requirement, :requirements, :existing, :possibility_set, :locked_requirement, :requirement_trees, :activated_by_name, :underlying_error ) class Conflict # @return [Object] a spec that was unable to be activated due to a conflict def possibility possibility_set && possibility_set.latest_version end end # A collection of possibility states that share the same dependencies # @attr [Array] dependencies the dependencies for this set of possibilities # @attr [Array] possibilities the possibilities PossibilitySet = Struct.new(:dependencies, :possibilities) class PossibilitySet # String representation of the possibility set, for debugging def to_s "[#{possibilities.join(', ')}]" end # @return [Object] most up-to-date dependency in the possibility set def latest_version possibilities.last end end # Details of the state to unwind to when a conflict occurs, and the cause of the unwind # @attr [Integer] state_index the index of the state to unwind to # @attr [Object] state_requirement the requirement of the state we're unwinding to # @attr [Array] requirement_tree for the requirement we're relaxing # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict # @attr [Array] requirement_trees for the conflict # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind UnwindDetails = Struct.new( :state_index, :state_requirement, :requirement_tree, :conflicting_requirements, :requirement_trees, :requirements_unwound_to_instead ) class UnwindDetails include Comparable # We compare UnwindDetails when choosing which state to unwind to. If # two options have the same state_index we prefer the one most # removed from a requirement that caused the conflict. Both options # would unwind to the same state, but a `grandparent` option will # filter out fewer of its possibilities after doing so - where a state # is both a `parent` and a `grandparent` to requirements that have # caused a conflict this is the correct behaviour. # @param [UnwindDetail] other UnwindDetail to be compared # @return [Integer] integer specifying ordering def <=>(other) if state_index > other.state_index 1 elsif state_index == other.state_index reversed_requirement_tree_index <=> other.reversed_requirement_tree_index else -1 end end # @return [Integer] index of state requirement in reversed requirement tree # (the conflicting requirement itself will be at position 0) def reversed_requirement_tree_index @reversed_requirement_tree_index ||= if state_requirement requirement_tree.reverse.index(state_requirement) else 999_999 end end # @return [Boolean] where the requirement of the state we're unwinding # to directly caused the conflict. Note: in this case, it is # impossible for the state we're unwinding to be a parent of # any of the other conflicting requirements (or we would have # circularity) def unwinding_to_primary_requirement? requirement_tree.last == state_requirement end # @return [Array] array of sub-dependencies to avoid when choosing a # new possibility for the state we've unwound to. Only relevant for # non-primary unwinds def sub_dependencies_to_avoid @requirements_to_avoid ||= requirement_trees.map do |tree| index = tree.index(state_requirement) tree[index + 1] if index end.compact end # @return [Array] array of all the requirements that led to the need for # this unwind def all_requirements @all_requirements ||= requirement_trees.flatten(1) end end # @return [SpecificationProvider] the provider that knows about # dependencies, requirements, specifications, versions, etc. attr_reader :specification_provider # @return [UI] the UI that knows how to communicate feedback about the # resolution process back to the user attr_reader :resolver_ui # @return [DependencyGraph] the base dependency graph to which # dependencies should be 'locked' attr_reader :base # @return [Array] the dependencies that were explicitly required attr_reader :original_requested # Initializes a new resolution. # @param [SpecificationProvider] specification_provider # see {#specification_provider} # @param [UI] resolver_ui see {#resolver_ui} # @param [Array] requested see {#original_requested} # @param [DependencyGraph] base see {#base} def initialize(specification_provider, resolver_ui, requested, base) @specification_provider = specification_provider @resolver_ui = resolver_ui @original_requested = requested @base = base @states = [] @iteration_counter = 0 @parents_of = Hash.new { |h, k| h[k] = [] } end # Resolves the {#original_requested} dependencies into a full dependency # graph # @raise [ResolverError] if successful resolution is impossible # @return [DependencyGraph] the dependency graph of successfully resolved # dependencies def resolve start_resolution while state break if !state.requirement && state.requirements.empty? indicate_progress if state.respond_to?(:pop_possibility_state) # DependencyState debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } state.pop_possibility_state.tap do |s| if s states.push(s) activated.tag(s) end end end process_topmost_state end resolve_activated_specs ensure end_resolution end # @return [Integer] the number of resolver iterations in between calls to # {#resolver_ui}'s {UI#indicate_progress} method attr_accessor :iteration_rate private :iteration_rate # @return [Time] the time at which resolution began attr_accessor :started_at private :started_at # @return [Array] the stack of states for the resolution attr_accessor :states private :states private # Sets up the resolution process # @return [void] def start_resolution @started_at = Time.now push_initial_state debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } resolver_ui.before_resolution end def resolve_activated_specs activated.vertices.each do |_, vertex| next unless vertex.payload latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } end activated.set_payload(vertex.name, latest_version) end activated.freeze end # Ends the resolution process # @return [void] def end_resolution resolver_ui.after_resolution debug do "Finished resolution (#{@iteration_counter} steps) " \ "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" end debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state end require_relative 'state' require_relative 'modules/specification_provider' require_relative 'delegates/resolution_state' require_relative 'delegates/specification_provider' include Gem::Molinillo::Delegates::ResolutionState include Gem::Molinillo::Delegates::SpecificationProvider # Processes the topmost available {RequirementState} on the stack # @return [void] def process_topmost_state if possibility attempt_to_activate else create_conflict unwind_for_conflict end rescue CircularDependencyError => underlying_error create_conflict(underlying_error) unwind_for_conflict end # @return [Object] the current possibility that the resolution is trying # to activate def possibility possibilities.last end # @return [RequirementState] the current state the resolution is # operating upon def state states.last end # Creates and pushes the initial state for the resolution, based upon the # {#requested} dependencies # @return [void] def push_initial_state graph = DependencyGraph.new.tap do |dg| original_requested.each do |requested| vertex = dg.add_vertex(name_for(requested), nil, true) vertex.explicit_requirements << requested end dg.tag(:initial_state) end push_state_for_requirements(original_requested, true, graph) end # Unwinds the states stack because a conflict has been encountered # @return [void] def unwind_for_conflict details_for_unwind = build_details_for_unwind unwind_options = unused_unwind_options debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } conflicts.tap do |c| sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) raise_error_unless_state(c) activated.rewind_to(sliced_states.first || :initial_state) if sliced_states state.conflicts = c state.unused_unwind_options = unwind_options filter_possibilities_after_unwind(details_for_unwind) index = states.size - 1 @parents_of.each { |_, a| a.reject! { |i| i >= index } } state.unused_unwind_options.reject! { |uw| uw.state_index >= index } end end # Raises a VersionConflict error, or any underlying error, if there is no # current state # @return [void] def raise_error_unless_state(conflicts) return if state error = conflicts.values.map(&:underlying_error).compact.first raise error || VersionConflict.new(conflicts, specification_provider) end # @return [UnwindDetails] Details of the nearest index to which we could unwind def build_details_for_unwind # Get the possible unwinds for the current conflict current_conflict = conflicts[name] binding_requirements = binding_requirements_for_conflict(current_conflict) unwind_details = unwind_options_for_requirements(binding_requirements) last_detail_for_current_unwind = unwind_details.sort.last current_detail = last_detail_for_current_unwind # Look for past conflicts that could be unwound to affect the # requirement tree for the current conflict all_reqs = last_detail_for_current_unwind.all_requirements all_reqs_size = all_reqs.size relevant_unused_unwinds = unused_unwind_options.select do |alternative| diff_reqs = all_reqs - alternative.requirements_unwound_to_instead next if diff_reqs.size == all_reqs_size # Find the highest index unwind whilst looping through current_detail = alternative if alternative > current_detail alternative end # Add the current unwind options to the `unused_unwind_options` array. # The "used" option will be filtered out during `unwind_for_conflict`. state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } # Update the requirements_unwound_to_instead on any relevant unused unwinds relevant_unused_unwinds.each do |d| (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! end unwind_details.each do |d| (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! end current_detail end # @param [Array] binding_requirements array of requirements that combine to create a conflict # @return [Array] array of UnwindDetails that have a chance # of resolving the passed requirements def unwind_options_for_requirements(binding_requirements) unwind_details = [] trees = [] binding_requirements.reverse_each do |r| partial_tree = [r] trees << partial_tree unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) # If this requirement has alternative possibilities, check if any would # satisfy the other requirements that created this conflict requirement_state = find_state_for(r) if conflict_fixing_possibilities?(requirement_state, binding_requirements) unwind_details << UnwindDetails.new( states.index(requirement_state), r, partial_tree, binding_requirements, trees, [] ) end # Next, look at the parent of this requirement, and check if the requirement # could have been avoided if an alternative PossibilitySet had been chosen parent_r = parent_of(r) next if parent_r.nil? partial_tree.unshift(parent_r) requirement_state = find_state_for(parent_r) if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } unwind_details << UnwindDetails.new( states.index(requirement_state), parent_r, partial_tree, binding_requirements, trees, [] ) end # Finally, look at the grandparent and up of this requirement, looking # for any possibilities that wouldn't create their parent requirement grandparent_r = parent_of(parent_r) until grandparent_r.nil? partial_tree.unshift(grandparent_r) requirement_state = find_state_for(grandparent_r) if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } unwind_details << UnwindDetails.new( states.index(requirement_state), grandparent_r, partial_tree, binding_requirements, trees, [] ) end parent_r = grandparent_r grandparent_r = parent_of(parent_r) end end unwind_details end # @param [DependencyState] state # @param [Array] binding_requirements array of requirements # @return [Boolean] whether or not the given state has any possibilities # that could satisfy the given requirements def conflict_fixing_possibilities?(state, binding_requirements) return false unless state state.possibilities.any? do |possibility_set| possibility_set.possibilities.any? do |poss| possibility_satisfies_requirements?(poss, binding_requirements) end end end # Filter's a state's possibilities to remove any that would not fix the # conflict we've just rewound from # @param [UnwindDetails] unwind_details details of the conflict just # unwound from # @return [void] def filter_possibilities_after_unwind(unwind_details) return unless state && !state.possibilities.empty? if unwind_details.unwinding_to_primary_requirement? filter_possibilities_for_primary_unwind(unwind_details) else filter_possibilities_for_parent_unwind(unwind_details) end end # Filter's a state's possibilities to remove any that would not satisfy # the requirements in the conflict we've just rewound from # @param [UnwindDetails] unwind_details details of the conflict just unwound from # @return [void] def filter_possibilities_for_primary_unwind(unwind_details) unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } unwinds_to_state << unwind_details unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) state.possibilities.reject! do |possibility_set| possibility_set.possibilities.none? do |poss| unwind_requirement_sets.any? do |requirements| possibility_satisfies_requirements?(poss, requirements) end end end end # @param [Object] possibility a single possibility # @param [Array] requirements an array of requirements # @return [Boolean] whether the possibility satisfies all of the # given requirements def possibility_satisfies_requirements?(possibility, requirements) name = name_for(possibility) activated.tag(:swap) activated.set_payload(name, possibility) if activated.vertex_named(name) satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } activated.rewind_to(:swap) satisfied end # Filter's a state's possibilities to remove any that would (eventually) # create a requirement in the conflict we've just rewound from # @param [UnwindDetails] unwind_details details of the conflict just unwound from # @return [void] def filter_possibilities_for_parent_unwind(unwind_details) unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } unwinds_to_state << unwind_details primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq parent_unwinds = unwinds_to_state.uniq - primary_unwinds allowed_possibility_sets = primary_unwinds.flat_map do |unwind| states[unwind.state_index].possibilities.select do |possibility_set| possibility_set.possibilities.any? do |poss| possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) end end end requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) state.possibilities.reject! do |possibility_set| !allowed_possibility_sets.include?(possibility_set) && (requirements_to_avoid - possibility_set.dependencies).empty? end end # @param [Conflict] conflict # @return [Array] minimal array of requirements that would cause the passed # conflict to occur. def binding_requirements_for_conflict(conflict) return [conflict.requirement] if conflict.possibility.nil? possible_binding_requirements = conflict.requirements.values.flatten(1).uniq # When there's a `CircularDependency` error the conflicting requirement # (the one causing the circular) won't be `conflict.requirement` # (which won't be for the right state, because we won't have created it, # because it's circular). # We need to make sure we have that requirement in the conflict's list, # otherwise we won't be able to unwind properly, so we just return all # the requirements for the conflict. return possible_binding_requirements if conflict.underlying_error possibilities = search_for(conflict.requirement) # If all the requirements together don't filter out all possibilities, # then the only two requirements we need to consider are the initial one # (where the dependency's version was first chosen) and the last if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact end # Loop through the possible binding requirements, removing each one # that doesn't bind. Use a `reverse_each` as we want the earliest set of # binding requirements, and don't use `reject!` as we wish to refine the # array *on each iteration*. binding_requirements = possible_binding_requirements.dup possible_binding_requirements.reverse_each do |req| next if req == conflict.requirement unless binding_requirement_in_set?(req, binding_requirements, possibilities) binding_requirements -= [req] end end binding_requirements end # @param [Object] requirement we wish to check # @param [Array] possible_binding_requirements array of requirements # @param [Array] possibilities array of possibilities the requirements will be used to filter # @return [Boolean] whether or not the given requirement is required to filter # out all elements of the array of possibilities. def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) possibilities.any? do |poss| possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) end end # @param [Object] requirement # @return [Object] the requirement that led to `requirement` being added # to the list of requirements. def parent_of(requirement) return unless requirement return unless index = @parents_of[requirement].last return unless parent_state = @states[index] parent_state.requirement end # @param [String] name # @return [Object] the requirement that led to a version of a possibility # with the given name being activated. def requirement_for_existing_name(name) return nil unless vertex = activated.vertex_named(name) return nil unless vertex.payload states.find { |s| s.name == name }.requirement end # @param [Object] requirement # @return [ResolutionState] the state whose `requirement` is the given # `requirement`. def find_state_for(requirement) return nil unless requirement states.find { |i| requirement == i.requirement } end # @param [Object] underlying_error # @return [Conflict] a {Conflict} that reflects the failure to activate # the {#possibility} in conjunction with the current {#state} def create_conflict(underlying_error = nil) vertex = activated.vertex_named(name) locked_requirement = locked_requirement_named(name) requirements = {} unless vertex.explicit_requirements.empty? requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements end requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement vertex.incoming_edges.each do |edge| (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) end activated_by_name = {} activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } conflicts[name] = Conflict.new( requirement, requirements, vertex.payload && vertex.payload.latest_version, possibility, locked_requirement, requirement_trees, activated_by_name, underlying_error ) end # @return [Array>] The different requirement # trees that led to every requirement for the current spec. def requirement_trees vertex = activated.vertex_named(name) vertex.requirements.map { |r| requirement_tree_for(r) } end # @param [Object] requirement # @return [Array] the list of requirements that led to # `requirement` being required. def requirement_tree_for(requirement) tree = [] while requirement tree.unshift(requirement) requirement = parent_of(requirement) end tree end # Indicates progress roughly once every second # @return [void] def indicate_progress @iteration_counter += 1 @progress_rate ||= resolver_ui.progress_rate if iteration_rate.nil? if Time.now - started_at >= @progress_rate self.iteration_rate = @iteration_counter end end if iteration_rate && (@iteration_counter % iteration_rate) == 0 resolver_ui.indicate_progress end end # Calls the {#resolver_ui}'s {UI#debug} method # @param [Integer] depth the depth of the {#states} stack # @param [Proc] block a block that yields a {#to_s} # @return [void] def debug(depth = 0, &block) resolver_ui.debug(depth, &block) end # Attempts to activate the current {#possibility} # @return [void] def attempt_to_activate debug(depth) { 'Attempting to activate ' + possibility.to_s } existing_vertex = activated.vertex_named(name) if existing_vertex.payload debug(depth) { "Found existing spec (#{existing_vertex.payload})" } attempt_to_filter_existing_spec(existing_vertex) else latest = possibility.latest_version possibility.possibilities.select! do |possibility| requirement_satisfied_by?(requirement, activated, possibility) end if possibility.latest_version.nil? # ensure there's a possibility for better error messages possibility.possibilities << latest if latest create_conflict unwind_for_conflict else activate_new_spec end end end # Attempts to update the existing vertex's `PossibilitySet` with a filtered version # @return [void] def attempt_to_filter_existing_spec(vertex) filtered_set = filtered_possibility_set(vertex) if !filtered_set.possibilities.empty? activated.set_payload(name, filtered_set) new_requirements = requirements.dup push_state_for_requirements(new_requirements, false) else create_conflict debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } unwind_for_conflict end end # Generates a filtered version of the existing vertex's `PossibilitySet` using the # current state's `requirement` # @param [Object] vertex existing vertex # @return [PossibilitySet] filtered possibility set def filtered_possibility_set(vertex) PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) end # @param [String] requirement_name the spec name to search for # @return [Object] the locked spec named `requirement_name`, if one # is found on {#base} def locked_requirement_named(requirement_name) vertex = base.vertex_named(requirement_name) vertex && vertex.payload end # Add the current {#possibility} to the dependency graph of the current # {#state} # @return [void] def activate_new_spec conflicts.delete(name) debug(depth) { "Activated #{name} at #{possibility}" } activated.set_payload(name, possibility) require_nested_dependencies_for(possibility) end # Requires the dependencies that the recently activated spec has # @param [Object] possibility_set the PossibilitySet that has just been # activated # @return [void] def require_nested_dependencies_for(possibility_set) nested_dependencies = dependencies_for(possibility_set.latest_version) debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } nested_dependencies.each do |d| activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) parent_index = states.size - 1 parents = @parents_of[d] parents << parent_index if parents.empty? end push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) end # Pushes a new {DependencyState} that encapsulates both existing and new # requirements # @param [Array] new_requirements # @param [Boolean] requires_sort # @param [Object] new_activated # @return [void] def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort new_requirement = nil loop do new_requirement = new_requirements.shift break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } end new_name = new_requirement ? name_for(new_requirement) : ''.freeze possibilities = possibilities_for_requirement(new_requirement) handle_missing_or_push_dependency_state DependencyState.new( new_name, new_requirements, new_activated, new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup ) end # Checks a proposed requirement with any existing locked requirement # before generating an array of possibilities for it. # @param [Object] requirement the proposed requirement # @param [Object] activated # @return [Array] possibilities def possibilities_for_requirement(requirement, activated = self.activated) return [] unless requirement if locked_requirement_named(name_for(requirement)) return locked_requirement_possibility_set(requirement, activated) end group_possibilities(search_for(requirement)) end # @param [Object] requirement the proposed requirement # @param [Object] activated # @return [Array] possibility set containing only the locked requirement, if any def locked_requirement_possibility_set(requirement, activated = self.activated) all_possibilities = search_for(requirement) locked_requirement = locked_requirement_named(name_for(requirement)) # Longwinded way to build a possibilities array with either the locked # requirement or nothing in it. Required, since the API for # locked_requirement isn't guaranteed. locked_possibilities = all_possibilities.select do |possibility| requirement_satisfied_by?(locked_requirement, activated, possibility) end group_possibilities(locked_possibilities) end # Build an array of PossibilitySets, with each element representing a group of # dependency versions that all have the same sub-dependency version constraints # and are contiguous. # @param [Array] possibilities an array of possibilities # @return [Array] an array of possibility sets def group_possibilities(possibilities) possibility_sets = [] current_possibility_set = nil possibilities.reverse_each do |possibility| dependencies = dependencies_for(possibility) if current_possibility_set && dependencies_equal?(current_possibility_set.dependencies, dependencies) current_possibility_set.possibilities.unshift(possibility) else possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) current_possibility_set = possibility_sets.first end end possibility_sets end # Pushes a new {DependencyState}. # If the {#specification_provider} says to # {SpecificationProvider#allow_missing?} that particular requirement, and # there are no possibilities for that requirement, then `state` is not # pushed, and the vertex in {#activated} is removed, and we continue # resolving the remaining requirements. # @param [DependencyState] state # @return [void] def handle_missing_or_push_dependency_state(state) if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) state.activated.detach_vertex_named(state.name) push_state_for_requirements(state.requirements.dup, false, state.activated) else states.push(state).tap { activated.tag(state) } end end end end end PK!$$0rubygems/vendor/molinillo/lib/molinillo/state.rbnu[# frozen_string_literal: true module Gem::Molinillo # A state that a {Resolution} can be in # @attr [String] name the name of the current requirement # @attr [Array] requirements currently unsatisfied requirements # @attr [DependencyGraph] activated the graph of activated dependencies # @attr [Object] requirement the current requirement # @attr [Object] possibilities the possibilities to satisfy the current requirement # @attr [Integer] depth the depth of the resolution # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name # @attr [Array] unused_unwind_options unwinds for previous conflicts that weren't explored ResolutionState = Struct.new( :name, :requirements, :activated, :requirement, :possibilities, :depth, :conflicts, :unused_unwind_options ) class ResolutionState # Returns an empty resolution state # @return [ResolutionState] an empty state def self.empty new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) end end # A state that encapsulates a set of {#requirements} with an {Array} of # possibilities class DependencyState < ResolutionState # Removes a possibility from `self` # @return [PossibilityState] a state with a single possibility, # the possibility that was removed from `self` def pop_possibility_state PossibilityState.new( name, requirements.dup, activated, requirement, [possibilities.pop], depth + 1, conflicts.dup, unused_unwind_options.dup ).tap do |state| state.activated.tag(state) end end end # A state that encapsulates a single possibility to fulfill the given # {#requirement} class PossibilityState < ResolutionState end end PK!~R ;rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rbnu[# frozen_string_literal: true require_relative '../../../../vendored_tsort' require_relative 'dependency_graph/log' require_relative 'dependency_graph/vertex' module Gem::Molinillo # A directed acyclic graph that is tuned to hold named dependencies class DependencyGraph include Enumerable # Enumerates through the vertices of the graph. # @return [Array] The graph's vertices. def each return vertices.values.each unless block_given? vertices.values.each { |v| yield v } end include Gem::TSort # @!visibility private alias tsort_each_node each # @!visibility private def tsort_each_child(vertex, &block) vertex.successors.each(&block) end # Topologically sorts the given vertices. # @param [Enumerable] vertices the vertices to be sorted, which must # all belong to the same graph. # @return [Array] The sorted vertices. def self.tsort(vertices) Gem::TSort.tsort( lambda { |b| vertices.each(&b) }, lambda { |v, &b| (v.successors & vertices).each(&b) } ) end # A directed edge of a {DependencyGraph} # @attr [Vertex] origin The origin of the directed edge # @attr [Vertex] destination The destination of the directed edge # @attr [Object] requirement The requirement the directed edge represents Edge = Struct.new(:origin, :destination, :requirement) # @return [{String => Vertex}] the vertices of the dependency graph, keyed # by {Vertex#name} attr_reader :vertices # @return [Log] the op log for this graph attr_reader :log # Initializes an empty dependency graph def initialize @vertices = {} @log = Log.new end # Tags the current state of the dependency as the given tag # @param [Object] tag an opaque tag for the current state of the graph # @return [Void] def tag(tag) log.tag(self, tag) end # Rewinds the graph to the state tagged as `tag` # @param [Object] tag the tag to rewind to # @return [Void] def rewind_to(tag) log.rewind_to(self, tag) end # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} # are properly copied. # @param [DependencyGraph] other the graph to copy. def initialize_copy(other) super @vertices = {} @log = other.log.dup traverse = lambda do |new_v, old_v| return if new_v.outgoing_edges.size == old_v.outgoing_edges.size old_v.outgoing_edges.each do |edge| destination = add_vertex(edge.destination.name, edge.destination.payload) add_edge_no_circular(new_v, destination, edge.requirement) traverse.call(destination, edge.destination) end end other.vertices.each do |name, vertex| new_vertex = add_vertex(name, vertex.payload, vertex.root?) new_vertex.explicit_requirements.replace(vertex.explicit_requirements) traverse.call(new_vertex, vertex) end end # @return [String] a string suitable for debugging def inspect "#{self.class}:#{vertices.values.inspect}" end # @param [Hash] options options for dot output. # @return [String] Returns a dot format representation of the graph def to_dot(options = {}) edge_label = options.delete(:edge_label) raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? dot_vertices = [] dot_edges = [] vertices.each do |n, v| dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" v.outgoing_edges.each do |e| label = edge_label ? edge_label.call(e) : e.requirement dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" end end dot_vertices.uniq! dot_vertices.sort! dot_edges.uniq! dot_edges.sort! dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') dot.join("\n") end # @param [DependencyGraph] other # @return [Boolean] whether the two dependency graphs are equal, determined # by a recursive traversal of each {#root_vertices} and its # {Vertex#successors} def ==(other) return false unless other return true if equal?(other) vertices.each do |name, vertex| other_vertex = other.vertex_named(name) return false unless other_vertex return false unless vertex.payload == other_vertex.payload return false unless other_vertex.successors.to_set == vertex.successors.to_set end end # @param [String] name # @param [Object] payload # @param [Array] parent_names # @param [Object] requirement the requirement that is requiring the child # @return [void] def add_child_vertex(name, payload, parent_names, requirement) root = !parent_names.delete(nil) { true } vertex = add_vertex(name, payload, root) vertex.explicit_requirements << requirement if root parent_names.each do |parent_name| parent_vertex = vertex_named(parent_name) add_edge(parent_vertex, vertex, requirement) end vertex end # Adds a vertex with the given name, or updates the existing one. # @param [String] name # @param [Object] payload # @return [Vertex] the vertex that was added to `self` def add_vertex(name, payload, root = false) log.add_vertex(self, name, payload, root) end # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively # removing any non-root vertices that were orphaned in the process # @param [String] name # @return [Array] the vertices which have been detached def detach_vertex_named(name) log.detach_vertex_named(self, name) end # @param [String] name # @return [Vertex,nil] the vertex with the given name def vertex_named(name) vertices[name] end # @param [String] name # @return [Vertex,nil] the root vertex with the given name def root_vertex_named(name) vertex = vertex_named(name) vertex if vertex && vertex.root? end # Adds a new {Edge} to the dependency graph # @param [Vertex] origin # @param [Vertex] destination # @param [Object] requirement the requirement that this edge represents # @return [Edge] the added edge def add_edge(origin, destination, requirement) if destination.path_to?(origin) raise CircularDependencyError.new(path(destination, origin)) end add_edge_no_circular(origin, destination, requirement) end # Deletes an {Edge} from the dependency graph # @param [Edge] edge # @return [Void] def delete_edge(edge) log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) end # Sets the payload of the vertex with the given name # @param [String] name the name of the vertex # @param [Object] payload the payload # @return [Void] def set_payload(name, payload) log.set_payload(self, name, payload) end private # Adds a new {Edge} to the dependency graph without checking for # circularity. # @param (see #add_edge) # @return (see #add_edge) def add_edge_no_circular(origin, destination, requirement) log.add_edge_no_circular(self, origin.name, destination.name, requirement) end # Returns the path between two vertices # @raise [ArgumentError] if there is no path between the vertices # @param [Vertex] from # @param [Vertex] to # @return [Array] the shortest path from `from` to `to` def path(from, to) distances = Hash.new(vertices.size + 1) distances[from.name] = 0 predecessors = {} each do |vertex| vertex.successors.each do |successor| if distances[successor.name] > distances[vertex.name] + 1 distances[successor.name] = distances[vertex.name] + 1 predecessors[successor] = vertex end end end path = [to] while before = predecessors[to] path << before to = before break if to == from end unless path.last.equal?(from) raise ArgumentError, "There is no path from #{from.name} to #{to.name}" end path.reverse end end end PK!^ژY.Y.0rubygems/vendor/net-protocol/lib/net/protocol.rbnu[# frozen_string_literal: true # # = net/protocol.rb # #-- # Copyright (c) 1999-2004 Yukihiro Matsumoto # Copyright (c) 1999-2004 Minero Aoki # # written and maintained by Minero Aoki # # This program is free software. You can re-distribute and/or # modify this program under the same terms as Ruby itself, # Ruby Distribute License or GNU General Public License. # # $Id$ #++ # # WARNING: This file is going to remove. # Do not rely on the implementation written in this file. # require 'socket' require_relative '../../../timeout/lib/timeout' require 'io/wait' module Gem::Net # :nodoc: class Protocol #:nodoc: internal use only VERSION = "0.2.2" private def Protocol.protocol_param(name, val) module_eval(<<-End, __FILE__, __LINE__ + 1) def #{name} #{val} end End end def ssl_socket_connect(s, timeout) if timeout while true raise Gem::Net::OpenTimeout if timeout <= 0 start = Process.clock_gettime Process::CLOCK_MONOTONIC # to_io is required because SSLSocket doesn't have wait_readable yet case s.connect_nonblock(exception: false) when :wait_readable; s.to_io.wait_readable(timeout) when :wait_writable; s.to_io.wait_writable(timeout) else; break end timeout -= Process.clock_gettime(Process::CLOCK_MONOTONIC) - start end else s.connect end end end class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end class ProtoFatalError < ProtocolError; end class ProtoUnknownError < ProtocolError; end class ProtoServerError < ProtocolError; end class ProtoAuthError < ProtocolError; end class ProtoCommandError < ProtocolError; end class ProtoRetriableError < ProtocolError; end ProtocRetryError = ProtoRetriableError ## # OpenTimeout, a subclass of Gem::Timeout::Error, is raised if a connection cannot # be created within the open_timeout. class OpenTimeout < Gem::Timeout::Error; end ## # ReadTimeout, a subclass of Gem::Timeout::Error, is raised if a chunk of the # response cannot be read within the read_timeout. class ReadTimeout < Gem::Timeout::Error def initialize(io = nil) @io = io end attr_reader :io def message msg = super if @io msg = "#{msg} with #{@io.inspect}" end msg end end ## # WriteTimeout, a subclass of Gem::Timeout::Error, is raised if a chunk of the # response cannot be written within the write_timeout. Not raised on Windows. class WriteTimeout < Gem::Timeout::Error def initialize(io = nil) @io = io end attr_reader :io def message msg = super if @io msg = "#{msg} with #{@io.inspect}" end msg end end class BufferedIO #:nodoc: internal use only def initialize(io, read_timeout: 60, write_timeout: 60, continue_timeout: nil, debug_output: nil) @io = io @read_timeout = read_timeout @write_timeout = write_timeout @continue_timeout = continue_timeout @debug_output = debug_output @rbuf = ''.b @rbuf_empty = true @rbuf_offset = 0 end attr_reader :io attr_accessor :read_timeout attr_accessor :write_timeout attr_accessor :continue_timeout attr_accessor :debug_output def inspect "#<#{self.class} io=#{@io}>" end def eof? @io.eof? end def closed? @io.closed? end def close @io.close end # # Read # public def read(len, dest = ''.b, ignore_eof = false) LOG "reading #{len} bytes..." read_bytes = 0 begin while read_bytes + rbuf_size < len if s = rbuf_consume_all read_bytes += s.bytesize dest << s end rbuf_fill end s = rbuf_consume(len - read_bytes) read_bytes += s.bytesize dest << s rescue EOFError raise unless ignore_eof end LOG "read #{read_bytes} bytes" dest end def read_all(dest = ''.b) LOG 'reading all...' read_bytes = 0 begin while true if s = rbuf_consume_all read_bytes += s.bytesize dest << s end rbuf_fill end rescue EOFError ; end LOG "read #{read_bytes} bytes" dest end def readuntil(terminator, ignore_eof = false) offset = @rbuf_offset begin until idx = @rbuf.index(terminator, offset) offset = @rbuf.bytesize rbuf_fill end return rbuf_consume(idx + terminator.bytesize - @rbuf_offset) rescue EOFError raise unless ignore_eof return rbuf_consume end end def readline readuntil("\n").chop end private BUFSIZE = 1024 * 16 def rbuf_fill tmp = @rbuf_empty ? @rbuf : nil case rv = @io.read_nonblock(BUFSIZE, tmp, exception: false) when String @rbuf_empty = false if rv.equal?(tmp) @rbuf_offset = 0 else @rbuf << rv rv.clear end return when :wait_readable (io = @io.to_io).wait_readable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io) # continue looping when :wait_writable # OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable. # http://www.openssl.org/support/faq.html#PROG10 (io = @io.to_io).wait_writable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io) # continue looping when nil raise EOFError, 'end of file reached' end while true end def rbuf_flush if @rbuf_empty @rbuf.clear @rbuf_offset = 0 end nil end def rbuf_size @rbuf.bytesize - @rbuf_offset end def rbuf_consume_all rbuf_consume if rbuf_size > 0 end def rbuf_consume(len = nil) if @rbuf_offset == 0 && (len.nil? || len == @rbuf.bytesize) s = @rbuf @rbuf = ''.b @rbuf_offset = 0 @rbuf_empty = true elsif len.nil? s = @rbuf.byteslice(@rbuf_offset..-1) @rbuf = ''.b @rbuf_offset = 0 @rbuf_empty = true else s = @rbuf.byteslice(@rbuf_offset, len) @rbuf_offset += len @rbuf_empty = @rbuf_offset == @rbuf.bytesize rbuf_flush end @debug_output << %Q[-> #{s.dump}\n] if @debug_output s end # # Write # public def write(*strs) writing { write0(*strs) } end alias << write def writeline(str) writing { write0 str + "\r\n" } end private def writing @written_bytes = 0 @debug_output << '<- ' if @debug_output yield @debug_output << "\n" if @debug_output bytes = @written_bytes @written_bytes = nil bytes end def write0(*strs) @debug_output << strs.map(&:dump).join if @debug_output orig_written_bytes = @written_bytes strs.each_with_index do |str, i| need_retry = true case len = @io.write_nonblock(str, exception: false) when Integer @written_bytes += len len -= str.bytesize if len == 0 if strs.size == i+1 return @written_bytes - orig_written_bytes else need_retry = false # next string end elsif len < 0 str = str.byteslice(len, -len) else # len > 0 need_retry = false # next string end # continue looping when :wait_writable (io = @io.to_io).wait_writable(@write_timeout) or raise Gem::Net::WriteTimeout.new(io) # continue looping end while need_retry end end # # Logging # private def LOG_off @save_debug_out = @debug_output @debug_output = nil end def LOG_on @debug_output = @save_debug_out end def LOG(msg) return unless @debug_output @debug_output << msg + "\n" end end class InternetMessageIO < BufferedIO #:nodoc: internal use only def initialize(*, **) super @wbuf = nil end # # Read # def each_message_chunk LOG 'reading message...' LOG_off() read_bytes = 0 while (line = readuntil("\r\n")) != ".\r\n" read_bytes += line.size yield line.delete_prefix('.') end LOG_on() LOG "read message (#{read_bytes} bytes)" end # *library private* (cannot handle 'break') def each_list_item while (str = readuntil("\r\n")) != ".\r\n" yield str.chop end end def write_message_0(src) prev = @written_bytes each_crlf_line(src) do |line| write0 dot_stuff(line) end @written_bytes - prev end # # Write # def write_message(src) LOG "writing message from #{src.class}" LOG_off() len = writing { using_each_crlf_line { write_message_0 src } } LOG_on() LOG "wrote #{len} bytes" len end def write_message_by_block(&block) LOG 'writing message from block' LOG_off() len = writing { using_each_crlf_line { begin block.call(WriteAdapter.new(self.method(:write_message_0))) rescue LocalJumpError # allow `break' from writer block end } } LOG_on() LOG "wrote #{len} bytes" len end private def dot_stuff(s) s.sub(/\A\./, '..') end def using_each_crlf_line @wbuf = ''.b yield if not @wbuf.empty? # unterminated last line write0 dot_stuff(@wbuf.chomp) + "\r\n" elsif @written_bytes == 0 # empty src write0 "\r\n" end write0 ".\r\n" @wbuf = nil end def each_crlf_line(src) buffer_filling(@wbuf, src) do while line = @wbuf.slice!(/\A[^\r\n]*(?:\n|\r(?:\n|(?!\z)))/) yield line.chomp("\n") + "\r\n" end end end def buffer_filling(buf, src) case src when String # for speeding up. 0.step(src.size - 1, 1024) do |i| buf << src[i, 1024] yield end when File # for speeding up. while s = src.read(1024) buf << s yield end else # generic reader src.each do |str| buf << str yield if buf.size > 1024 end yield unless buf.empty? end end end # # The writer adapter class # class WriteAdapter def initialize(writer) @writer = writer end def inspect "#<#{self.class} writer=#{@writer.inspect}>" end def write(str) @writer.call(str) end alias print write def <<(str) write str self end def puts(str = '') write str.chomp("\n") + "\n" end def printf(*args) write sprintf(*args) end end class ReadAdapter #:nodoc: internal use only def initialize(block) @block = block end def inspect "#<#{self.class}>" end def <<(str) call_block(str, &@block) if @block end private # This method is needed because @block must be called by yield, # not Proc#call. You can see difference when using `break' in # the block. def call_block(str) yield str end end module NetPrivate #:nodoc: obsolete Socket = ::Gem::Net::InternetMessageIO end end # module Gem::Net PK!"b  rubygems/vendor/uri/lib/uri.rbnu[# frozen_string_literal: false # Gem::URI is a module providing classes to handle Uniform Resource Identifiers # (RFC2396[http://tools.ietf.org/html/rfc2396]). # # == Features # # * Uniform way of handling URIs. # * Flexibility to introduce custom Gem::URI schemes. # * Flexibility to have an alternate Gem::URI::Parser (or just different patterns # and regexp's). # # == Basic example # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI("http://foo.com/posts?id=30&limit=5#time=1305298413") # #=> # # # uri.scheme #=> "http" # uri.host #=> "foo.com" # uri.path #=> "/posts" # uri.query #=> "id=30&limit=5" # uri.fragment #=> "time=1305298413" # # uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413" # # == Adding custom URIs # # module Gem::URI # class RSYNC < Generic # DEFAULT_PORT = 873 # end # register_scheme 'RSYNC', RSYNC # end # #=> Gem::URI::RSYNC # # Gem::URI.scheme_list # #=> {"FILE"=>Gem::URI::File, "FTP"=>Gem::URI::FTP, "HTTP"=>Gem::URI::HTTP, # # "HTTPS"=>Gem::URI::HTTPS, "LDAP"=>Gem::URI::LDAP, "LDAPS"=>Gem::URI::LDAPS, # # "MAILTO"=>Gem::URI::MailTo, "RSYNC"=>Gem::URI::RSYNC} # # uri = Gem::URI("rsync://rsync.foo.com") # #=> # # # == RFC References # # A good place to view an RFC spec is http://www.ietf.org/rfc.html. # # Here is a list of all related RFC's: # - RFC822[http://tools.ietf.org/html/rfc822] # - RFC1738[http://tools.ietf.org/html/rfc1738] # - RFC2255[http://tools.ietf.org/html/rfc2255] # - RFC2368[http://tools.ietf.org/html/rfc2368] # - RFC2373[http://tools.ietf.org/html/rfc2373] # - RFC2396[http://tools.ietf.org/html/rfc2396] # - RFC2732[http://tools.ietf.org/html/rfc2732] # - RFC3986[http://tools.ietf.org/html/rfc3986] # # == Class tree # # - Gem::URI::Generic (in uri/generic.rb) # - Gem::URI::File - (in uri/file.rb) # - Gem::URI::FTP - (in uri/ftp.rb) # - Gem::URI::HTTP - (in uri/http.rb) # - Gem::URI::HTTPS - (in uri/https.rb) # - Gem::URI::LDAP - (in uri/ldap.rb) # - Gem::URI::LDAPS - (in uri/ldaps.rb) # - Gem::URI::MailTo - (in uri/mailto.rb) # - Gem::URI::Parser - (in uri/common.rb) # - Gem::URI::REGEXP - (in uri/common.rb) # - Gem::URI::REGEXP::PATTERN - (in uri/common.rb) # - Gem::URI::Util - (in uri/common.rb) # - Gem::URI::Error - (in uri/common.rb) # - Gem::URI::InvalidURIError - (in uri/common.rb) # - Gem::URI::InvalidComponentError - (in uri/common.rb) # - Gem::URI::BadURIError - (in uri/common.rb) # # == Copyright Info # # Author:: Akira Yamada # Documentation:: # Akira Yamada # Dmitry V. Sabanin # Vincent Batts # License:: # Copyright (c) 2001 akira yamada # You can redistribute it and/or modify it under the same term as Ruby. # module Gem::URI end require_relative 'uri/version' require_relative 'uri/common' require_relative 'uri/generic' require_relative 'uri/file' require_relative 'uri/ftp' require_relative 'uri/http' require_relative 'uri/https' require_relative 'uri/ldap' require_relative 'uri/ldaps' require_relative 'uri/mailto' require_relative 'uri/ws' require_relative 'uri/wss' PK!)vn&rubygems/vendor/uri/lib/uri/version.rbnu[module Gem::URI # :stopdoc: VERSION_CODE = '001303'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end PK! BB$rubygems/vendor/uri/lib/uri/https.rbnu[# frozen_string_literal: false # = uri/https.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'http' module Gem::URI # The default port for HTTPS URIs is 443, and the scheme is 'https:' rather # than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs; # see Gem::URI::HTTP. class HTTPS < HTTP # A Default port of 443 for Gem::URI::HTTPS DEFAULT_PORT = 443 end register_scheme 'HTTPS', HTTPS end PK!ްlbEE-rubygems/vendor/uri/lib/uri/rfc2396_parser.rbnu[# frozen_string_literal: false #-- # = uri/common.rb # # Author:: Akira Yamada # License:: # You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # module Gem::URI # # Includes Gem::URI::REGEXP::PATTERN # module RFC2396_REGEXP # # Patterns used to parse Gem::URI's # module PATTERN # :stopdoc: # RFC 2396 (Gem::URI Generic Syntax) # RFC 2732 (IPv6 Literal Addresses in URL's) # RFC 2373 (IPv6 Addressing Architecture) # alpha = lowalpha | upalpha ALPHA = "a-zA-Z" # alphanum = alpha | digit ALNUM = "#{ALPHA}\\d" # hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | # "a" | "b" | "c" | "d" | "e" | "f" HEX = "a-fA-F\\d" # escaped = "%" hex hex ESCAPED = "%[#{HEX}]{2}" # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | # "(" | ")" # unreserved = alphanum | mark UNRESERVED = "\\-_.!~*'()#{ALNUM}" # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | # "$" | "," # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | # "$" | "," | "[" | "]" (RFC 2732) RESERVED = ";/?:@&=+$,\\[\\]" # domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)" # toplabel = alpha | alpha *( alphanum | "-" ) alphanum TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)" # hostname = *( domainlabel "." ) toplabel [ "." ] HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?" # :startdoc: end # PATTERN # :startdoc: end # REGEXP # Class that parses String's into Gem::URI's. # # It contains a Hash set of patterns and Regexp's that match and validate. # class RFC2396_Parser include RFC2396_REGEXP # # == Synopsis # # Gem::URI::Parser.new([opts]) # # == Args # # The constructor accepts a hash as options for parser. # Keys of options are pattern names of Gem::URI components # and values of options are pattern strings. # The constructor generates set of regexps for parsing URIs. # # You can use the following keys: # # * :ESCAPED (Gem::URI::PATTERN::ESCAPED in default) # * :UNRESERVED (Gem::URI::PATTERN::UNRESERVED in default) # * :DOMLABEL (Gem::URI::PATTERN::DOMLABEL in default) # * :TOPLABEL (Gem::URI::PATTERN::TOPLABEL in default) # * :HOSTNAME (Gem::URI::PATTERN::HOSTNAME in default) # # == Examples # # p = Gem::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> # # Gem::URI.parse(u.to_s) #=> raises Gem::URI::InvalidURIError # # s = "http://example.com/ABCD" # u1 = p.parse(s) #=> # # u2 = Gem::URI.parse(s) #=> # # u1 == u2 #=> true # u1.eql?(u2) #=> false # def initialize(opts = {}) @pattern = initialize_pattern(opts) @pattern.each_value(&:freeze) @pattern.freeze @regexp = initialize_regexp(@pattern) @regexp.each_value(&:freeze) @regexp.freeze end # The Hash of patterns. # # See also Gem::URI::Parser.initialize_pattern. attr_reader :pattern # The Hash of Regexp. # # See also Gem::URI::Parser.initialize_regexp. attr_reader :regexp # Returns a split Gem::URI against +regexp[:ABS_URI]+. def split(uri) case uri when '' # null uri when @regexp[:ABS_URI] scheme, opaque, userinfo, host, port, registry, path, query, fragment = $~[1..-1] # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] # absoluteURI = scheme ":" ( hier_part | opaque_part ) # hier_part = ( net_path | abs_path ) [ "?" query ] # opaque_part = uric_no_slash *uric # abs_path = "/" path_segments # net_path = "//" authority [ abs_path ] # authority = server | reg_name # server = [ [ userinfo "@" ] hostport ] if !scheme raise InvalidURIError, "bad Gem::URI(absolute but no scheme): #{uri}" end if !opaque && (!path && (!host && !registry)) raise InvalidURIError, "bad Gem::URI(absolute but no path): #{uri}" end when @regexp[:REL_URI] scheme = nil opaque = nil userinfo, host, port, registry, rel_segment, abs_path, query, fragment = $~[1..-1] if rel_segment && abs_path path = rel_segment + abs_path elsif rel_segment path = rel_segment elsif abs_path path = abs_path end # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] # net_path = "//" authority [ abs_path ] # abs_path = "/" path_segments # rel_path = rel_segment [ abs_path ] # authority = server | reg_name # server = [ [ userinfo "@" ] hostport ] else raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri}" end path = '' if !path && !opaque # (see RFC2396 Section 5.2) ret = [ scheme, userinfo, host, port, # X registry, # X path, # Y opaque, # Y query, fragment ] return ret end # # == Args # # +uri+:: # String # # == Description # # Parses +uri+ and constructs either matching Gem::URI scheme object # (File, FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or Gem::URI::Generic. # # == Usage # # p = Gem::URI::Parser.new # p.parse("ldap://ldap.example.com/dc=example?user=john") # #=> # # def parse(uri) Gem::URI.for(*self.split(uri), self) end # # == Args # # +uris+:: # an Array of Strings # # == Description # # Attempts to parse and merge a set of URIs. # def join(*uris) uris[0] = convert_to_uri(uris[0]) uris.inject :merge end # # :call-seq: # extract( str ) # extract( str, schemes ) # extract( str, schemes ) {|item| block } # # == Args # # +str+:: # String to search # +schemes+:: # Patterns to apply to +str+ # # == Description # # Attempts to parse and merge a set of URIs. # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # # See also Gem::URI::Parser.make_regexp. # def extract(str, schemes = nil) if block_given? str.scan(make_regexp(schemes)) { yield $& } nil else result = [] str.scan(make_regexp(schemes)) { result.push $& } result end end # Returns Regexp that is default +self.regexp[:ABS_URI_REF]+, # unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+. def make_regexp(schemes = nil) unless schemes @regexp[:ABS_URI_REF] else /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x end end # # :call-seq: # escape( str ) # escape( str, unsafe ) # # == Args # # +str+:: # String to make safe # +unsafe+:: # Regexp to apply. Defaults to +self.regexp[:UNSAFE]+ # # == Description # # Constructs a safe String from +str+, removing unsafe characters, # replacing them with codes. # def escape(str, unsafe = @regexp[:UNSAFE]) unless unsafe.kind_of?(Regexp) # perhaps unsafe is String object unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false) end str.gsub(unsafe) do us = $& tmp = '' us.each_byte do |uc| tmp << sprintf('%%%02X', uc) end tmp end.force_encoding(Encoding::US_ASCII) end # # :call-seq: # unescape( str ) # unescape( str, escaped ) # # == Args # # +str+:: # String to remove escapes from # +escaped+:: # Regexp to apply. Defaults to +self.regexp[:ESCAPED]+ # # == Description # # Removes escapes from +str+. # def unescape(str, escaped = @regexp[:ESCAPED]) enc = str.encoding enc = Encoding::UTF_8 if enc == Encoding::US_ASCII str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } end @@to_s = Kernel.instance_method(:to_s) if @@to_s.respond_to?(:bind_call) def inspect @@to_s.bind_call(self) end else def inspect @@to_s.bind(self).call end end private # Constructs the default Hash of patterns. def initialize_pattern(opts = {}) ret = {} ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED) ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME) # RFC 2396 (Gem::URI Generic Syntax) # RFC 2732 (IPv6 Literal Addresses in URL's) # RFC 2373 (IPv6 Addressing Architecture) # uric = reserved | unreserved | escaped ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})" # uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | # "&" | "=" | "+" | "$" | "," ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})" # query = *uric ret[:QUERY] = query = "#{uric}*" # fragment = *uric ret[:FRAGMENT] = fragment = "#{uric}*" # hostname = *( domainlabel "." ) toplabel [ "." ] # reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986 unless hostname ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+" end # RFC 2373, APPENDIX B: # IPv6address = hexpart [ ":" IPv4address ] # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT # hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] # hexseq = hex4 *( ":" hex4) # hex4 = 1*4HEXDIG # # XXX: This definition has a flaw. "::" + IPv4address must be # allowed too. Here is a replacement. # # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" # hex4 = 1*4HEXDIG hex4 = "[#{PATTERN::HEX}]{1,4}" # lastpart = hex4 | IPv4address lastpart = "(?:#{hex4}|#{ipv4addr})" # hexseq1 = *( hex4 ":" ) hex4 hexseq1 = "(?:#{hex4}:)*#{hex4}" # hexseq2 = *( hex4 ":" ) lastpart hexseq2 = "(?:#{hex4}:)*#{lastpart}" # IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ] ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)" # IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT # unused # ipv6reference = "[" IPv6address "]" (RFC 2732) ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]" # host = hostname | IPv4address # host = hostname | IPv4address | IPv6reference (RFC 2732) ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})" # port = *digit ret[:PORT] = port = '\d*' # hostport = host [ ":" port ] ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?" # userinfo = *( unreserved | escaped | # ";" | ":" | "&" | "=" | "+" | "$" | "," ) ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*" # pchar = unreserved | escaped | # ":" | "@" | "&" | "=" | "+" | "$" | "," pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})" # param = *pchar param = "#{pchar}*" # segment = *pchar *( ";" param ) segment = "#{pchar}*(?:;#{param})*" # path_segments = segment *( "/" segment ) ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*" # server = [ [ userinfo "@" ] hostport ] server = "(?:#{userinfo}@)?#{hostport}" # reg_name = 1*( unreserved | escaped | "$" | "," | # ";" | ":" | "@" | "&" | "=" | "+" ) ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+" # authority = server | reg_name authority = "(?:#{server}|#{reg_name})" # rel_segment = 1*( unreserved | escaped | # ";" | "@" | "&" | "=" | "+" | "$" | "," ) ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+" # scheme = alpha *( alpha | digit | "+" | "-" | "." ) ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*" # abs_path = "/" path_segments ret[:ABS_PATH] = abs_path = "/#{path_segments}" # rel_path = rel_segment [ abs_path ] ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?" # net_path = "//" authority [ abs_path ] ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?" # hier_part = ( net_path | abs_path ) [ "?" query ] ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?" # opaque_part = uric_no_slash *uric ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*" # absoluteURI = scheme ":" ( hier_part | opaque_part ) ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})" # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?" # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?" ret[:X_ABS_URI] = " (#{scheme}): (?# 1: scheme) (?: (#{opaque_part}) (?# 2: opaque) | (?:(?: //(?: (?:(?:(#{userinfo})@)? (?# 3: userinfo) (?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port) | (#{reg_name}) (?# 6: registry) ) | (?!//)) (?# XXX: '//' is the mark for hostport) (#{abs_path})? (?# 7: path) )(?:\\?(#{query}))? (?# 8: query) ) (?:\\#(#{fragment}))? (?# 9: fragment) " ret[:X_REL_URI] = " (?: (?: // (?: (?:(#{userinfo})@)? (?# 1: userinfo) (#{host})?(?::(\\d*))? (?# 2: host, 3: port) | (#{reg_name}) (?# 4: registry) ) ) | (#{rel_segment}) (?# 5: rel_segment) )? (#{abs_path})? (?# 6: abs_path) (?:\\?(#{query}))? (?# 7: query) (?:\\#(#{fragment}))? (?# 8: fragment) " ret end # Constructs the default Hash of Regexp's. def initialize_regexp(pattern) ret = {} # for Gem::URI::split ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) # for Gem::URI::extract ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED) ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED) # for Gem::URI::escape/unescape ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED]) ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]") # for Generic#initialize ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z") ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z") ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z") ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z") ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z") ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z") ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z") ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z") ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z") ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z") ret end def convert_to_uri(uri) if uri.is_a?(Gem::URI::Generic) uri elsif uri = String.try_convert(uri) parse(uri) else raise ArgumentError, "bad argument (expected Gem::URI object or Gem::URI string)" end end end # class Parser end # module Gem::URI PK!_$rubygems/vendor/uri/lib/uri/ldaps.rbnu[# frozen_string_literal: false # = uri/ldap.rb # # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'ldap' module Gem::URI # The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather # than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs; # see Gem::URI::LDAP. class LDAPS < LDAP # A Default port of 636 for Gem::URI::LDAPS DEFAULT_PORT = 636 end register_scheme 'LDAPS', LDAPS end PK! e-rubygems/vendor/uri/lib/uri/rfc3986_parser.rbnu[# frozen_string_literal: true module Gem::URI class RFC3986_Parser # :nodoc: # Gem::URI defined in RFC3986 HOST = %r[ (?\[(?: (? (?:\h{1,4}:){6} (?\h{1,4}:\h{1,4} | (?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d) \.\g\.\g\.\g) ) | ::(?:\h{1,4}:){5}\g | \h{1,4}?::(?:\h{1,4}:){4}\g | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4} | (?:(?:\h{1,4}:){,6}\h{1,4})?:: ) | (?v\h++\.[!$&-.0-9:;=A-Z_a-z~]++) )\]) | \g | (?(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+) ]x USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/ SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source RFC3986_URI = %r[\A (?#{SEG}){0} (? (?#{SCHEME}): (?// (? (?:(?#{USERINFO.source})@)? (?#{HOST.source.delete(" \n")}) (?::(?\d*+))? ) (?(?:/\g*+)?) | (?/((?!/)\g++)?) | (?(?!/)\g++) | (?) ) (?:\?(?[^\#]*+))? (?:\#(?#{FRAGMENT}))? )\z]x RFC3986_relative_ref = %r[\A (?#{SEG}){0} (? (?// (? (?:(?#{USERINFO.source})@)? (?#{HOST.source.delete(" \n")}(?\d*+))? ) (?(?:/\g*+)?) | (?/\g*+) | (?#{SEG_NC}++(?:/\g*+)?) | (?) ) (?:\?(?[^#]*+))? (?:\#(?#{FRAGMENT}))? )\z]x attr_reader :regexp def initialize @regexp = default_regexp.each_value(&:freeze).freeze end def split(uri) #:nodoc: begin uri = uri.to_str rescue NoMethodError raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri.inspect}" end uri.ascii_only? or raise InvalidURIError, "Gem::URI must be ascii only #{uri.dump}" if m = RFC3986_URI.match(uri) query = m["query"] scheme = m["scheme"] opaque = m["path-rootless"] if opaque opaque << "?#{query}" if query [ scheme, nil, # userinfo nil, # host nil, # port nil, # registry nil, # path opaque, nil, # query m["fragment"] ] else # normal [ scheme, m["userinfo"], m["host"], m["port"], nil, # registry (m["path-abempty"] || m["path-absolute"] || m["path-empty"]), nil, # opaque query, m["fragment"] ] end elsif m = RFC3986_relative_ref.match(uri) [ nil, # scheme m["userinfo"], m["host"], m["port"], nil, # registry, (m["path-abempty"] || m["path-absolute"] || m["path-noscheme"] || m["path-empty"]), nil, # opaque m["query"], m["fragment"] ] else raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri.inspect}" end end def parse(uri) # :nodoc: Gem::URI.for(*self.split(uri), self) end def join(*uris) # :nodoc: uris[0] = convert_to_uri(uris[0]) uris.inject :merge end @@to_s = Kernel.instance_method(:to_s) if @@to_s.respond_to?(:bind_call) def inspect @@to_s.bind_call(self) end else def inspect @@to_s.bind(self).call end end private def default_regexp # :nodoc: { SCHEME: %r[\A#{SCHEME}\z]o, USERINFO: %r[\A#{USERINFO}\z]o, HOST: %r[\A#{HOST}\z]o, ABS_PATH: %r[\A/#{SEG}*+\z]o, REL_PATH: %r[\A(?!/)#{SEG}++\z]o, QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z], FRAGMENT: %r[\A#{FRAGMENT}\z]o, OPAQUE: %r[\A(?:[^/].*)?\z], PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, } end def convert_to_uri(uri) if uri.is_a?(Gem::URI::Generic) uri elsif uri = String.try_convert(uri) parse(uri) else raise ArgumentError, "bad argument (expected Gem::URI object or Gem::URI string)" end end end # class Parser end # module Gem::URI PK! %rubygems/vendor/uri/lib/uri/mailto.rbnu[# frozen_string_literal: false # = uri/mailto.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # RFC6068, the mailto URL scheme. # class MailTo < Generic include RFC2396_REGEXP # A Default port of nil for Gem::URI::MailTo. DEFAULT_PORT = nil # An Array of the available components for Gem::URI::MailTo. COMPONENT = [ :scheme, :to, :headers ].freeze # :stopdoc: # "hname" and "hvalue" are encodings of an RFC 822 header name and # value, respectively. As with "to", all URL reserved characters must # be encoded. # # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it # consists of zero or more comma-separated mail addresses, possibly # including "phrase" and "comment" components. Note that all URL # reserved characters in "to" must be encoded: in particular, # parentheses, commas, and the percent sign ("%"), which commonly occur # in the "mailbox" syntax. # # Within mailto URLs, the characters "?", "=", "&" are reserved. # ; RFC 6068 # hfields = "?" hfield *( "&" hfield ) # hfield = hfname "=" hfvalue # hfname = *qchar # hfvalue = *qchar # qchar = unreserved / pct-encoded / some-delims # some-delims = "!" / "$" / "'" / "(" / ")" / "*" # / "+" / "," / ";" / ":" / "@" # # ; RFC3986 # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" # pct-encoded = "%" HEXDIG HEXDIG HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/ # practical regexp for email address # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/ # :startdoc: # # == Description # # Creates a new Gem::URI::MailTo object from components, with syntax checking. # # Components can be provided as an Array or Hash. If an Array is used, # the components must be supplied as [to, headers]. # # If a Hash is used, the keys are the component names preceded by colons. # # The headers can be supplied as a pre-encoded string, such as # "subject=subscribe&cc=address", or as an Array of Arrays # like [['subject', 'subscribe'], ['cc', 'address']]. # # Examples: # # require 'rubygems/vendor/uri/lib/uri' # # m1 = Gem::URI::MailTo.build(['joe@example.com', 'subject=Ruby']) # m1.to_s # => "mailto:joe@example.com?subject=Ruby" # # m2 = Gem::URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]]) # m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com" # # m3 = Gem::URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]}) # m3.to_s # => "mailto:listman@example.com?subject=subscribe" # def self.build(args) tmp = Util.make_components_hash(self, args) case tmp[:to] when Array tmp[:opaque] = tmp[:to].join(',') when String tmp[:opaque] = tmp[:to].dup else tmp[:opaque] = '' end if tmp[:headers] query = case tmp[:headers] when Array tmp[:headers].collect { |x| if x.kind_of?(Array) x[0] + '=' + x[1..-1].join else x.to_s end }.join('&') when Hash tmp[:headers].collect { |h,v| h + '=' + v }.join('&') else tmp[:headers].to_s end unless query.empty? tmp[:opaque] << '?' << query end end super(tmp) end # # == Description # # Creates a new Gem::URI::MailTo object from generic URL components with # no syntax checking. # # This method is usually called from Gem::URI::parse, which checks # the validity of each component. # def initialize(*arg) super(*arg) @to = nil @headers = [] # The RFC3986 parser does not normally populate opaque @opaque = "?#{@query}" if @query && !@opaque unless @opaque raise InvalidComponentError, "missing opaque part for mailto URL" end to, header = @opaque.split('?', 2) # allow semicolon as a addr-spec separator # http://support.microsoft.com/kb/820868 unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to raise InvalidComponentError, "unrecognised opaque part for mailtoURL: #{@opaque}" end if arg[10] # arg_check self.to = to self.headers = header else set_to(to) set_headers(header) end end # The primary e-mail address of the URL, as a String. attr_reader :to # E-mail headers set by the URL, as an Array of Arrays. attr_reader :headers # Checks the to +v+ component. def check_to(v) return true unless v return true if v.size == 0 v.split(/[,;]/).each do |addr| # check url safety as path-rootless if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr raise InvalidComponentError, "an address in 'to' is invalid as Gem::URI #{addr.dump}" end # check addr-spec # don't s/\+/ /g addr.gsub!(/%\h\h/, Gem::URI::TBLDECWWWCOMP_) if EMAIL_REGEXP !~ addr raise InvalidComponentError, "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}" end end true end private :check_to # Private setter for to +v+. def set_to(v) @to = v end protected :set_to # Setter for to +v+. def to=(v) check_to(v) set_to(v) v end # Checks the headers +v+ component against either # * HEADER_REGEXP def check_headers(v) return true unless v return true if v.size == 0 if HEADER_REGEXP !~ v raise InvalidComponentError, "bad component(expected opaque component): #{v}" end true end private :check_headers # Private setter for headers +v+. def set_headers(v) @headers = [] if v v.split('&').each do |x| @headers << x.split(/=/, 2) end end end protected :set_headers # Setter for headers +v+. def headers=(v) check_headers(v) set_headers(v) v end # Constructs String from Gem::URI. def to_s @scheme + ':' + if @to @to else '' end + if @headers.size > 0 '?' + @headers.collect{|x| x.join('=')}.join('&') else '' end + if @fragment '#' + @fragment else '' end end # Returns the RFC822 e-mail text equivalent of the URL, as a String. # # Example: # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr") # uri.to_mailtext # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n" # def to_mailtext to = Gem::URI.decode_www_form_component(@to) head = '' body = '' @headers.each do |x| case x[0] when 'body' body = Gem::URI.decode_www_form_component(x[1]) when 'to' to << ', ' + Gem::URI.decode_www_form_component(x[1]) else head << Gem::URI.decode_www_form_component(x[0]).capitalize + ': ' + Gem::URI.decode_www_form_component(x[1]) + "\n" end end "To: #{to} #{head} #{body} " end alias to_rfc822text to_mailtext end register_scheme 'MAILTO', MailTo end PK!KT#rubygems/vendor/uri/lib/uri/http.rbnu[# frozen_string_literal: false # = uri/http.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # The syntax of HTTP URIs is defined in RFC1738 section 3.3. # # Note that the Ruby Gem::URI library allows HTTP URLs containing usernames and # passwords. This is not legal as per the RFC, but used to be # supported in Internet Explorer 5 and 6, before the MS04-004 security # update. See . # class HTTP < Generic # A Default port of 80 for Gem::URI::HTTP. DEFAULT_PORT = 80 # An Array of the available components for Gem::URI::HTTP. COMPONENT = %i[ scheme userinfo host port path query fragment ].freeze # # == Description # # Creates a new Gem::URI::HTTP object from components, with syntax checking. # # The components accepted are userinfo, host, port, path, query, and # fragment. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [userinfo, host, port, path, query, fragment]. # # Example: # # uri = Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar') # # uri = Gem::URI::HTTP.build([nil, "www.example.com", nil, "/path", # "query", 'fragment']) # # Currently, if passed userinfo components this method generates # invalid HTTP URIs as per RFC 1738. # def self.build(args) tmp = Util.make_components_hash(self, args) super(tmp) end # # == Description # # Returns the full path for an HTTP request, as required by Net::HTTP::Get. # # If the Gem::URI contains a query, the full path is Gem::URI#path + '?' + Gem::URI#query. # Otherwise, the path is simply Gem::URI#path. # # Example: # # uri = Gem::URI::HTTP.build(path: '/foo/bar', query: 'test=true') # uri.request_uri # => "/foo/bar?test=true" # def request_uri return unless @path url = @query ? "#@path?#@query" : @path.dup url.start_with?(?/.freeze) ? url : ?/ + url end # # == Description # # Returns the authority for an HTTP uri, as defined in # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2. # # # Example: # # Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com" # Gem::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000" # Gem::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com" # def authority if port == default_port host else "#{host}:#{port}" end end # # == Description # # Returns the origin for an HTTP uri, as defined in # https://datatracker.ietf.org/doc/html/rfc6454. # # # Example: # # Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com" # Gem::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000" # Gem::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com" # Gem::URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com" # def origin "#{scheme}://#{authority}" end end register_scheme 'HTTP', HTTP end PK!R""rubygems/vendor/uri/lib/uri/ftp.rbnu[# frozen_string_literal: false # = uri/ftp.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # FTP Gem::URI syntax is defined by RFC1738 section 3.2. # # This class will be redesigned because of difference of implementations; # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it # is a good summary about the de facto spec. # http://tools.ietf.org/html/draft-hoffman-ftp-uri-04 # class FTP < Generic # A Default port of 21 for Gem::URI::FTP. DEFAULT_PORT = 21 # # An Array of the available components for Gem::URI::FTP. # COMPONENT = [ :scheme, :userinfo, :host, :port, :path, :typecode ].freeze # # Typecode is "a", "i", or "d". # # * "a" indicates a text file (the FTP command was ASCII) # * "i" indicates a binary file (FTP command IMAGE) # * "d" indicates the contents of a directory should be displayed # TYPECODE = ['a', 'i', 'd'].freeze # Typecode prefix ";type=". TYPECODE_PREFIX = ';type='.freeze def self.new2(user, password, host, port, path, typecode = nil, arg_check = true) # :nodoc: # Do not use this method! Not tested. [Bug #7301] # This methods remains just for compatibility, # Keep it undocumented until the active maintainer is assigned. typecode = nil if typecode.size == 0 if typecode && !TYPECODE.include?(typecode) raise ArgumentError, "bad typecode is specified: #{typecode}" end # do escape self.new('ftp', [user, password], host, port, nil, typecode ? path + TYPECODE_PREFIX + typecode : path, nil, nil, nil, arg_check) end # # == Description # # Creates a new Gem::URI::FTP object from components, with syntax checking. # # The components accepted are +userinfo+, +host+, +port+, +path+, and # +typecode+. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [userinfo, host, port, path, typecode]. # # If the path supplied is absolute, it will be escaped in order to # make it absolute in the Gem::URI. # # Examples: # # require 'rubygems/vendor/uri/lib/uri' # # uri1 = Gem::URI::FTP.build(['user:password', 'ftp.example.com', nil, # '/path/file.zip', 'i']) # uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i" # # uri2 = Gem::URI::FTP.build({:host => 'ftp.example.com', # :path => 'ruby/src'}) # uri2.to_s # => "ftp://ftp.example.com/ruby/src" # def self.build(args) # Fix the incoming path to be generic URL syntax # FTP path -> URL path # foo/bar /foo/bar # /foo/bar /%2Ffoo/bar # if args.kind_of?(Array) args[3] = '/' + args[3].sub(/^\//, '%2F') else args[:path] = '/' + args[:path].sub(/^\//, '%2F') end tmp = Util::make_components_hash(self, args) if tmp[:typecode] if tmp[:typecode].size == 1 tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode] end tmp[:path] << tmp[:typecode] end return super(tmp) end # # == Description # # Creates a new Gem::URI::FTP object from generic URL components with no # syntax checking. # # Unlike build(), this method does not escape the path component as # required by RFC1738; instead it is treated as per RFC2396. # # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, # +opaque+, +query+, and +fragment+, in that order. # def initialize(scheme, userinfo, host, port, registry, path, opaque, query, fragment, parser = nil, arg_check = false) raise InvalidURIError unless path path = path.sub(/^\//,'') path.sub!(/^%2F/,'/') super(scheme, userinfo, host, port, registry, path, opaque, query, fragment, parser, arg_check) @typecode = nil if tmp = @path.index(TYPECODE_PREFIX) typecode = @path[tmp + TYPECODE_PREFIX.size..-1] @path = @path[0..tmp - 1] if arg_check self.typecode = typecode else self.set_typecode(typecode) end end end # typecode accessor. # # See Gem::URI::FTP::COMPONENT. attr_reader :typecode # Validates typecode +v+, # returns +true+ or +false+. # def check_typecode(v) if TYPECODE.include?(v) return true else raise InvalidComponentError, "bad typecode(expected #{TYPECODE.join(', ')}): #{v}" end end private :check_typecode # Private setter for the typecode +v+. # # See also Gem::URI::FTP.typecode=. # def set_typecode(v) @typecode = v end protected :set_typecode # # == Args # # +v+:: # String # # == Description # # Public setter for the typecode +v+ # (with validation). # # See also Gem::URI::FTP.check_typecode. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("ftp://john@ftp.example.com/my_file.img") # #=> # # uri.typecode = "i" # uri # #=> # # def typecode=(typecode) check_typecode(typecode) set_typecode(typecode) typecode end def merge(oth) # :nodoc: tmp = super(oth) if self != tmp tmp.set_typecode(oth.typecode) end return tmp end # Returns the path from an FTP Gem::URI. # # RFC 1738 specifically states that the path for an FTP Gem::URI does not # include the / which separates the Gem::URI path from the Gem::URI host. Example: # # ftp://ftp.example.com/pub/ruby # # The above Gem::URI indicates that the client should connect to # ftp.example.com then cd to pub/ruby from the initial login directory. # # If you want to cd to an absolute directory, you must include an # escaped / (%2F) in the path. Example: # # ftp://ftp.example.com/%2Fpub/ruby # # This method will then return "/pub/ruby". # def path return @path.sub(/^\//,'').sub(/^%2F/,'/') end # Private setter for the path of the Gem::URI::FTP. def set_path(v) super("/" + v.sub(/^\//, "%2F")) end protected :set_path # Returns a String representation of the Gem::URI::FTP. def to_s save_path = nil if @typecode save_path = @path @path = @path + TYPECODE_PREFIX + @typecode end str = super if @typecode @path = save_path end return str end end register_scheme 'FTP', FTP end PK!r ; ; !rubygems/vendor/uri/lib/uri/ws.rbnu[# frozen_string_literal: false # = uri/ws.rb # # Author:: Matt Muller # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # The syntax of WS URIs is defined in RFC6455 section 3. # # Note that the Ruby Gem::URI library allows WS URLs containing usernames and # passwords. This is not legal as per the RFC, but used to be # supported in Internet Explorer 5 and 6, before the MS04-004 security # update. See . # class WS < Generic # A Default port of 80 for Gem::URI::WS. DEFAULT_PORT = 80 # An Array of the available components for Gem::URI::WS. COMPONENT = %i[ scheme userinfo host port path query ].freeze # # == Description # # Creates a new Gem::URI::WS object from components, with syntax checking. # # The components accepted are userinfo, host, port, path, and query. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [userinfo, host, port, path, query]. # # Example: # # uri = Gem::URI::WS.build(host: 'www.example.com', path: '/foo/bar') # # uri = Gem::URI::WS.build([nil, "www.example.com", nil, "/path", "query"]) # # Currently, if passed userinfo components this method generates # invalid WS URIs as per RFC 1738. # def self.build(args) tmp = Util.make_components_hash(self, args) super(tmp) end # # == Description # # Returns the full path for a WS Gem::URI, as required by Net::HTTP::Get. # # If the Gem::URI contains a query, the full path is Gem::URI#path + '?' + Gem::URI#query. # Otherwise, the path is simply Gem::URI#path. # # Example: # # uri = Gem::URI::WS.build(path: '/foo/bar', query: 'test=true') # uri.request_uri # => "/foo/bar?test=true" # def request_uri return unless @path url = @query ? "#@path?#@query" : @path.dup url.start_with?(?/.freeze) ? url : ?/ + url end end register_scheme 'WS', WS end PK!p;vgg%rubygems/vendor/uri/lib/uri/common.rbnu[# frozen_string_literal: true #-- # = uri/common.rb # # Author:: Akira Yamada # License:: # You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative "rfc2396_parser" require_relative "rfc3986_parser" module Gem::URI include RFC2396_REGEXP REGEXP = RFC2396_REGEXP Parser = RFC2396_Parser RFC3986_PARSER = RFC3986_Parser.new Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) RFC2396_PARSER = RFC2396_Parser.new Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) # Gem::URI::Parser.new DEFAULT_PARSER = Parser.new DEFAULT_PARSER.pattern.each_pair do |sym, str| unless REGEXP::PATTERN.const_defined?(sym) REGEXP::PATTERN.const_set(sym, str) end end DEFAULT_PARSER.regexp.each_pair do |sym, str| const_set(sym, str) end Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) module Util # :nodoc: def make_components_hash(klass, array_hash) tmp = {} if array_hash.kind_of?(Array) && array_hash.size == klass.component.size - 1 klass.component[1..-1].each_index do |i| begin tmp[klass.component[i + 1]] = array_hash[i].clone rescue TypeError tmp[klass.component[i + 1]] = array_hash[i] end end elsif array_hash.kind_of?(Hash) array_hash.each do |key, value| begin tmp[key] = value.clone rescue TypeError tmp[key] = value end end else raise ArgumentError, "expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})" end tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase return tmp end module_function :make_components_hash end module Schemes end private_constant :Schemes # Registers the given +klass+ as the class to be instantiated # when parsing a \Gem::URI with the given +scheme+: # # Gem::URI.register_scheme('MS_SEARCH', Gem::URI::Generic) # => Gem::URI::Generic # Gem::URI.scheme_list['MS_SEARCH'] # => Gem::URI::Generic # # Note that after calling String#upcase on +scheme+, it must be a valid # constant name. def self.register_scheme(scheme, klass) Schemes.const_set(scheme.to_s.upcase, klass) end # Returns a hash of the defined schemes: # # Gem::URI.scheme_list # # => # {"MAILTO"=>Gem::URI::MailTo, # "LDAPS"=>Gem::URI::LDAPS, # "WS"=>Gem::URI::WS, # "HTTP"=>Gem::URI::HTTP, # "HTTPS"=>Gem::URI::HTTPS, # "LDAP"=>Gem::URI::LDAP, # "FILE"=>Gem::URI::File, # "FTP"=>Gem::URI::FTP} # # Related: Gem::URI.register_scheme. def self.scheme_list Schemes.constants.map { |name| [name.to_s.upcase, Schemes.const_get(name)] }.to_h end INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: # # - The new object is an instance of Gem::URI.scheme_list[scheme.upcase]. # - The object is initialized by calling the class initializer # using +scheme+ and +arguments+. # See Gem::URI::Generic.new. # # Examples: # # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top'] # Gem::URI.for('https', *values) # # => # # Gem::URI.for('foo', *values, default: Gem::URI::HTTP) # # => # # def self.for(scheme, *arguments, default: Generic) const_name = scheme.to_s.upcase uri_class = INITIAL_SCHEMES[const_name] uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false) Schemes.const_get(const_name, false) end uri_class ||= default return uri_class.new(scheme, *arguments) end # # Base class for all Gem::URI exceptions. # class Error < StandardError; end # # Not a Gem::URI. # class InvalidURIError < Error; end # # Not a Gem::URI component. # class InvalidComponentError < Error; end # # Gem::URI is valid, bad usage is not. # class BadURIError < Error; end # Returns a 9-element array representing the parts of the \Gem::URI # formed from the string +uri+; # each array element is a string or +nil+: # # names = %w[scheme userinfo host port registry path opaque query fragment] # values = Gem::URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # names.zip(values) # # => # [["scheme", "https"], # ["userinfo", "john.doe"], # ["host", "www.example.com"], # ["port", "123"], # ["registry", nil], # ["path", "/forum/questions/"], # ["opaque", nil], # ["query", "tag=networking&order=newest"], # ["fragment", "top"]] # def self.split(uri) RFC3986_PARSER.split(uri) end # Returns a new \Gem::URI object constructed from the given string +uri+: # # Gem::URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => # # Gem::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => # # # It's recommended to first ::escape string +uri+ # if it may contain invalid Gem::URI characters. # def self.parse(uri) RFC3986_PARSER.parse(uri) end # Merges the given Gem::URI strings +str+ # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html]. # # Each string in +str+ is converted to an # {RFC3986 Gem::URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged. # # Examples: # # Gem::URI.join("http://example.com/","main.rbx") # # => # # # Gem::URI.join('http://example.com', 'foo') # # => # # # Gem::URI.join('http://example.com', '/foo', '/bar') # # => # # # Gem::URI.join('http://example.com', '/foo', 'bar') # # => # # # Gem::URI.join('http://example.com', '/foo/', 'bar') # # => # # def self.join(*str) RFC3986_PARSER.join(*str) end # # == Synopsis # # Gem::URI::extract(str[, schemes][,&blk]) # # == Args # # +str+:: # String to extract URIs from. # +schemes+:: # Limit Gem::URI matching to specific schemes. # # == Description # # Extracts URIs from a string. If block given, iterates through all matched URIs. # Returns nil if block given or array with matches. # # == Usage # # require "rubygems/vendor/uri/lib/uri" # # Gem::URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") # # => ["http://foo.example.com/bla", "mailto:test@example.com"] # def self.extract(str, schemes = nil, &block) # :nodoc: warn "Gem::URI.extract is obsolete", uplevel: 1 if $VERBOSE DEFAULT_PARSER.extract(str, schemes, &block) end # # == Synopsis # # Gem::URI::regexp([match_schemes]) # # == Args # # +match_schemes+:: # Array of schemes. If given, resulting regexp matches to URIs # whose scheme is one of the match_schemes. # # == Description # # Returns a Regexp object which matches to Gem::URI-like strings. # The Regexp object returned by this method includes arbitrary # number of capture group (parentheses). Never rely on its number. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # # extract first Gem::URI from html_string # html_string.slice(Gem::URI.regexp) # # # remove ftp URIs # html_string.sub(Gem::URI.regexp(['ftp']), '') # # # You should not rely on the number of parentheses # html_string.scan(Gem::URI.regexp) do |*matches| # p $& # end # def self.regexp(schemes = nil)# :nodoc: warn "Gem::URI.regexp is obsolete", uplevel: 1 if $VERBOSE DEFAULT_PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: 256.times do |i| TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) end TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze TBLDECWWWCOMP_ = {} # :nodoc: 256.times do |i| h, l = i>>4, i&15 TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr end TBLDECWWWCOMP_['+'] = ' ' TBLDECWWWCOMP_.freeze # Returns a URL-encoded string derived from the given string +str+. # # The returned string: # # - Preserves: # # - Characters '*', '.', '-', and '_'. # - Character in ranges 'a'..'z', 'A'..'Z', # and '0'..'9'. # # Example: # # Gem::URI.encode_www_form_component('*.-_azAZ09') # # => "*.-_azAZ09" # # - Converts: # # - Character ' ' to character '+'. # - Any other character to "percent notation"; # the percent notation for character c is '%%%X' % c.ord. # # Example: # # Gem::URI.encode_www_form_component('Here are some punctuation characters: ,;?:') # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A" # # Encoding: # # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored. # - Otherwise +str+ is converted first to Encoding::UTF_8 # (with suitable character replacements), # and then to encoding +enc+. # # In either case, the returned string has forced encoding Encoding::US_ASCII. # # Related: Gem::URI.encode_uri_component (encodes ' ' as '%20'). def self.encode_www_form_component(str, enc=nil) _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc) end # Returns a string decoded from the given \URL-encoded string +str+. # # The given string is first encoded as Encoding::ASCII-8BIT (using String#b), # then decoded (as below), and finally force-encoded to the given encoding +enc+. # # The returned string: # # - Preserves: # # - Characters '*', '.', '-', and '_'. # - Character in ranges 'a'..'z', 'A'..'Z', # and '0'..'9'. # # Example: # # Gem::URI.decode_www_form_component('*.-_azAZ09') # # => "*.-_azAZ09" # # - Converts: # # - Character '+' to character ' '. # - Each "percent notation" to an ASCII character. # # Example: # # Gem::URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A') # # => "Here are some punctuation characters: ,;?:" # # Related: Gem::URI.decode_uri_component (preserves '+'). def self.decode_www_form_component(str, enc=Encoding::UTF_8) _decode_uri_component(/\+|%\h\h/, str, enc) end # Like Gem::URI.encode_www_form_component, except that ' ' (space) # is encoded as '%20' (instead of '+'). def self.encode_uri_component(str, enc=nil) _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc) end # Like Gem::URI.decode_www_form_component, except that '+' is preserved. def self.decode_uri_component(str, enc=Encoding::UTF_8) _decode_uri_component(/%\h\h/, str, enc) end def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT if enc && enc != Encoding::ASCII_8BIT str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) str.encode!(enc, fallback: ->(x){"&##{x.ord};"}) end str.force_encoding(Encoding::ASCII_8BIT) end str.gsub!(regexp, table) str.force_encoding(Encoding::US_ASCII) end private_class_method :_encode_uri_component def self._decode_uri_component(regexp, str, enc) raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) end private_class_method :_decode_uri_component # Returns a URL-encoded string derived from the given # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes] # +enum+. # # The result is suitable for use as form data # for an \HTTP request whose Content-Type is # 'application/x-www-form-urlencoded'. # # The returned string consists of the elements of +enum+, # each converted to one or more URL-encoded strings, # and all joined with character '&'. # # Simple examples: # # Gem::URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]]) # # => "foo=0&bar=1&baz=2" # Gem::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) # # => "foo=0&bar=1&baz=2" # # The returned string is formed using method Gem::URI.encode_www_form_component, # which converts certain characters: # # Gem::URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@') # # => "f%23o=%2F&b-r=%24&b+z=%40" # # When +enum+ is Array-like, each element +ele+ is converted to a field: # # - If +ele+ is an array of two or more elements, # the field is formed from its first two elements # (and any additional elements are ignored): # # name = Gem::URI.encode_www_form_component(ele[0], enc) # value = Gem::URI.encode_www_form_component(ele[1], enc) # "#{name}=#{value}" # # Examples: # # Gem::URI.encode_www_form([%w[foo bar], %w[baz bat bah]]) # # => "foo=bar&baz=bat" # Gem::URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']]) # # => "foo=0&bar=baz" # # - If +ele+ is an array of one element, # the field is formed from ele[0]: # # Gem::URI.encode_www_form_component(ele[0]) # # Example: # # Gem::URI.encode_www_form([['foo'], [:bar], [0]]) # # => "foo&bar&0" # # - Otherwise the field is formed from +ele+: # # Gem::URI.encode_www_form_component(ele) # # Example: # # Gem::URI.encode_www_form(['foo', :bar, 0]) # # => "foo&bar&0" # # The elements of an Array-like +enum+ may be mixture: # # Gem::URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat]) # # => "foo=0&bar=1&baz&bat" # # When +enum+ is Hash-like, # each +key+/+value+ pair is converted to one or more fields: # # - If +value+ is # {Array-convertible}[rdoc-ref:implicit_conversion.rdoc@Array-Convertible+Objects], # each element +ele+ in +value+ is paired with +key+ to form a field: # # name = Gem::URI.encode_www_form_component(key, enc) # value = Gem::URI.encode_www_form_component(ele, enc) # "#{name}=#{value}" # # Example: # # Gem::URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]}) # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2" # # - Otherwise, +key+ and +value+ are paired to form a field: # # name = Gem::URI.encode_www_form_component(key, enc) # value = Gem::URI.encode_www_form_component(value, enc) # "#{name}=#{value}" # # Example: # # Gem::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) # # => "foo=0&bar=1&baz=2" # # The elements of a Hash-like +enum+ may be mixture: # # Gem::URI.encode_www_form({foo: [0, 1], bar: 2}) # # => "foo=0&foo=1&bar=2" # def self.encode_www_form(enum, enc=nil) enum.map do |k,v| if v.nil? encode_www_form_component(k, enc) elsif v.respond_to?(:to_ary) v.to_ary.map do |w| str = encode_www_form_component(k, enc) unless w.nil? str << '=' str << encode_www_form_component(w, enc) end end.join('&') else str = encode_www_form_component(k, enc) str << '=' str << encode_www_form_component(v, enc) end end.join('&') end # Returns name/value pairs derived from the given string +str+, # which must be an ASCII string. # # The method may be used to decode the body of Net::HTTPResponse object +res+ # for which res['Content-Type'] is 'application/x-www-form-urlencoded'. # # The returned data is an array of 2-element subarrays; # each subarray is a name/value pair (both are strings). # Each returned string has encoding +enc+, # and has had invalid characters removed via # {String#scrub}[rdoc-ref:String#scrub]. # # A simple example: # # Gem::URI.decode_www_form('foo=0&bar=1&baz') # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] # # The returned strings have certain conversions, # similar to those performed in Gem::URI.decode_www_form_component: # # Gem::URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40') # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]] # # The given string may contain consecutive separators: # # Gem::URI.decode_www_form('foo=0&&bar=1&&baz=2') # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]] # # A different separator may be specified: # # Gem::URI.decode_www_form('foo=0--bar=1--baz', separator: '--') # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] # def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false) raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only? ary = [] return ary if str.empty? enc = Encoding.find(enc) str.b.each_line(separator) do |string| string.chomp!(separator) key, sep, val = string.partition('=') if isindex if sep.empty? val = key key = +'' end isindex = false end if use__charset_ and key == '_charset_' and e = get_encoding(val) enc = e use__charset_ = false end key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) if val val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) else val = +'' end ary << [key, val] end ary.each do |k, v| k.force_encoding(enc) k.scrub! v.force_encoding(enc) v.scrub! end ary end private =begin command for WEB_ENCODINGS_ curl https://encoding.spec.whatwg.org/encodings.json| ruby -rjson -e 'H={} h={ "shift_jis"=>"Windows-31J", "euc-jp"=>"cp51932", "iso-2022-jp"=>"cp50221", "x-mac-cyrillic"=>"macCyrillic", } JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x| Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next x["labels"].each{|y|H[y]=n} } puts "{" H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]} puts "}" ' =end WEB_ENCODINGS_ = { "unicode-1-1-utf-8"=>"utf-8", "utf-8"=>"utf-8", "utf8"=>"utf-8", "866"=>"ibm866", "cp866"=>"ibm866", "csibm866"=>"ibm866", "ibm866"=>"ibm866", "csisolatin2"=>"iso-8859-2", "iso-8859-2"=>"iso-8859-2", "iso-ir-101"=>"iso-8859-2", "iso8859-2"=>"iso-8859-2", "iso88592"=>"iso-8859-2", "iso_8859-2"=>"iso-8859-2", "iso_8859-2:1987"=>"iso-8859-2", "l2"=>"iso-8859-2", "latin2"=>"iso-8859-2", "csisolatin3"=>"iso-8859-3", "iso-8859-3"=>"iso-8859-3", "iso-ir-109"=>"iso-8859-3", "iso8859-3"=>"iso-8859-3", "iso88593"=>"iso-8859-3", "iso_8859-3"=>"iso-8859-3", "iso_8859-3:1988"=>"iso-8859-3", "l3"=>"iso-8859-3", "latin3"=>"iso-8859-3", "csisolatin4"=>"iso-8859-4", "iso-8859-4"=>"iso-8859-4", "iso-ir-110"=>"iso-8859-4", "iso8859-4"=>"iso-8859-4", "iso88594"=>"iso-8859-4", "iso_8859-4"=>"iso-8859-4", "iso_8859-4:1988"=>"iso-8859-4", "l4"=>"iso-8859-4", "latin4"=>"iso-8859-4", "csisolatincyrillic"=>"iso-8859-5", "cyrillic"=>"iso-8859-5", "iso-8859-5"=>"iso-8859-5", "iso-ir-144"=>"iso-8859-5", "iso8859-5"=>"iso-8859-5", "iso88595"=>"iso-8859-5", "iso_8859-5"=>"iso-8859-5", "iso_8859-5:1988"=>"iso-8859-5", "arabic"=>"iso-8859-6", "asmo-708"=>"iso-8859-6", "csiso88596e"=>"iso-8859-6", "csiso88596i"=>"iso-8859-6", "csisolatinarabic"=>"iso-8859-6", "ecma-114"=>"iso-8859-6", "iso-8859-6"=>"iso-8859-6", "iso-8859-6-e"=>"iso-8859-6", "iso-8859-6-i"=>"iso-8859-6", "iso-ir-127"=>"iso-8859-6", "iso8859-6"=>"iso-8859-6", "iso88596"=>"iso-8859-6", "iso_8859-6"=>"iso-8859-6", "iso_8859-6:1987"=>"iso-8859-6", "csisolatingreek"=>"iso-8859-7", "ecma-118"=>"iso-8859-7", "elot_928"=>"iso-8859-7", "greek"=>"iso-8859-7", "greek8"=>"iso-8859-7", "iso-8859-7"=>"iso-8859-7", "iso-ir-126"=>"iso-8859-7", "iso8859-7"=>"iso-8859-7", "iso88597"=>"iso-8859-7", "iso_8859-7"=>"iso-8859-7", "iso_8859-7:1987"=>"iso-8859-7", "sun_eu_greek"=>"iso-8859-7", "csiso88598e"=>"iso-8859-8", "csisolatinhebrew"=>"iso-8859-8", "hebrew"=>"iso-8859-8", "iso-8859-8"=>"iso-8859-8", "iso-8859-8-e"=>"iso-8859-8", "iso-ir-138"=>"iso-8859-8", "iso8859-8"=>"iso-8859-8", "iso88598"=>"iso-8859-8", "iso_8859-8"=>"iso-8859-8", "iso_8859-8:1988"=>"iso-8859-8", "visual"=>"iso-8859-8", "csisolatin6"=>"iso-8859-10", "iso-8859-10"=>"iso-8859-10", "iso-ir-157"=>"iso-8859-10", "iso8859-10"=>"iso-8859-10", "iso885910"=>"iso-8859-10", "l6"=>"iso-8859-10", "latin6"=>"iso-8859-10", "iso-8859-13"=>"iso-8859-13", "iso8859-13"=>"iso-8859-13", "iso885913"=>"iso-8859-13", "iso-8859-14"=>"iso-8859-14", "iso8859-14"=>"iso-8859-14", "iso885914"=>"iso-8859-14", "csisolatin9"=>"iso-8859-15", "iso-8859-15"=>"iso-8859-15", "iso8859-15"=>"iso-8859-15", "iso885915"=>"iso-8859-15", "iso_8859-15"=>"iso-8859-15", "l9"=>"iso-8859-15", "iso-8859-16"=>"iso-8859-16", "cskoi8r"=>"koi8-r", "koi"=>"koi8-r", "koi8"=>"koi8-r", "koi8-r"=>"koi8-r", "koi8_r"=>"koi8-r", "koi8-ru"=>"koi8-u", "koi8-u"=>"koi8-u", "dos-874"=>"windows-874", "iso-8859-11"=>"windows-874", "iso8859-11"=>"windows-874", "iso885911"=>"windows-874", "tis-620"=>"windows-874", "windows-874"=>"windows-874", "cp1250"=>"windows-1250", "windows-1250"=>"windows-1250", "x-cp1250"=>"windows-1250", "cp1251"=>"windows-1251", "windows-1251"=>"windows-1251", "x-cp1251"=>"windows-1251", "ansi_x3.4-1968"=>"windows-1252", "ascii"=>"windows-1252", "cp1252"=>"windows-1252", "cp819"=>"windows-1252", "csisolatin1"=>"windows-1252", "ibm819"=>"windows-1252", "iso-8859-1"=>"windows-1252", "iso-ir-100"=>"windows-1252", "iso8859-1"=>"windows-1252", "iso88591"=>"windows-1252", "iso_8859-1"=>"windows-1252", "iso_8859-1:1987"=>"windows-1252", "l1"=>"windows-1252", "latin1"=>"windows-1252", "us-ascii"=>"windows-1252", "windows-1252"=>"windows-1252", "x-cp1252"=>"windows-1252", "cp1253"=>"windows-1253", "windows-1253"=>"windows-1253", "x-cp1253"=>"windows-1253", "cp1254"=>"windows-1254", "csisolatin5"=>"windows-1254", "iso-8859-9"=>"windows-1254", "iso-ir-148"=>"windows-1254", "iso8859-9"=>"windows-1254", "iso88599"=>"windows-1254", "iso_8859-9"=>"windows-1254", "iso_8859-9:1989"=>"windows-1254", "l5"=>"windows-1254", "latin5"=>"windows-1254", "windows-1254"=>"windows-1254", "x-cp1254"=>"windows-1254", "cp1255"=>"windows-1255", "windows-1255"=>"windows-1255", "x-cp1255"=>"windows-1255", "cp1256"=>"windows-1256", "windows-1256"=>"windows-1256", "x-cp1256"=>"windows-1256", "cp1257"=>"windows-1257", "windows-1257"=>"windows-1257", "x-cp1257"=>"windows-1257", "cp1258"=>"windows-1258", "windows-1258"=>"windows-1258", "x-cp1258"=>"windows-1258", "x-mac-cyrillic"=>"macCyrillic", "x-mac-ukrainian"=>"macCyrillic", "chinese"=>"gbk", "csgb2312"=>"gbk", "csiso58gb231280"=>"gbk", "gb2312"=>"gbk", "gb_2312"=>"gbk", "gb_2312-80"=>"gbk", "gbk"=>"gbk", "iso-ir-58"=>"gbk", "x-gbk"=>"gbk", "gb18030"=>"gb18030", "big5"=>"big5", "big5-hkscs"=>"big5", "cn-big5"=>"big5", "csbig5"=>"big5", "x-x-big5"=>"big5", "cseucpkdfmtjapanese"=>"cp51932", "euc-jp"=>"cp51932", "x-euc-jp"=>"cp51932", "csiso2022jp"=>"cp50221", "iso-2022-jp"=>"cp50221", "csshiftjis"=>"Windows-31J", "ms932"=>"Windows-31J", "ms_kanji"=>"Windows-31J", "shift-jis"=>"Windows-31J", "shift_jis"=>"Windows-31J", "sjis"=>"Windows-31J", "windows-31j"=>"Windows-31J", "x-sjis"=>"Windows-31J", "cseuckr"=>"euc-kr", "csksc56011987"=>"euc-kr", "euc-kr"=>"euc-kr", "iso-ir-149"=>"euc-kr", "korean"=>"euc-kr", "ks_c_5601-1987"=>"euc-kr", "ks_c_5601-1989"=>"euc-kr", "ksc5601"=>"euc-kr", "ksc_5601"=>"euc-kr", "windows-949"=>"euc-kr", "utf-16be"=>"utf-16be", "utf-16"=>"utf-16le", "utf-16le"=>"utf-16le", } # :nodoc: Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor) # :nodoc: # return encoding or nil # http://encoding.spec.whatwg.org/#concept-encoding-get def self.get_encoding(label) Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil end end # module Gem::URI module Gem # # Returns a \Gem::URI object derived from the given +uri+, # which may be a \Gem::URI string or an existing \Gem::URI object: # # # Returns a new Gem::URI. # uri = Gem::URI('http://github.com/ruby/ruby') # # => # # # Returns the given Gem::URI. # Gem::URI(uri) # # => # # def URI(uri) if uri.is_a?(Gem::URI::Generic) uri elsif uri = String.try_convert(uri) Gem::URI.parse(uri) else raise ArgumentError, "bad argument (expected Gem::URI object or Gem::URI string)" end end module_function :URI end PK!H)<''"rubygems/vendor/uri/lib/uri/wss.rbnu[# frozen_string_literal: false # = uri/wss.rb # # Author:: Matt Muller # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'ws' module Gem::URI # The default port for WSS URIs is 443, and the scheme is 'wss:' rather # than 'ws:'. Other than that, WSS URIs are identical to WS URIs; # see Gem::URI::WS. class WSS < WS # A Default port of 443 for Gem::URI::WSS DEFAULT_PORT = 443 end register_scheme 'WSS', WSS end PK!~Cf` ` #rubygems/vendor/uri/lib/uri/file.rbnu[# frozen_string_literal: true require_relative 'generic' module Gem::URI # # The "file" Gem::URI is defined by RFC8089. # class File < Generic # A Default port of nil for Gem::URI::File. DEFAULT_PORT = nil # # An Array of the available components for Gem::URI::File. # COMPONENT = [ :scheme, :host, :path ].freeze # # == Description # # Creates a new Gem::URI::File object from components, with syntax checking. # # The components accepted are +host+ and +path+. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [host, path]. # # A path from e.g. the File class should be escaped before # being passed. # # Examples: # # require 'rubygems/vendor/uri/lib/uri' # # uri1 = Gem::URI::File.build(['host.example.com', '/path/file.zip']) # uri1.to_s # => "file://host.example.com/path/file.zip" # # uri2 = Gem::URI::File.build({:host => 'host.example.com', # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # # uri3 = Gem::URI::File.build({:path => Gem::URI::escape('/path/my file.txt')}) # uri3.to_s # => "file:///path/my%20file.txt" # def self.build(args) tmp = Util::make_components_hash(self, args) super(tmp) end # Protected setter for the host component +v+. # # See also Gem::URI::Generic.host=. # def set_host(v) v = "" if v.nil? || v == "localhost" @host = v end # do nothing def set_port(v) end # raise InvalidURIError def check_userinfo(user) raise Gem::URI::InvalidURIError, "can not set userinfo for file Gem::URI" end # raise InvalidURIError def check_user(user) raise Gem::URI::InvalidURIError, "can not set user for file Gem::URI" end # raise InvalidURIError def check_password(user) raise Gem::URI::InvalidURIError, "can not set password for file Gem::URI" end # do nothing def set_userinfo(v) end # do nothing def set_user(v) end # do nothing def set_password(v) end end register_scheme 'FILE', File end PK!Pyjj#rubygems/vendor/uri/lib/uri/ldap.rbnu[# frozen_string_literal: false # = uri/ldap.rb # # Author:: # Takaaki Tateishi # Akira Yamada # License:: # Gem::URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada. # You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # LDAP Gem::URI SCHEMA (described in RFC2255). #-- # ldap:///[?[?[?[?]]]] #++ class LDAP < Generic # A Default port of 389 for Gem::URI::LDAP. DEFAULT_PORT = 389 # An Array of the available components for Gem::URI::LDAP. COMPONENT = [ :scheme, :host, :port, :dn, :attributes, :scope, :filter, :extensions, ].freeze # Scopes available for the starting point. # # * SCOPE_BASE - the Base DN # * SCOPE_ONE - one level under the Base DN, not including the base DN and # not including any entries under this # * SCOPE_SUB - subtrees, all entries at all levels # SCOPE = [ SCOPE_ONE = 'one', SCOPE_SUB = 'sub', SCOPE_BASE = 'base', ].freeze # # == Description # # Creates a new Gem::URI::LDAP object from components, with syntax checking. # # The components accepted are host, port, dn, attributes, # scope, filter, and extensions. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [host, port, dn, attributes, scope, filter, extensions]. # # Example: # # uri = Gem::URI::LDAP.build({:host => 'ldap.example.com', # :dn => '/dc=example'}) # # uri = Gem::URI::LDAP.build(["ldap.example.com", nil, # "/dc=example;dc=com", "query", nil, nil, nil]) # def self.build(args) tmp = Util::make_components_hash(self, args) if tmp[:dn] tmp[:path] = tmp[:dn] end query = [] [:extensions, :filter, :scope, :attributes].collect do |x| next if !tmp[x] && query.size == 0 query.unshift(tmp[x]) end tmp[:query] = query.join('?') return super(tmp) end # # == Description # # Creates a new Gem::URI::LDAP object from generic Gem::URI components as per # RFC 2396. No LDAP-specific syntax checking is performed. # # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, # +opaque+, +query+, and +fragment+, in that order. # # Example: # # uri = Gem::URI::LDAP.new("ldap", nil, "ldap.example.com", nil, nil, # "/dc=example;dc=com", nil, "query", nil) # # See also Gem::URI::Generic.new. # def initialize(*arg) super(*arg) if @fragment raise InvalidURIError, 'bad LDAP URL' end parse_dn parse_query end # Private method to cleanup +dn+ from using the +path+ component attribute. def parse_dn raise InvalidURIError, 'bad LDAP URL' unless @path @dn = @path[1..-1] end private :parse_dn # Private method to cleanup +attributes+, +scope+, +filter+, and +extensions+ # from using the +query+ component attribute. def parse_query @attributes = nil @scope = nil @filter = nil @extensions = nil if @query attrs, scope, filter, extensions = @query.split('?') @attributes = attrs if attrs && attrs.size > 0 @scope = scope if scope && scope.size > 0 @filter = filter if filter && filter.size > 0 @extensions = extensions if extensions && extensions.size > 0 end end private :parse_query # Private method to assemble +query+ from +attributes+, +scope+, +filter+, and +extensions+. def build_path_query @path = '/' + @dn query = [] [@extensions, @filter, @scope, @attributes].each do |x| next if !x && query.size == 0 query.unshift(x) end @query = query.join('?') end private :build_path_query # Returns dn. def dn @dn end # Private setter for dn +val+. def set_dn(val) @dn = val build_path_query @dn end protected :set_dn # Setter for dn +val+. def dn=(val) set_dn(val) val end # Returns attributes. def attributes @attributes end # Private setter for attributes +val+. def set_attributes(val) @attributes = val build_path_query @attributes end protected :set_attributes # Setter for attributes +val+. def attributes=(val) set_attributes(val) val end # Returns scope. def scope @scope end # Private setter for scope +val+. def set_scope(val) @scope = val build_path_query @scope end protected :set_scope # Setter for scope +val+. def scope=(val) set_scope(val) val end # Returns filter. def filter @filter end # Private setter for filter +val+. def set_filter(val) @filter = val build_path_query @filter end protected :set_filter # Setter for filter +val+. def filter=(val) set_filter(val) val end # Returns extensions. def extensions @extensions end # Private setter for extensions +val+. def set_extensions(val) @extensions = val build_path_query @extensions end protected :set_extensions # Setter for extensions +val+. def extensions=(val) set_extensions(val) val end # Checks if Gem::URI has a path. # For Gem::URI::LDAP this will return +false+. def hierarchical? false end end register_scheme 'LDAP', LDAP end PK!AhS&rubygems/vendor/uri/lib/uri/generic.rbnu[# frozen_string_literal: true # = uri/generic.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'common' autoload :IPSocket, 'socket' autoload :IPAddr, 'ipaddr' module Gem::URI # # Base class for all Gem::URI classes. # Implements generic Gem::URI syntax as per RFC 2396. # class Generic include Gem::URI # # A Default port of nil for Gem::URI::Generic. # DEFAULT_PORT = nil # # Returns default port. # def self.default_port self::DEFAULT_PORT end # # Returns default port. # def default_port self.class.default_port end # # An Array of the available components for Gem::URI::Generic. # COMPONENT = [ :scheme, :userinfo, :host, :port, :registry, :path, :opaque, :query, :fragment ].freeze # # Components of the Gem::URI in the order. # def self.component self::COMPONENT end USE_REGISTRY = false # :nodoc: def self.use_registry # :nodoc: self::USE_REGISTRY end # # == Synopsis # # See ::new. # # == Description # # At first, tries to create a new Gem::URI::Generic instance using # Gem::URI::Generic::build. But, if exception Gem::URI::InvalidComponentError is raised, # then it does Gem::URI::Escape.escape all Gem::URI components and tries again. # def self.build2(args) begin return self.build(args) rescue InvalidComponentError if args.kind_of?(Array) return self.build(args.collect{|x| if x.is_a?(String) DEFAULT_PARSER.escape(x) else x end }) elsif args.kind_of?(Hash) tmp = {} args.each do |key, value| tmp[key] = if value DEFAULT_PARSER.escape(value) else value end end return self.build(tmp) end end end # # == Synopsis # # See ::new. # # == Description # # Creates a new Gem::URI::Generic instance from components of Gem::URI::Generic # with check. Components are: scheme, userinfo, host, port, registry, path, # opaque, query, and fragment. You can provide arguments either by an Array or a Hash. # See ::new for hash keys to use or for order of array items. # def self.build(args) if args.kind_of?(Array) && args.size == ::Gem::URI::Generic::COMPONENT.size tmp = args.dup elsif args.kind_of?(Hash) tmp = ::Gem::URI::Generic::COMPONENT.collect do |c| if args.include?(c) args[c] else nil end end else component = self.class.component rescue ::Gem::URI::Generic::COMPONENT raise ArgumentError, "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" end tmp << nil tmp << true return self.new(*tmp) end # # == Args # # +scheme+:: # Protocol scheme, i.e. 'http','ftp','mailto' and so on. # +userinfo+:: # User name and password, i.e. 'sdmitry:bla'. # +host+:: # Server host name. # +port+:: # Server port. # +registry+:: # Registry of naming authorities. # +path+:: # Path on server. # +opaque+:: # Opaque part. # +query+:: # Query data. # +fragment+:: # Part of the Gem::URI after '#' character. # +parser+:: # Parser for internal use [Gem::URI::DEFAULT_PARSER by default]. # +arg_check+:: # Check arguments [false by default]. # # == Description # # Creates a new Gem::URI::Generic instance from ``generic'' components without check. # def initialize(scheme, userinfo, host, port, registry, path, opaque, query, fragment, parser = DEFAULT_PARSER, arg_check = false) @scheme = nil @user = nil @password = nil @host = nil @port = nil @path = nil @query = nil @opaque = nil @fragment = nil @parser = parser == DEFAULT_PARSER ? nil : parser if arg_check self.scheme = scheme self.hostname = host self.port = port self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) self.set_host(host) self.set_port(port) self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) self.fragment=(fragment) end if registry raise InvalidURIError, "the scheme #{@scheme} does not accept registry part: #{registry} (or bad hostname?)" end @scheme&.freeze self.set_path('') if !@path && !@opaque # (see RFC2396 Section 5.2) self.set_port(self.default_port) if self.default_port && !@port end # # Returns the scheme component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").scheme #=> "http" # attr_reader :scheme # Returns the host component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").host #=> "foo" # # It returns nil if no host component exists. # # Gem::URI("mailto:foo@example.org").host #=> nil # # The component does not contain the port number. # # Gem::URI("http://foo:8080/bar/baz").host #=> "foo" # # Since IPv6 addresses are wrapped with brackets in URIs, # this method returns IPv6 addresses wrapped with brackets. # This form is not appropriate to pass to socket methods such as TCPSocket.open. # If unwrapped host names are required, use the #hostname method. # # Gem::URI("http://[::1]/bar/baz").host #=> "[::1]" # Gem::URI("http://[::1]/bar/baz").hostname #=> "::1" # attr_reader :host # Returns the port component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").port #=> 80 # Gem::URI("http://foo:8080/bar/baz").port #=> 8080 # attr_reader :port def registry # :nodoc: nil end # Returns the path component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").path #=> "/bar/baz" # attr_reader :path # Returns the query component of the Gem::URI. # # Gem::URI("http://foo/bar/baz?search=FooBar").query #=> "search=FooBar" # attr_reader :query # Returns the opaque part of the Gem::URI. # # Gem::URI("mailto:foo@example.org").opaque #=> "foo@example.org" # Gem::URI("http://foo/bar/baz").opaque #=> nil # # The portion of the path that does not make use of the slash '/'. # The path typically refers to an absolute path or an opaque part. # (See RFC2396 Section 3 and 5.2.) # attr_reader :opaque # Returns the fragment component of the Gem::URI. # # Gem::URI("http://foo/bar/baz?search=FooBar#ponies").fragment #=> "ponies" # attr_reader :fragment # Returns the parser to be used. # # Unless a Gem::URI::Parser is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser DEFAULT_PARSER else @parser || DEFAULT_PARSER end end # Replaces self by other Gem::URI object. # def replace!(oth) if self.class != oth.class raise ArgumentError, "expected #{self.class} object" end component.each do |c| self.__send__("#{c}=", oth.__send__(c)) end end private :replace! # # Components of the Gem::URI in the order. # def component self.class.component end # # Checks the scheme +v+ component against the Gem::URI::Parser Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v raise InvalidComponentError, "bad component(expected scheme component): #{v}" end return true end private :check_scheme # Protected setter for the scheme component +v+. # # See also Gem::URI::Generic.scheme=. # def set_scheme(v) @scheme = v&.downcase end protected :set_scheme # # == Args # # +v+:: # String # # == Description # # Public setter for the scheme component +v+ # (with validation). # # See also Gem::URI::Generic.check_scheme. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.scheme = "https" # uri.to_s #=> "https://my.example.com" # def scheme=(v) check_scheme(v) set_scheme(v) v end # # Checks the +user+ and +password+. # # If +password+ is not provided, then +user+ is # split, using Gem::URI::Generic.split_userinfo, to # pull +user+ and +password. # # See also Gem::URI::Generic.check_user, Gem::URI::Generic.check_password. # def check_userinfo(user, password = nil) if !password user, password = split_userinfo(user) end check_user(user) check_password(password, user) return true end private :check_userinfo # # Checks the user +v+ component for RFC2396 compliance # and against the Gem::URI::Parser Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. # def check_user(v) if @opaque raise InvalidURIError, "can not set user with opaque" end return v unless v if parser.regexp[:USERINFO] !~ v raise InvalidComponentError, "bad component(expected userinfo component or user component): #{v}" end return true end private :check_user # # Checks the password +v+ component for RFC2396 compliance # and against the Gem::URI::Parser Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. # def check_password(v, user = @user) if @opaque raise InvalidURIError, "can not set password with opaque" end return v unless v if !user raise InvalidURIError, "password component depends user component" end if parser.regexp[:USERINFO] !~ v raise InvalidComponentError, "bad password component" end return true end private :check_password # # Sets userinfo, argument is string like 'name:pass'. # def userinfo=(userinfo) if userinfo.nil? return nil end check_userinfo(*userinfo) set_userinfo(*userinfo) # returns userinfo end # # == Args # # +v+:: # String # # == Description # # Public setter for the +user+ component # (with validation). # # See also Gem::URI::Generic.check_user. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://john:S3nsit1ve@my.example.com") # uri.user = "sam" # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com" # def user=(user) check_user(user) set_user(user) # returns user end # # == Args # # +v+:: # String # # == Description # # Public setter for the +password+ component # (with validation). # # See also Gem::URI::Generic.check_password. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://john:S3nsit1ve@my.example.com") # uri.password = "V3ry_S3nsit1ve" # uri.to_s #=> "http://john:V3ry_S3nsit1ve@my.example.com" # def password=(password) check_password(password) set_password(password) # returns password end # Protected setter for the +user+ component, and +password+ if available # (with validation). # # See also Gem::URI::Generic.userinfo=. # def set_userinfo(user, password = nil) unless password user, password = split_userinfo(user) end @user = user @password = password [@user, @password] end protected :set_userinfo # Protected setter for the user component +v+. # # See also Gem::URI::Generic.user=. # def set_user(v) set_userinfo(v, nil) v end protected :set_user # Protected setter for the password component +v+. # # See also Gem::URI::Generic.password=. # def set_password(v) @password = v # returns v end protected :set_password # Returns the userinfo +ui+ as [user, password] # if properly formatted as 'user:password'. def split_userinfo(ui) return nil, nil unless ui user, password = ui.split(':', 2) return user, password end private :split_userinfo # Escapes 'user:password' +v+ based on RFC 1738 section 3.1. def escape_userpass(v) parser.escape(v, /[@:\/]/o) # RFC 1738 section 3.1 #/ end private :escape_userpass # Returns the userinfo, either as 'user' or 'user:password'. def userinfo if @user.nil? nil elsif @password.nil? @user else @user + ':' + @password end end # Returns the user component (without Gem::URI decoding). def user @user end # Returns the password component (without Gem::URI decoding). def password @password end # Returns the authority info (array of user, password, host and # port), if any is set. Or returns +nil+. def authority return @user, @password, @host, @port if @user || @password || @host || @port end # Returns the user component after Gem::URI decoding. def decoded_user Gem::URI.decode_uri_component(@user) if @user end # Returns the password component after Gem::URI decoding. def decoded_password Gem::URI.decode_uri_component(@password) if @password end # # Checks the host +v+ component for RFC2396 compliance # and against the Gem::URI::Parser Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. # def check_host(v) return v unless v if @opaque raise InvalidURIError, "can not set host with registry or opaque" elsif parser.regexp[:HOST] !~ v raise InvalidComponentError, "bad component(expected host component): #{v}" end return true end private :check_host # Protected setter for the host component +v+. # # See also Gem::URI::Generic.host=. # def set_host(v) @host = v end protected :set_host # Protected setter for the authority info (+user+, +password+, +host+ # and +port+). If +port+ is +nil+, +default_port+ will be set. # protected def set_authority(user, password, host, port = nil) @user, @password, @host, @port = user, password, host, port || self.default_port end # # == Args # # +v+:: # String # # == Description # # Public setter for the host component +v+ # (with validation). # # See also Gem::URI::Generic.check_host. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.host = "foo.com" # uri.to_s #=> "http://foo.com" # def host=(v) check_host(v) set_host(v) set_userinfo(nil) v end # Extract the host part of the Gem::URI and unwrap brackets for IPv6 addresses. # # This method is the same as Gem::URI::Generic#host except # brackets for IPv6 (and future IP) addresses are removed. # # uri = Gem::URI("http://[::1]/bar") # uri.hostname #=> "::1" # uri.host #=> "[::1]" # def hostname v = self.host v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v end # Sets the host part of the Gem::URI as the argument with brackets for IPv6 addresses. # # This method is the same as Gem::URI::Generic#host= except # the argument can be a bare IPv6 address. # # uri = Gem::URI("http://foo/bar") # uri.hostname = "::1" # uri.to_s #=> "http://[::1]/bar" # # If the argument seems to be an IPv6 address, # it is wrapped with brackets. # def hostname=(v) v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':') self.host = v end # # Checks the port +v+ component for RFC2396 compliance # and against the Gem::URI::Parser Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. # def check_port(v) return v unless v if @opaque raise InvalidURIError, "can not set port with registry or opaque" elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v raise InvalidComponentError, "bad component(expected port component): #{v.inspect}" end return true end private :check_port # Protected setter for the port component +v+. # # See also Gem::URI::Generic.port=. # def set_port(v) v = v.empty? ? nil : v.to_i unless !v || v.kind_of?(Integer) @port = v end protected :set_port # # == Args # # +v+:: # String # # == Description # # Public setter for the port component +v+ # (with validation). # # See also Gem::URI::Generic.check_port. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.port = 8080 # uri.to_s #=> "http://my.example.com:8080" # def port=(v) check_port(v) set_port(v) set_userinfo(nil) port end def check_registry(v) # :nodoc: raise InvalidURIError, "can not set registry" end private :check_registry def set_registry(v) #:nodoc: raise InvalidURIError, "can not set registry" end protected :set_registry def registry=(v) raise InvalidURIError, "can not set registry" end # # Checks the path +v+ component for RFC2396 compliance # and against the Gem::URI::Parser Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, # with a path component defined. # def check_path(v) # raise if both hier and opaque are not nil, because: # absoluteURI = scheme ":" ( hier_part | opaque_part ) # hier_part = ( net_path | abs_path ) [ "?" query ] if v && @opaque raise InvalidURIError, "path conflicts with opaque" end # If scheme is ftp, path may be relative. # See RFC 1738 section 3.2.2, and RFC 2396. if @scheme && @scheme != "ftp" if v && v != '' && parser.regexp[:ABS_PATH] !~ v raise InvalidComponentError, "bad component(expected absolute path component): #{v}" end else if v && v != '' && parser.regexp[:ABS_PATH] !~ v && parser.regexp[:REL_PATH] !~ v raise InvalidComponentError, "bad component(expected relative path component): #{v}" end end return true end private :check_path # Protected setter for the path component +v+. # # See also Gem::URI::Generic.path=. # def set_path(v) @path = v end protected :set_path # # == Args # # +v+:: # String # # == Description # # Public setter for the path component +v+ # (with validation). # # See also Gem::URI::Generic.check_path. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/pub/files") # uri.path = "/faq/" # uri.to_s #=> "http://my.example.com/faq/" # def path=(v) check_path(v) set_path(v) v end # # == Args # # +v+:: # String # # == Description # # Public setter for the query component +v+. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/?id=25") # uri.query = "id=1" # uri.to_s #=> "http://my.example.com/?id=1" # def query=(v) return @query = nil unless v raise InvalidURIError, "query conflicts with opaque" if @opaque x = v.to_str v = x.dup if x.equal? v v.encode!(Encoding::UTF_8) rescue nil v.delete!("\t\r\n") v.force_encoding(Encoding::ASCII_8BIT) raise InvalidURIError, "invalid percent escape: #{$1}" if /(%\H\H)/n.match(v) v.gsub!(/(?!%\h\h|[!$-&(-;=?-_a-~])./n.freeze){'%%%02X' % $&.ord} v.force_encoding(Encoding::US_ASCII) @query = v end # # Checks the opaque +v+ component for RFC2396 compliance and # against the Gem::URI::Parser Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. # def check_opaque(v) return v unless v # raise if both hier and opaque are not nil, because: # absoluteURI = scheme ":" ( hier_part | opaque_part ) # hier_part = ( net_path | abs_path ) [ "?" query ] if @host || @port || @user || @path # userinfo = @user + ':' + @password raise InvalidURIError, "can not set opaque with host, port, userinfo or path" elsif v && parser.regexp[:OPAQUE] !~ v raise InvalidComponentError, "bad component(expected opaque component): #{v}" end return true end private :check_opaque # Protected setter for the opaque component +v+. # # See also Gem::URI::Generic.opaque=. # def set_opaque(v) @opaque = v end protected :set_opaque # # == Args # # +v+:: # String # # == Description # # Public setter for the opaque component +v+ # (with validation). # # See also Gem::URI::Generic.check_opaque. # def opaque=(v) check_opaque(v) set_opaque(v) v end # # Checks the fragment +v+ component against the Gem::URI::Parser Regexp for :FRAGMENT. # # # == Args # # +v+:: # String # # == Description # # Public setter for the fragment component +v+ # (with validation). # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/?id=25#time=1305212049") # uri.fragment = "time=1305212086" # uri.to_s #=> "http://my.example.com/?id=25#time=1305212086" # def fragment=(v) return @fragment = nil unless v x = v.to_str v = x.dup if x.equal? v v.encode!(Encoding::UTF_8) rescue nil v.delete!("\t\r\n") v.force_encoding(Encoding::ASCII_8BIT) v.gsub!(/(?!%\h\h|[!-~])./n){'%%%02X' % $&.ord} v.force_encoding(Encoding::US_ASCII) @fragment = v end # # Returns true if Gem::URI is hierarchical. # # == Description # # Gem::URI has components listed in order of decreasing significance from left to right, # see RFC3986 https://tools.ietf.org/html/rfc3986 1.2.3. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/") # uri.hierarchical? # #=> true # uri = Gem::URI.parse("mailto:joe@example.com") # uri.hierarchical? # #=> false # def hierarchical? if @path true else false end end # # Returns true if Gem::URI has a scheme (e.g. http:// or https://) specified. # def absolute? if @scheme true else false end end alias absolute absolute? # # Returns true if Gem::URI does not have a scheme (e.g. http:// or https://) specified. # def relative? !absolute? end # # Returns an Array of the path split on '/'. # def split_path(path) path.split("/", -1) end private :split_path # # Merges a base path +base+, with relative path +rel+, # returns a modified base path. # def merge_path(base, rel) # RFC2396, Section 5.2, 5) # RFC2396, Section 5.2, 6) base_path = split_path(base) rel_path = split_path(rel) # RFC2396, Section 5.2, 6), a) base_path << '' if base_path.last == '..' while i = base_path.index('..') base_path.slice!(i - 1, 2) end if (first = rel_path.first) and first.empty? base_path.clear rel_path.shift end # RFC2396, Section 5.2, 6), c) # RFC2396, Section 5.2, 6), d) rel_path.push('') if rel_path.last == '.' || rel_path.last == '..' rel_path.delete('.') # RFC2396, Section 5.2, 6), e) tmp = [] rel_path.each do |x| if x == '..' && !(tmp.empty? || tmp.last == '..') tmp.pop else tmp << x end end add_trailer_slash = !tmp.empty? if base_path.empty? base_path = [''] # keep '/' for root directory elsif add_trailer_slash base_path.pop end while x = tmp.shift if x == '..' # RFC2396, Section 4 # a .. or . in an absolute path has no special meaning base_path.pop if base_path.size > 1 else # if x == '..' # valid absolute (but abnormal) path "/../..." # else # valid absolute path # end base_path << x tmp.each {|t| base_path << t} add_trailer_slash = false break end end base_path.push('') if add_trailer_slash return base_path.join('/') end private :merge_path # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Destructive form of #merge. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.merge!("/main.rbx?page=1") # uri.to_s # => "http://my.example.com/main.rbx?page=1" # def merge!(oth) t = merge(oth) if self == t nil else replace!(t) self end end # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Merges two URIs. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.merge("/main.rbx?page=1") # # => "http://my.example.com/main.rbx?page=1" # def merge(oth) rel = parser.__send__(:convert_to_uri, oth) if rel.absolute? #raise BadURIError, "both Gem::URI are absolute" if absolute? # hmm... should return oth for usability? return rel end unless self.absolute? raise BadURIError, "both Gem::URI are relative" end base = self.dup authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query base.fragment=(rel.fragment) if rel.fragment return base end base.query = nil base.fragment=(nil) # RFC2396, Section 5.2, 4) if authority base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) end # RFC2396, Section 5.2, 7) base.query = rel.query if rel.query base.fragment=(rel.fragment) if rel.fragment return base end # merge alias + merge # :stopdoc: def route_from_path(src, dst) case dst when src # RFC2396, Section 4.2 return '' when %r{(?:\A|/)\.\.?(?:/|\z)} # dst has abnormal absolute path, # like "/./", "/../", "/x/../", ... return dst.dup end src_path = src.scan(%r{[^/]*/}) dst_path = dst.scan(%r{[^/]*/?}) # discard same parts while !dst_path.empty? && dst_path.first == src_path.first src_path.shift dst_path.shift end tmp = dst_path.join # calculate if src_path.empty? if tmp.empty? return './' elsif dst_path.first.include?(':') # (see RFC2396 Section 5) return './' + tmp else return tmp end end return '../' * src_path.size + tmp end private :route_from_path # :startdoc: # :stopdoc: def route_from0(oth) oth = parser.__send__(:convert_to_uri, oth) if self.relative? raise BadURIError, "relative Gem::URI: #{self}" end if oth.relative? raise BadURIError, "relative Gem::URI: #{oth}" end if self.scheme != oth.scheme return self, self.dup end rel = Gem::URI::Generic.new(nil, # it is relative Gem::URI self.userinfo, self.host, self.port, nil, self.path, self.opaque, self.query, self.fragment, parser) if rel.userinfo != oth.userinfo || rel.host.to_s.downcase != oth.host.to_s.downcase || rel.port != oth.port if self.userinfo.nil? && self.host.nil? return self, self.dup end rel.set_port(nil) if rel.port == oth.default_port return rel, rel end rel.set_userinfo(nil) rel.set_host(nil) rel.set_port(nil) if rel.path && rel.path == oth.path rel.set_path('') rel.query = nil if rel.query == oth.query return rel, rel elsif rel.opaque && rel.opaque == oth.opaque rel.set_opaque('') rel.query = nil if rel.query == oth.query return rel, rel end # you can modify `rel', but can not `oth'. return oth, rel end private :route_from0 # :startdoc: # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Calculates relative path from oth to self. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse('http://my.example.com/main.rbx?page=1') # uri.route_from('http://my.example.com') # #=> # # def route_from(oth) # you can modify `rel', but can not `oth'. begin oth, rel = route_from0(oth) rescue raise $!.class, $!.message end if oth == rel return rel end rel.set_path(route_from_path(oth.path, self.path)) if rel.path == './' && self.query # "./?foo" -> "?foo" rel.set_path('') end return rel end alias - route_from # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Calculates relative path to oth from self. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse('http://my.example.com') # uri.route_to('http://my.example.com/main.rbx?page=1') # #=> # # def route_to(oth) parser.__send__(:convert_to_uri, oth).route_from(self) end # # Returns normalized Gem::URI. # # require 'rubygems/vendor/uri/lib/uri' # # Gem::URI("HTTP://my.EXAMPLE.com").normalize # #=> # # # Normalization here means: # # * scheme and host are converted to lowercase, # * an empty path component is set to "/". # def normalize uri = dup uri.normalize! uri end # # Destructive version of #normalize. # def normalize! if path&.empty? set_path('/') end if scheme && scheme != scheme.downcase set_scheme(self.scheme.downcase) end if host && host != host.downcase set_host(self.host.downcase) end end # # Constructs String from Gem::URI. # def to_s str = ''.dup if @scheme str << @scheme str << ':' end if @opaque str << @opaque else if @host || %w[file postgres].include?(@scheme) str << '//' end if self.userinfo str << self.userinfo str << '@' end if @host str << @host end if @port && @port != self.default_port str << ':' str << @port.to_s end str << @path if @query str << '?' str << @query end end if @fragment str << '#' str << @fragment end str end alias to_str to_s # # Compares two URIs. # def ==(oth) if self.class == oth.class self.normalize.component_ary == oth.normalize.component_ary else false end end def hash self.component_ary.hash end def eql?(oth) self.class == oth.class && parser == oth.parser && self.component_ary.eql?(oth.component_ary) end =begin --- Gem::URI::Generic#===(oth) =end # def ===(oth) # raise NotImplementedError # end =begin =end # Returns an Array of the components defined from the COMPONENT Array. def component_ary component.collect do |x| self.__send__(x) end end protected :component_ary # == Args # # +components+:: # Multiple Symbol arguments defined in Gem::URI::HTTP. # # == Description # # Selects specified components from Gem::URI. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse('http://myuser:mypass@my.example.com/test.rbx') # uri.select(:userinfo, :host, :path) # # => ["myuser:mypass", "my.example.com", "/test.rbx"] # def select(*components) components.collect do |c| if component.include?(c) self.__send__(c) else raise ArgumentError, "expected of components of #{self.class} (#{self.class.component.join(', ')})" end end end def inspect "#<#{self.class} #{self}>" end # # == Args # # +v+:: # Gem::URI or String # # == Description # # Attempts to parse other Gem::URI +oth+, # returns [parsed_oth, self]. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.coerce("http://foo.com") # #=> [#, #] # def coerce(oth) case oth when String oth = parser.parse(oth) else super end return oth, self end # Returns a proxy Gem::URI. # The proxy Gem::URI is obtained from environment variables such as http_proxy, # ftp_proxy, no_proxy, etc. # If there is no proper proxy, nil is returned. # # If the optional parameter +env+ is specified, it is used instead of ENV. # # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.) # are examined, too. # # But http_proxy and HTTP_PROXY is treated specially under CGI environment. # It's because HTTP_PROXY may be set by Proxy: header. # So HTTP_PROXY is not used. # http_proxy is not used too if the variable is case insensitive. # CGI_HTTP_PROXY can be used instead. def find_proxy(env=ENV) raise BadURIError, "relative Gem::URI: #{self}" if self.relative? name = self.scheme.downcase + '_proxy' proxy_uri = nil if name == 'http_proxy' && env.include?('REQUEST_METHOD') # CGI? # HTTP_PROXY conflicts with *_proxy for proxy settings and # HTTP_* for header information in CGI. # So it should be careful to use it. pairs = env.reject {|k, v| /\Ahttp_proxy\z/i !~ k } case pairs.length when 0 # no proxy setting anyway. proxy_uri = nil when 1 k, _ = pairs.shift if k == 'http_proxy' && env[k.upcase] == nil # http_proxy is safe to use because ENV is case sensitive. proxy_uri = env[name] else proxy_uri = nil end else # http_proxy is safe to use because ENV is case sensitive. proxy_uri = env.to_hash[name] end if !proxy_uri # Use CGI_HTTP_PROXY. cf. libwww-perl. proxy_uri = env["CGI_#{name.upcase}"] end elsif name == 'http_proxy' if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost'] p_port = ENV_JAVA['http.proxyPort'] if p_user = ENV_JAVA['http.proxyUser'] p_pass = ENV_JAVA['http.proxyPass'] proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}" else proxy_uri = "http://#{p_addr}:#{p_port}" end else unless proxy_uri = env[name] if proxy_uri = env[name.upcase] warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 end end end else proxy_uri = env[name] || env[name.upcase] end if proxy_uri.nil? || proxy_uri.empty? return nil end if self.hostname begin addr = IPSocket.getaddress(self.hostname) return nil if /\A127\.|\A::1\z/ =~ addr rescue SocketError end end name = 'no_proxy' if no_proxy = env[name] || env[name.upcase] return nil unless Gem::URI::Generic.use_proxy?(self.hostname, addr, self.port, no_proxy) end Gem::URI.parse(proxy_uri) end def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc: hostname = hostname.downcase dothostname = ".#{hostname}" no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) {|p_host, p_port| if !p_port || port == p_port.to_i if p_host.start_with?('.') return false if hostname.end_with?(p_host.downcase) else return false if dothostname.end_with?(".#{p_host.downcase}") end if addr begin return false if IPAddr.new(p_host).include?(addr) rescue IPAddr::InvalidAddressError next end end end } true end end end PK!]ZZ$rubygems/vendor/resolv/lib/resolv.rbnu[# frozen_string_literal: true require 'socket' require_relative '../../timeout/lib/timeout' require 'io/wait' begin require_relative '../../../vendored_securerandom' rescue LoadError end # Gem::Resolv is a thread-aware DNS resolver library written in Ruby. Gem::Resolv can # handle multiple DNS requests concurrently without blocking the entire Ruby # interpreter. # # See also resolv-replace.rb to replace the libc resolver with Gem::Resolv. # # Gem::Resolv can look up various DNS resources using the DNS module directly. # # Examples: # # p Gem::Resolv.getaddress "www.ruby-lang.org" # p Gem::Resolv.getname "210.251.121.214" # # Gem::Resolv::DNS.open do |dns| # ress = dns.getresources "www.ruby-lang.org", Gem::Resolv::DNS::Resource::IN::A # p ress.map(&:address) # ress = dns.getresources "ruby-lang.org", Gem::Resolv::DNS::Resource::IN::MX # p ress.map { |r| [r.exchange.to_s, r.preference] } # end # # # == Bugs # # * NIS is not supported. # * /etc/nsswitch.conf is not supported. class Gem::Resolv VERSION = "0.4.0" ## # Looks up the first IP address for +name+. def self.getaddress(name) DefaultResolver.getaddress(name) end ## # Looks up all IP address for +name+. def self.getaddresses(name) DefaultResolver.getaddresses(name) end ## # Iterates over all IP addresses for +name+. def self.each_address(name, &block) DefaultResolver.each_address(name, &block) end ## # Looks up the hostname of +address+. def self.getname(address) DefaultResolver.getname(address) end ## # Looks up all hostnames for +address+. def self.getnames(address) DefaultResolver.getnames(address) end ## # Iterates over all hostnames for +address+. def self.each_name(address, &proc) DefaultResolver.each_name(address, &proc) end ## # Creates a new Gem::Resolv using +resolvers+. def initialize(resolvers=nil, use_ipv6: nil) @resolvers = resolvers || [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(use_ipv6: use_ipv6))] end ## # Looks up the first IP address for +name+. def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("no address for #{name}") end ## # Looks up all IP address for +name+. def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end ## # Iterates over all IP addresses for +name+. def each_address(name) if AddressRegex =~ name yield name return end yielded = false @resolvers.each {|r| r.each_address(name) {|address| yield address.to_s yielded = true } return if yielded } end ## # Looks up the hostname of +address+. def getname(address) each_name(address) {|name| return name} raise ResolvError.new("no name for #{address}") end ## # Looks up all hostnames for +address+. def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end ## # Iterates over all hostnames for +address+. def each_name(address) yielded = false @resolvers.each {|r| r.each_name(address) {|name| yield name.to_s yielded = true } return if yielded } end ## # Indicates a failure to resolve a name or address. class ResolvError < StandardError; end ## # Indicates a timeout resolving a name or address. class ResolvTimeout < Gem::Timeout::Error; end ## # Gem::Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and begin require 'win32/resolv' DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL rescue LoadError end end DefaultFileName ||= '/etc/hosts' ## # Creates a new Gem::Resolv::Hosts, using +filename+ for its data source. def initialize(filename = DefaultFileName) @filename = filename @mutex = Thread::Mutex.new @initialized = nil end def lazy_initialize # :nodoc: @mutex.synchronize { unless @initialized @name2addr = {} @addr2name = {} File.open(@filename, 'rb') {|f| f.each {|line| line.sub!(/#.*/, '') addr, *hostnames = line.split(/\s+/) next unless addr (@addr2name[addr] ||= []).concat(hostnames) hostnames.each {|hostname| (@name2addr[hostname] ||= []) << addr} } } @name2addr.each {|name, arr| arr.reverse!} @initialized = true end } self end ## # Gets the IP address of +name+ from the hosts file. def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("#{@filename} has no name: #{name}") end ## # Gets all IP addresses for +name+ from the hosts file. def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end ## # Iterates over all IP addresses for +name+ retrieved from the hosts file. def each_address(name, &proc) lazy_initialize @name2addr[name]&.each(&proc) end ## # Gets the hostname of +address+ from the hosts file. def getname(address) each_name(address) {|name| return name} raise ResolvError.new("#{@filename} has no address: #{address}") end ## # Gets all hostnames for +address+ from the hosts file. def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end ## # Iterates over all hostnames for +address+ retrieved from the hosts file. def each_name(address, &proc) lazy_initialize @addr2name[address]&.each(&proc) end end ## # Gem::Resolv::DNS is a DNS stub resolver. # # Information taken from the following places: # # * STD0013 # * RFC 1035 # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters # * etc. class DNS ## # Default DNS Port Port = 53 ## # Default DNS UDP packet size UDPSize = 512 ## # Creates a new DNS resolver. See Gem::Resolv::DNS.new for argument details. # # Yields the created DNS resolver to the block, if given, otherwise # returns it. def self.open(*args) dns = new(*args) return dns unless block_given? begin yield dns ensure dns.close end end ## # Creates a new DNS resolver. # # +config_info+ can be: # # nil:: Uses /etc/resolv.conf. # String:: Path to a file using /etc/resolv.conf's format. # Hash:: Must contain :nameserver, :search and :ndots keys. # :nameserver_port can be used to specify port number of nameserver address. # :raise_timeout_errors can be used to raise timeout errors # as exceptions instead of treating the same as an NXDOMAIN response. # # The value of :nameserver should be an address string or # an array of address strings. # - :nameserver => '8.8.8.8' # - :nameserver => ['8.8.8.8', '8.8.4.4'] # # The value of :nameserver_port should be an array of # pair of nameserver address and port number. # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]] # # Example: # # Gem::Resolv::DNS.new(:nameserver => ['210.251.121.21'], # :search => ['ruby-lang.org'], # :ndots => 1) def initialize(config_info=nil) @mutex = Thread::Mutex.new @config = Config.new(config_info) @initialized = nil end # Sets the resolver timeouts. This may be a single positive number # or an array of positive numbers representing timeouts in seconds. # If an array is specified, a DNS request will retry and wait for # each successive interval in the array until a successful response # is received. Specifying +nil+ reverts to the default timeouts: # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ] # # Example: # # dns.timeouts = 3 # def timeouts=(values) @config.timeouts = values end def lazy_initialize # :nodoc: @mutex.synchronize { unless @initialized @config.lazy_initialize @initialized = true end } self end ## # Closes the DNS resolver. def close @mutex.synchronize { if @initialized @initialized = false end } end ## # Gets the IP address of +name+ from the DNS resolver. # # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved address will # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("DNS result has no information for #{name}") end ## # Gets all IP addresses for +name+ from the DNS resolver. # # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end ## # Iterates over all IP addresses for +name+ retrieved from the DNS # resolver. # # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 def each_address(name) each_resource(name, Resource::IN::A) {|resource| yield resource.address} if use_ipv6? each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address} end end def use_ipv6? # :nodoc: use_ipv6 = @config.use_ipv6? unless use_ipv6.nil? return use_ipv6 end begin list = Socket.ip_address_list rescue NotImplementedError return true end list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? } end private :use_ipv6? ## # Gets the hostname for +address+ from the DNS resolver. # # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String. Retrieved # name will be a Gem::Resolv::DNS::Name. def getname(address) each_name(address) {|name| return name} raise ResolvError.new("DNS result has no information for #{address}") end ## # Gets all hostnames for +address+ from the DNS resolver. # # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String. Retrieved # names will be Gem::Resolv::DNS::Name instances. def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end ## # Iterates over all hostnames for +address+ retrieved from the DNS # resolver. # # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String. Retrieved # names will be Gem::Resolv::DNS::Name instances. def each_name(address) case address when Name ptr = address when IPv4, IPv6 ptr = address.to_name when IPv4::Regex ptr = IPv4.create(address).to_name when IPv6::Regex ptr = IPv6.create(address).to_name else raise ResolvError.new("cannot interpret as address: #{address}") end each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name} end ## # Look up the +typeclass+ DNS resource of +name+. # # +name+ must be a Gem::Resolv::DNS::Name or a String. # # +typeclass+ should be one of the following: # # * Gem::Resolv::DNS::Resource::IN::A # * Gem::Resolv::DNS::Resource::IN::AAAA # * Gem::Resolv::DNS::Resource::IN::ANY # * Gem::Resolv::DNS::Resource::IN::CNAME # * Gem::Resolv::DNS::Resource::IN::HINFO # * Gem::Resolv::DNS::Resource::IN::MINFO # * Gem::Resolv::DNS::Resource::IN::MX # * Gem::Resolv::DNS::Resource::IN::NS # * Gem::Resolv::DNS::Resource::IN::PTR # * Gem::Resolv::DNS::Resource::IN::SOA # * Gem::Resolv::DNS::Resource::IN::TXT # * Gem::Resolv::DNS::Resource::IN::WKS # # Returned resource is represented as a Gem::Resolv::DNS::Resource instance, # i.e. Gem::Resolv::DNS::Resource::IN::A. def getresource(name, typeclass) each_resource(name, typeclass) {|resource| return resource} raise ResolvError.new("DNS result has no information for #{name}") end ## # Looks up all +typeclass+ DNS resources for +name+. See #getresource for # argument details. def getresources(name, typeclass) ret = [] each_resource(name, typeclass) {|resource| ret << resource} return ret end ## # Iterates over all +typeclass+ DNS resources for +name+. See # #getresource for argument details. def each_resource(name, typeclass, &proc) fetch_resource(name, typeclass) {|reply, reply_name| extract_resources(reply, reply_name, typeclass, &proc) } end def fetch_resource(name, typeclass) lazy_initialize begin requester = make_udp_requester rescue Errno::EACCES # fall back to TCP end senders = {} begin @config.resolv(name) {|candidate, tout, nameserver, port| requester ||= make_tcp_requester(nameserver, port) msg = Message.new msg.rd = 1 msg.add_question(candidate, typeclass) unless sender = senders[[candidate, nameserver, port]] sender = requester.sender(msg, candidate, nameserver, port) next if !sender senders[[candidate, nameserver, port]] = sender end reply, reply_name = requester.request(sender, tout) case reply.rcode when RCode::NoError if reply.tc == 1 and not Requester::TCP === requester requester.close # Retry via TCP: requester = make_tcp_requester(nameserver, port) senders = {} # This will use TCP for all remaining candidates (assuming the # current candidate does not already respond successfully via # TCP). This makes sense because we already know the full # response will not fit in an untruncated UDP packet. redo else yield(reply, reply_name) end return when RCode::NXDomain raise Config::NXDomain.new(reply_name.to_s) else raise Config::OtherResolvError.new(reply_name.to_s) end } ensure requester&.close end end def make_udp_requester # :nodoc: nameserver_port = @config.nameserver_port if nameserver_port.length == 1 Requester::ConnectedUDP.new(*nameserver_port[0]) else Requester::UnconnectedUDP.new(*nameserver_port) end end def make_tcp_requester(host, port) # :nodoc: return Requester::TCP.new(host, port) end def extract_resources(msg, name, typeclass) # :nodoc: if typeclass < Resource::ANY n0 = Name.create(name) msg.each_resource {|n, ttl, data| yield data if n0 == n } end yielded = false n0 = Name.create(name) msg.each_resource {|n, ttl, data| if n0 == n case data when typeclass yield data yielded = true when Resource::CNAME n0 = data.name end end } return if yielded msg.each_resource {|n, ttl, data| if n0 == n case data when typeclass yield data end end } end if defined? Gem::SecureRandom def self.random(arg) # :nodoc: begin Gem::SecureRandom.random_number(arg) rescue NotImplementedError rand(arg) end end else def self.random(arg) # :nodoc: rand(arg) end end RequestID = {} # :nodoc: RequestIDMutex = Thread::Mutex.new # :nodoc: def self.allocate_request_id(host, port) # :nodoc: id = nil RequestIDMutex.synchronize { h = (RequestID[[host, port]] ||= {}) begin id = random(0x0000..0xffff) end while h[id] h[id] = true } id end def self.free_request_id(host, port, id) # :nodoc: RequestIDMutex.synchronize { key = [host, port] if h = RequestID[key] h.delete id if h.empty? RequestID.delete key end end } end def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: begin port = random(1024..65535) udpsock.bind(bind_host, port) rescue Errno::EADDRINUSE, # POSIX Errno::EACCES, # SunOS: See PRIV_SYS_NFS in privileges(5) Errno::EPERM # FreeBSD: security.mac.portacl.port_high is configurable. See mac_portacl(4). retry end end class Requester # :nodoc: def initialize @senders = {} @socks = nil end def request(sender, tout) start = Process.clock_gettime(Process::CLOCK_MONOTONIC) timelimit = start + tout begin sender.send rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this Errno::ENETUNREACH raise ResolvTimeout end while true before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC) timeout = timelimit - before_select if timeout <= 0 raise ResolvTimeout end if @socks.size == 1 select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil else select_result = IO.select(@socks, nil, nil, timeout) end if !select_result after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC) next if after_select < timelimit raise ResolvTimeout end begin reply, from = recv_reply(select_result[0]) rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD Errno::ECONNRESET # Windows # No name server running on the server? # Don't wait anymore. raise ResolvTimeout end begin msg = Message.decode(reply) rescue DecodeError next # broken DNS message ignored end if sender == sender_for(from, msg) break else # unexpected DNS message ignored end end return msg, sender.data end def sender_for(addr, msg) @senders[[addr,msg.id]] end def close socks = @socks @socks = nil socks&.each(&:close) end class Sender # :nodoc: def initialize(msg, data, sock) @msg = msg @data = data @sock = sock end end class UnconnectedUDP < Requester # :nodoc: def initialize(*nameserver_port) super() @nameserver_port = nameserver_port @initialized = false @mutex = Thread::Mutex.new end def lazy_initialize @mutex.synchronize { next if @initialized @initialized = true @socks_hash = {} @socks = [] @nameserver_port.each {|host, port| if host.index(':') bind_host = "::" af = Socket::AF_INET6 else bind_host = "0.0.0.0" af = Socket::AF_INET end next if @socks_hash[bind_host] begin sock = UDPSocket.new(af) rescue Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT next # The kernel doesn't support the address family. end @socks << sock @socks_hash[bind_host] = sock sock.do_not_reverse_lookup = true DNS.bind_random_port(sock, bind_host) } } self end def recv_reply(readable_socks) lazy_initialize reply, from = readable_socks[0].recvfrom(UDPSize) return reply, [from[3],from[1]] end def sender(msg, data, host, port=Port) host = Addrinfo.ip(host).ip_address lazy_initialize sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"] return nil if !sock service = [host, port] id = DNS.allocate_request_id(host, port) request = msg.encode request[0,2] = [id].pack('n') return @senders[[service, id]] = Sender.new(request, data, sock, host, port) end def close @mutex.synchronize { if @initialized super @senders.each_key {|service, id| DNS.free_request_id(service[0], service[1], id) } @initialized = false end } end class Sender < Requester::Sender # :nodoc: def initialize(msg, data, sock, host, port) super(msg, data, sock) @host = host @port = port end attr_reader :data def send raise "@sock is nil." if @sock.nil? @sock.send(@msg, 0, @host, @port) end end end class ConnectedUDP < Requester # :nodoc: def initialize(host, port=Port) super() @host = host @port = port @mutex = Thread::Mutex.new @initialized = false end def lazy_initialize @mutex.synchronize { next if @initialized @initialized = true is_ipv6 = @host.index(':') sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET) @socks = [sock] sock.do_not_reverse_lookup = true DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0") sock.connect(@host, @port) } self end def recv_reply(readable_socks) lazy_initialize reply = readable_socks[0].recv(UDPSize) return reply, nil end def sender(msg, data, host=@host, port=@port) lazy_initialize unless host == @host && port == @port raise RequestError.new("host/port don't match: #{host}:#{port}") end id = DNS.allocate_request_id(@host, @port) request = msg.encode request[0,2] = [id].pack('n') return @senders[[nil,id]] = Sender.new(request, data, @socks[0]) end def close @mutex.synchronize do if @initialized super @senders.each_key {|from, id| DNS.free_request_id(@host, @port, id) } @initialized = false end end end class Sender < Requester::Sender # :nodoc: def send raise "@sock is nil." if @sock.nil? @sock.send(@msg, 0) end attr_reader :data end end class MDNSOneShot < UnconnectedUDP # :nodoc: def sender(msg, data, host, port=Port) lazy_initialize id = DNS.allocate_request_id(host, port) request = msg.encode request[0,2] = [id].pack('n') sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"] return @senders[id] = UnconnectedUDP::Sender.new(request, data, sock, host, port) end def sender_for(addr, msg) lazy_initialize @senders[msg.id] end end class TCP < Requester # :nodoc: def initialize(host, port=Port) super() @host = host @port = port sock = TCPSocket.new(@host, @port) @socks = [sock] @senders = {} end def recv_reply(readable_socks) len = readable_socks[0].read(2).unpack('n')[0] reply = @socks[0].read(len) return reply, nil end def sender(msg, data, host=@host, port=@port) unless host == @host && port == @port raise RequestError.new("host/port don't match: #{host}:#{port}") end id = DNS.allocate_request_id(@host, @port) request = msg.encode request[0,2] = [request.length, id].pack('nn') return @senders[[nil,id]] = Sender.new(request, data, @socks[0]) end class Sender < Requester::Sender # :nodoc: def send @sock.print(@msg) @sock.flush end attr_reader :data end def close super @senders.each_key {|from,id| DNS.free_request_id(@host, @port, id) } end end ## # Indicates a problem with the DNS request. class RequestError < StandardError end end class Config # :nodoc: def initialize(config_info=nil) @mutex = Thread::Mutex.new @config_info = config_info @initialized = nil @timeouts = nil end def timeouts=(values) if values values = Array(values) values.each do |t| Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric" t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive" end @timeouts = values else @timeouts = nil end end def Config.parse_resolv_conf(filename) nameserver = [] search = nil ndots = 1 File.open(filename, 'rb') {|f| f.each {|line| line.sub!(/[#;].*/, '') keyword, *args = line.split(/\s+/) next unless keyword case keyword when 'nameserver' nameserver.concat(args) when 'domain' next if args.empty? search = [args[0]] when 'search' next if args.empty? search = args when 'options' args.each {|arg| case arg when /\Andots:(\d+)\z/ ndots = $1.to_i end } end } } return { :nameserver => nameserver, :search => search, :ndots => ndots } end def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename config_hash = Config.parse_resolv_conf(filename) else if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM require 'win32/resolv' search, nameserver = Win32::Resolv.get_resolv_info config_hash = {} config_hash[:nameserver] = nameserver if nameserver config_hash[:search] = [search].flatten if search end end config_hash || {} end def lazy_initialize @mutex.synchronize { unless @initialized @nameserver_port = [] @use_ipv6 = nil @search = nil @ndots = 1 case @config_info when nil config_hash = Config.default_config_hash when String config_hash = Config.parse_resolv_conf(@config_info) when Hash config_hash = @config_info.dup if String === config_hash[:nameserver] config_hash[:nameserver] = [config_hash[:nameserver]] end if String === config_hash[:search] config_hash[:search] = [config_hash[:search]] end else raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}") end if config_hash.include? :nameserver @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] } end if config_hash.include? :nameserver_port @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] } end if config_hash.include? :use_ipv6 @use_ipv6 = config_hash[:use_ipv6] end @search = config_hash[:search] if config_hash.include? :search @ndots = config_hash[:ndots] if config_hash.include? :ndots @raise_timeout_errors = config_hash[:raise_timeout_errors] if @nameserver_port.empty? @nameserver_port << ['0.0.0.0', Port] end if @search @search = @search.map {|arg| Label.split(arg) } else hostname = Socket.gethostname if /\./ =~ hostname @search = [Label.split($')] else @search = [[]] end end if !@nameserver_port.kind_of?(Array) || @nameserver_port.any? {|ns_port| !(Array === ns_port) || ns_port.length != 2 !(String === ns_port[0]) || !(Integer === ns_port[1]) } raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}") end if !@search.kind_of?(Array) || !@search.all? {|ls| ls.all? {|l| Label::Str === l } } raise ArgumentError.new("invalid search config: #{@search.inspect}") end if !@ndots.kind_of?(Integer) raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}") end @initialized = true end } self end def single? lazy_initialize if @nameserver_port.length == 1 return @nameserver_port[0] else return nil end end def nameserver_port @nameserver_port end def use_ipv6? @use_ipv6 end def generate_candidates(name) candidates = nil name = Name.create(name) if name.absolute? candidates = [name] else if @ndots <= name.length - 1 candidates = [Name.new(name.to_a)] else candidates = [] end candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)}) fname = Name.create("#{name}.") if !candidates.include?(fname) candidates << fname end end return candidates end InitialTimeout = 5 def generate_timeouts ts = [InitialTimeout] ts << ts[-1] * 2 / @nameserver_port.length ts << ts[-1] * 2 ts << ts[-1] * 2 return ts end def resolv(name) candidates = generate_candidates(name) timeouts = @timeouts || generate_timeouts timeout_error = false begin candidates.each {|candidate| begin timeouts.each {|tout| @nameserver_port.each {|nameserver, port| begin yield candidate, tout, nameserver, port rescue ResolvTimeout end } } timeout_error = true raise ResolvError.new("DNS resolv timeout: #{name}") rescue NXDomain end } rescue ResolvError raise if @raise_timeout_errors && timeout_error end end ## # Indicates no such domain was found. class NXDomain < ResolvError end ## # Indicates some other unhandled resolver error was encountered. class OtherResolvError < ResolvError end end module OpCode # :nodoc: Query = 0 IQuery = 1 Status = 2 Notify = 4 Update = 5 end module RCode # :nodoc: NoError = 0 FormErr = 1 ServFail = 2 NXDomain = 3 NotImp = 4 Refused = 5 YXDomain = 6 YXRRSet = 7 NXRRSet = 8 NotAuth = 9 NotZone = 10 BADVERS = 16 BADSIG = 16 BADKEY = 17 BADTIME = 18 BADMODE = 19 BADNAME = 20 BADALG = 21 end ## # Indicates that the DNS response was unable to be decoded. class DecodeError < StandardError end ## # Indicates that the DNS request was unable to be encoded. class EncodeError < StandardError end module Label # :nodoc: def self.split(arg) labels = [] arg.scan(/[^\.]+/) {labels << Str.new($&)} return labels end class Str # :nodoc: def initialize(string) @string = string # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343] # This assumes @string is given in ASCII compatible encoding. @downcase = string.b.downcase end attr_reader :string, :downcase def to_s return @string end def inspect return "#<#{self.class} #{self}>" end def ==(other) return self.class == other.class && @downcase == other.downcase end def eql?(other) return self == other end def hash return @downcase.hash end end end ## # A representation of a DNS name. class Name ## # Creates a new DNS name from +arg+. +arg+ can be: # # Name:: returns +arg+. # String:: Creates a new Name. def self.create(arg) case arg when Name return arg when String return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false) else raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}") end end def initialize(labels, absolute=true) # :nodoc: labels = labels.map {|label| case label when String then Label::Str.new(label) when Label::Str then label else raise ArgumentError, "unexpected label: #{label.inspect}" end } @labels = labels @absolute = absolute end def inspect # :nodoc: "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>" end ## # True if this name is absolute. def absolute? return @absolute end def ==(other) # :nodoc: return false unless Name === other return false unless @absolute == other.absolute? return @labels == other.to_a end alias eql? == # :nodoc: ## # Returns true if +other+ is a subdomain. # # Example: # # domain = Gem::Resolv::DNS::Name.create("y.z") # p Gem::Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true # p Gem::Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true # p Gem::Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false # p Gem::Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false # p Gem::Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false # p Gem::Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false # def subdomain_of?(other) raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other return false if @absolute != other.absolute? other_len = other.length return false if @labels.length <= other_len return @labels[-other_len, other_len] == other.to_a end def hash # :nodoc: return @labels.hash ^ @absolute.hash end def to_a # :nodoc: return @labels end def length # :nodoc: return @labels.length end def [](i) # :nodoc: return @labels[i] end ## # returns the domain name as a string. # # The domain name doesn't have a trailing dot even if the name object is # absolute. # # Example: # # p Gem::Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z" # p Gem::Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z" def to_s return @labels.join('.') end end class Message # :nodoc: @@identifier = -1 def initialize(id = (@@identifier += 1) & 0xffff) @id = id @qr = 0 @opcode = 0 @aa = 0 @tc = 0 @rd = 0 # recursion desired @ra = 0 # recursion available @rcode = 0 @question = [] @answer = [] @authority = [] @additional = [] end attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode attr_reader :question, :answer, :authority, :additional def ==(other) return @id == other.id && @qr == other.qr && @opcode == other.opcode && @aa == other.aa && @tc == other.tc && @rd == other.rd && @ra == other.ra && @rcode == other.rcode && @question == other.question && @answer == other.answer && @authority == other.authority && @additional == other.additional end def add_question(name, typeclass) @question << [Name.create(name), typeclass] end def each_question @question.each {|name, typeclass| yield name, typeclass } end def add_answer(name, ttl, data) @answer << [Name.create(name), ttl, data] end def each_answer @answer.each {|name, ttl, data| yield name, ttl, data } end def add_authority(name, ttl, data) @authority << [Name.create(name), ttl, data] end def each_authority @authority.each {|name, ttl, data| yield name, ttl, data } end def add_additional(name, ttl, data) @additional << [Name.create(name), ttl, data] end def each_additional @additional.each {|name, ttl, data| yield name, ttl, data } end def each_resource each_answer {|name, ttl, data| yield name, ttl, data} each_authority {|name, ttl, data| yield name, ttl, data} each_additional {|name, ttl, data| yield name, ttl, data} end def encode return MessageEncoder.new {|msg| msg.put_pack('nnnnnn', @id, (@qr & 1) << 15 | (@opcode & 15) << 11 | (@aa & 1) << 10 | (@tc & 1) << 9 | (@rd & 1) << 8 | (@ra & 1) << 7 | (@rcode & 15), @question.length, @answer.length, @authority.length, @additional.length) @question.each {|q| name, typeclass = q msg.put_name(name) msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue) } [@answer, @authority, @additional].each {|rr| rr.each {|r| name, ttl, data = r msg.put_name(name) msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl) msg.put_length16 {data.encode_rdata(msg)} } } }.to_s end class MessageEncoder # :nodoc: def initialize @data = ''.dup @names = {} yield self end def to_s return @data end def put_bytes(d) @data << d end def put_pack(template, *d) @data << d.pack(template) end def put_length16 length_index = @data.length @data << "\0\0" data_start = @data.length yield data_end = @data.length @data[length_index, 2] = [data_end - data_start].pack("n") end def put_string(d) self.put_pack("C", d.length) @data << d end def put_string_list(ds) ds.each {|d| self.put_string(d) } end def put_name(d, compress: true) put_labels(d.to_a, compress: compress) end def put_labels(d, compress: true) d.each_index {|i| domain = d[i..-1] if compress && idx = @names[domain] self.put_pack("n", 0xc000 | idx) return else if @data.length < 0x4000 @names[domain] = @data.length end self.put_label(d[i]) end } @data << "\0" end def put_label(d) self.put_string(d.to_s) end end def Message.decode(m) o = Message.new(0) MessageDecoder.new(m) {|msg| id, flag, qdcount, ancount, nscount, arcount = msg.get_unpack('nnnnnn') o.id = id o.tc = (flag >> 9) & 1 o.rcode = flag & 15 return o unless o.tc.zero? o.qr = (flag >> 15) & 1 o.opcode = (flag >> 11) & 15 o.aa = (flag >> 10) & 1 o.rd = (flag >> 8) & 1 o.ra = (flag >> 7) & 1 (1..qdcount).each { name, typeclass = msg.get_question o.add_question(name, typeclass) } (1..ancount).each { name, ttl, data = msg.get_rr o.add_answer(name, ttl, data) } (1..nscount).each { name, ttl, data = msg.get_rr o.add_authority(name, ttl, data) } (1..arcount).each { name, ttl, data = msg.get_rr o.add_additional(name, ttl, data) } } return o end class MessageDecoder # :nodoc: def initialize(data) @data = data @index = 0 @limit = data.bytesize yield self end def inspect "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>" end def get_length16 len, = self.get_unpack('n') save_limit = @limit @limit = @index + len d = yield(len) if @index < @limit raise DecodeError.new("junk exists") elsif @limit < @index raise DecodeError.new("limit exceeded") end @limit = save_limit return d end def get_bytes(len = @limit - @index) raise DecodeError.new("limit exceeded") if @limit < @index + len d = @data.byteslice(@index, len) @index += len return d end def get_unpack(template) len = 0 template.each_byte {|byte| byte = "%c" % byte case byte when ?c, ?C len += 1 when ?n len += 2 when ?N len += 4 else raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'") end } raise DecodeError.new("limit exceeded") if @limit < @index + len arr = @data.unpack("@#{@index}#{template}") @index += len return arr end def get_string raise DecodeError.new("limit exceeded") if @limit <= @index len = @data.getbyte(@index) raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len d = @data.byteslice(@index + 1, len) @index += 1 + len return d end def get_string_list strings = [] while @index < @limit strings << self.get_string end strings end def get_list [].tap do |values| while @index < @limit values << yield end end end def get_name return Name.new(self.get_labels) end def get_labels prev_index = @index save_index = nil d = [] while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) when 0 @index += 1 if save_index @index = save_index end return d when 192..255 idx = self.get_unpack('n')[0] & 0x3fff if prev_index <= idx raise DecodeError.new("non-backward name pointer") end prev_index = idx if !save_index save_index = @index end @index = idx else d << self.get_label end end end def get_label return Label::Str.new(self.get_string) end def get_question name = self.get_name type, klass = self.get_unpack("nn") return name, Resource.get_class(type, klass) end def get_rr name = self.get_name type, klass, ttl = self.get_unpack('nnN') typeclass = Resource.get_class(type, klass) res = self.get_length16 do begin typeclass.decode_rdata self rescue => e raise DecodeError, e.message, e.backtrace end end res.instance_variable_set :@ttl, ttl return name, ttl, res end end end ## # SvcParams for service binding RRs. [RFC9460] class SvcParams include Enumerable ## # Create a list of SvcParams with the given initial content. # # +params+ has to be an enumerable of +SvcParam+s. # If its content has +SvcParam+s with the duplicate key, # the one appears last takes precedence. def initialize(params = []) @params = {} params.each do |param| add param end end ## # Get SvcParam for the given +key+ in this list. def [](key) @params[canonical_key(key)] end ## # Get the number of SvcParams in this list. def count @params.count end ## # Get whether this list is empty. def empty? @params.empty? end ## # Add the SvcParam +param+ to this list, overwriting the existing one with the same key. def add(param) @params[param.class.key_number] = param end ## # Remove the +SvcParam+ with the given +key+ and return it. def delete(key) @params.delete(canonical_key(key)) end ## # Enumerate the +SvcParam+s in this list. def each(&block) return enum_for(:each) unless block @params.each_value(&block) end def encode(msg) # :nodoc: @params.keys.sort.each do |key| msg.put_pack('n', key) msg.put_length16 do @params.fetch(key).encode(msg) end end end def self.decode(msg) # :nodoc: params = msg.get_list do key, = msg.get_unpack('n') msg.get_length16 do SvcParam::ClassHash[key].decode(msg) end end return self.new(params) end private def canonical_key(key) # :nodoc: case key when Integer key when /\Akey(\d+)\z/ Integer($1) when Symbol SvcParam::ClassHash[key].key_number else raise TypeError, 'key must be either String or Symbol' end end end ## # Base class for SvcParam. [RFC9460] class SvcParam ## # Get the presentation name of the SvcParamKey. def self.key_name const_get(:KeyName) end ## # Get the registered number of the SvcParamKey. def self.key_number const_get(:KeyNumber) end ClassHash = Hash.new do |h, key| # :nodoc: case key when Integer Generic.create(key) when /\Akey(?\d+)\z/ Generic.create(key.to_int) when Symbol raise KeyError, "unknown key #{key}" else raise TypeError, 'key must be either String or Symbol' end end ## # Generic SvcParam abstract class. class Generic < SvcParam ## # SvcParamValue in wire-format byte string. attr_reader :value ## # Create generic SvcParam def initialize(value) @value = value end def encode(msg) # :nodoc: msg.put_bytes(@value) end def self.decode(msg) # :nodoc: return self.new(msg.get_bytes) end def self.create(key_number) c = Class.new(Generic) key_name = :"key#{key_number}" c.const_set(:KeyName, key_name) c.const_set(:KeyNumber, key_number) self.const_set(:"Key#{key_number}", c) ClassHash[key_name] = ClassHash[key_number] = c return c end end ## # "mandatory" SvcParam -- Mandatory keys in service binding RR class Mandatory < SvcParam KeyName = :mandatory KeyNumber = 0 ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc: ## # Mandatory keys. attr_reader :keys ## # Initialize "mandatory" ScvParam. def initialize(keys) @keys = keys.map(&:to_int) end def encode(msg) # :nodoc: @keys.sort.each do |key| msg.put_pack('n', key) end end def self.decode(msg) # :nodoc: keys = msg.get_list { msg.get_unpack('n')[0] } return self.new(keys) end end ## # "alpn" SvcParam -- Additional supported protocols class ALPN < SvcParam KeyName = :alpn KeyNumber = 1 ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc: ## # Supported protocol IDs. attr_reader :protocol_ids ## # Initialize "alpn" ScvParam. def initialize(protocol_ids) @protocol_ids = protocol_ids.map(&:to_str) end def encode(msg) # :nodoc: msg.put_string_list(@protocol_ids) end def self.decode(msg) # :nodoc: return self.new(msg.get_string_list) end end ## # "no-default-alpn" SvcParam -- No support for default protocol class NoDefaultALPN < SvcParam KeyName = :'no-default-alpn' KeyNumber = 2 ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc: def encode(msg) # :nodoc: # no payload end def self.decode(msg) # :nodoc: return self.new end end ## # "port" SvcParam -- Port for alternative endpoint class Port < SvcParam KeyName = :port KeyNumber = 3 ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc: ## # Port number. attr_reader :port ## # Initialize "port" ScvParam. def initialize(port) @port = port.to_int end def encode(msg) # :nodoc: msg.put_pack('n', @port) end def self.decode(msg) # :nodoc: port, = msg.get_unpack('n') return self.new(port) end end ## # "ipv4hint" SvcParam -- IPv4 address hints class IPv4Hint < SvcParam KeyName = :ipv4hint KeyNumber = 4 ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc: ## # Set of IPv4 addresses. attr_reader :addresses ## # Initialize "ipv4hint" ScvParam. def initialize(addresses) @addresses = addresses.map {|address| IPv4.create(address) } end def encode(msg) # :nodoc: @addresses.each do |address| msg.put_bytes(address.address) end end def self.decode(msg) # :nodoc: addresses = msg.get_list { IPv4.new(msg.get_bytes(4)) } return self.new(addresses) end end ## # "ipv6hint" SvcParam -- IPv6 address hints class IPv6Hint < SvcParam KeyName = :ipv6hint KeyNumber = 6 ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc: ## # Set of IPv6 addresses. attr_reader :addresses ## # Initialize "ipv6hint" ScvParam. def initialize(addresses) @addresses = addresses.map {|address| IPv6.create(address) } end def encode(msg) # :nodoc: @addresses.each do |address| msg.put_bytes(address.address) end end def self.decode(msg) # :nodoc: addresses = msg.get_list { IPv6.new(msg.get_bytes(16)) } return self.new(addresses) end end ## # "dohpath" SvcParam -- DNS over HTTPS path template [RFC9461] class DoHPath < SvcParam KeyName = :dohpath KeyNumber = 7 ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc: ## # URI template for DoH queries. attr_reader :template ## # Initialize "dohpath" ScvParam. def initialize(template) @template = template.encode('utf-8') end def encode(msg) # :nodoc: msg.put_bytes(@template) end def self.decode(msg) # :nodoc: template = msg.get_bytes.force_encoding('utf-8') return self.new(template) end end end ## # A DNS query abstract class. class Query def encode_rdata(msg) # :nodoc: raise EncodeError.new("#{self.class} is query.") end def self.decode_rdata(msg) # :nodoc: raise DecodeError.new("#{self.class} is query.") end end ## # A DNS resource abstract class. class Resource < Query ## # Remaining Time To Live for this Resource. attr_reader :ttl ClassHash = {} # :nodoc: def encode_rdata(msg) # :nodoc: raise NotImplementedError.new end def self.decode_rdata(msg) # :nodoc: raise NotImplementedError.new end def ==(other) # :nodoc: return false unless self.class == other.class s_ivars = self.instance_variables s_ivars.sort! s_ivars.delete :@ttl o_ivars = other.instance_variables o_ivars.sort! o_ivars.delete :@ttl return s_ivars == o_ivars && s_ivars.collect {|name| self.instance_variable_get name} == o_ivars.collect {|name| other.instance_variable_get name} end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: h = 0 vars = self.instance_variables vars.delete :@ttl vars.each {|name| h ^= self.instance_variable_get(name).hash } return h end def self.get_class(type_value, class_value) # :nodoc: return ClassHash[[type_value, class_value]] || Generic.create(type_value, class_value) end ## # A generic resource abstract class. class Generic < Resource ## # Creates a new generic resource. def initialize(data) @data = data end ## # Data for this generic resource. attr_reader :data def encode_rdata(msg) # :nodoc: msg.put_bytes(data) end def self.decode_rdata(msg) # :nodoc: return self.new(msg.get_bytes) end def self.create(type_value, class_value) # :nodoc: c = Class.new(Generic) c.const_set(:TypeValue, type_value) c.const_set(:ClassValue, class_value) Generic.const_set("Type#{type_value}_Class#{class_value}", c) ClassHash[[type_value, class_value]] = c return c end end ## # Domain Name resource abstract class. class DomainName < Resource ## # Creates a new DomainName from +name+. def initialize(name) @name = name end ## # The name of this DomainName. attr_reader :name def encode_rdata(msg) # :nodoc: msg.put_name(@name) end def self.decode_rdata(msg) # :nodoc: return self.new(msg.get_name) end end # Standard (class generic) RRs ClassValue = nil # :nodoc: ## # An authoritative name server. class NS < DomainName TypeValue = 2 # :nodoc: end ## # The canonical name for an alias. class CNAME < DomainName TypeValue = 5 # :nodoc: end ## # Start Of Authority resource. class SOA < Resource TypeValue = 6 # :nodoc: ## # Creates a new SOA record. See the attr documentation for the # details of each argument. def initialize(mname, rname, serial, refresh, retry_, expire, minimum) @mname = mname @rname = rname @serial = serial @refresh = refresh @retry = retry_ @expire = expire @minimum = minimum end ## # Name of the host where the master zone file for this zone resides. attr_reader :mname ## # The person responsible for this domain name. attr_reader :rname ## # The version number of the zone file. attr_reader :serial ## # How often, in seconds, a secondary name server is to check for # updates from the primary name server. attr_reader :refresh ## # How often, in seconds, a secondary name server is to retry after a # failure to check for a refresh. attr_reader :retry ## # Time in seconds that a secondary name server is to use the data # before refreshing from the primary name server. attr_reader :expire ## # The minimum number of seconds to be used for TTL values in RRs. attr_reader :minimum def encode_rdata(msg) # :nodoc: msg.put_name(@mname) msg.put_name(@rname) msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum) end def self.decode_rdata(msg) # :nodoc: mname = msg.get_name rname = msg.get_name serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN') return self.new( mname, rname, serial, refresh, retry_, expire, minimum) end end ## # A Pointer to another DNS name. class PTR < DomainName TypeValue = 12 # :nodoc: end ## # Host Information resource. class HINFO < Resource TypeValue = 13 # :nodoc: ## # Creates a new HINFO running +os+ on +cpu+. def initialize(cpu, os) @cpu = cpu @os = os end ## # CPU architecture for this resource. attr_reader :cpu ## # Operating system for this resource. attr_reader :os def encode_rdata(msg) # :nodoc: msg.put_string(@cpu) msg.put_string(@os) end def self.decode_rdata(msg) # :nodoc: cpu = msg.get_string os = msg.get_string return self.new(cpu, os) end end ## # Mailing list or mailbox information. class MINFO < Resource TypeValue = 14 # :nodoc: def initialize(rmailbx, emailbx) @rmailbx = rmailbx @emailbx = emailbx end ## # Domain name responsible for this mail list or mailbox. attr_reader :rmailbx ## # Mailbox to use for error messages related to the mail list or mailbox. attr_reader :emailbx def encode_rdata(msg) # :nodoc: msg.put_name(@rmailbx) msg.put_name(@emailbx) end def self.decode_rdata(msg) # :nodoc: rmailbx = msg.get_string emailbx = msg.get_string return self.new(rmailbx, emailbx) end end ## # Mail Exchanger resource. class MX < Resource TypeValue= 15 # :nodoc: ## # Creates a new MX record with +preference+, accepting mail at # +exchange+. def initialize(preference, exchange) @preference = preference @exchange = exchange end ## # The preference for this MX. attr_reader :preference ## # The host of this MX. attr_reader :exchange def encode_rdata(msg) # :nodoc: msg.put_pack('n', @preference) msg.put_name(@exchange) end def self.decode_rdata(msg) # :nodoc: preference, = msg.get_unpack('n') exchange = msg.get_name return self.new(preference, exchange) end end ## # Unstructured text resource. class TXT < Resource TypeValue = 16 # :nodoc: def initialize(first_string, *rest_strings) @strings = [first_string, *rest_strings] end ## # Returns an Array of Strings for this TXT record. attr_reader :strings ## # Returns the concatenated string from +strings+. def data @strings.join("") end def encode_rdata(msg) # :nodoc: msg.put_string_list(@strings) end def self.decode_rdata(msg) # :nodoc: strings = msg.get_string_list return self.new(*strings) end end ## # Location resource class LOC < Resource TypeValue = 29 # :nodoc: def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude) @version = version @ssize = Gem::Resolv::LOC::Size.create(ssize) @hprecision = Gem::Resolv::LOC::Size.create(hprecision) @vprecision = Gem::Resolv::LOC::Size.create(vprecision) @latitude = Gem::Resolv::LOC::Coord.create(latitude) @longitude = Gem::Resolv::LOC::Coord.create(longitude) @altitude = Gem::Resolv::LOC::Alt.create(altitude) end ## # Returns the version value for this LOC record which should always be 00 attr_reader :version ## # The spherical size of this LOC # in meters using scientific notation as 2 integers of XeY attr_reader :ssize ## # The horizontal precision using ssize type values # in meters using scientific notation as 2 integers of XeY # for precision use value/2 e.g. 2m = +/-1m attr_reader :hprecision ## # The vertical precision using ssize type values # in meters using scientific notation as 2 integers of XeY # for precision use value/2 e.g. 2m = +/-1m attr_reader :vprecision ## # The latitude for this LOC where 2**31 is the equator # in thousandths of an arc second as an unsigned 32bit integer attr_reader :latitude ## # The longitude for this LOC where 2**31 is the prime meridian # in thousandths of an arc second as an unsigned 32bit integer attr_reader :longitude ## # The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid # in centimeters as an unsigned 32bit integer attr_reader :altitude def encode_rdata(msg) # :nodoc: msg.put_bytes(@version) msg.put_bytes(@ssize.scalar) msg.put_bytes(@hprecision.scalar) msg.put_bytes(@vprecision.scalar) msg.put_bytes(@latitude.coordinates) msg.put_bytes(@longitude.coordinates) msg.put_bytes(@altitude.altitude) end def self.decode_rdata(msg) # :nodoc: version = msg.get_bytes(1) ssize = msg.get_bytes(1) hprecision = msg.get_bytes(1) vprecision = msg.get_bytes(1) latitude = msg.get_bytes(4) longitude = msg.get_bytes(4) altitude = msg.get_bytes(4) return self.new( version, Gem::Resolv::LOC::Size.new(ssize), Gem::Resolv::LOC::Size.new(hprecision), Gem::Resolv::LOC::Size.new(vprecision), Gem::Resolv::LOC::Coord.new(latitude,"lat"), Gem::Resolv::LOC::Coord.new(longitude,"lon"), Gem::Resolv::LOC::Alt.new(altitude) ) end end ## # A Query type requesting any RR. class ANY < Query TypeValue = 255 # :nodoc: end ## # CAA resource record defined in RFC 8659 # # These records identify certificate authority allowed to issue # certificates for the given domain. class CAA < Resource TypeValue = 257 ## # Creates a new CAA for +flags+, +tag+ and +value+. def initialize(flags, tag, value) unless (0..255) === flags raise ArgumentError.new('flags must be an Integer between 0 and 255') end unless (1..15) === tag.bytesize raise ArgumentError.new('length of tag must be between 1 and 15') end @flags = flags @tag = tag @value = value end ## # Flags for this proprty: # - Bit 0 : 0 = not critical, 1 = critical attr_reader :flags ## # Property tag ("issue", "issuewild", "iodef"...). attr_reader :tag ## # Property value. attr_reader :value ## # Whether the critical flag is set on this property. def critical? flags & 0x80 != 0 end def encode_rdata(msg) # :nodoc: msg.put_pack('C', @flags) msg.put_string(@tag) msg.put_bytes(@value) end def self.decode_rdata(msg) # :nodoc: flags, = msg.get_unpack('C') tag = msg.get_string value = msg.get_bytes self.new flags, tag, value end end ClassInsensitiveTypes = [ # :nodoc: NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY, CAA ] ## # module IN contains ARPA Internet specific RRs. module IN ClassValue = 1 # :nodoc: ClassInsensitiveTypes.each {|s| c = Class.new(s) c.const_set(:TypeValue, s::TypeValue) c.const_set(:ClassValue, ClassValue) ClassHash[[s::TypeValue, ClassValue]] = c self.const_set(s.name.sub(/.*::/, ''), c) } ## # IPv4 Address resource class A < Resource TypeValue = 1 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: ## # Creates a new A for +address+. def initialize(address) @address = IPv4.create(address) end ## # The Gem::Resolv::IPv4 address for this A. attr_reader :address def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) end def self.decode_rdata(msg) # :nodoc: return self.new(IPv4.new(msg.get_bytes(4))) end end ## # Well Known Service resource. class WKS < Resource TypeValue = 11 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: def initialize(address, protocol, bitmap) @address = IPv4.create(address) @protocol = protocol @bitmap = bitmap end ## # The host these services run on. attr_reader :address ## # IP protocol number for these services. attr_reader :protocol ## # A bit map of enabled services on this host. # # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP # service (port 25). If this bit is set, then an SMTP server should # be listening on TCP port 25; if zero, SMTP service is not # supported. attr_reader :bitmap def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) msg.put_pack("n", @protocol) msg.put_bytes(@bitmap) end def self.decode_rdata(msg) # :nodoc: address = IPv4.new(msg.get_bytes(4)) protocol, = msg.get_unpack("n") bitmap = msg.get_bytes return self.new(address, protocol, bitmap) end end ## # An IPv6 address record. class AAAA < Resource TypeValue = 28 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: ## # Creates a new AAAA for +address+. def initialize(address) @address = IPv6.create(address) end ## # The Gem::Resolv::IPv6 address for this AAAA. attr_reader :address def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) end def self.decode_rdata(msg) # :nodoc: return self.new(IPv6.new(msg.get_bytes(16))) end end ## # SRV resource record defined in RFC 2782 # # These records identify the hostname and port that a service is # available at. class SRV < Resource TypeValue = 33 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: # Create a SRV resource record. # # See the documentation for #priority, #weight, #port and #target # for +priority+, +weight+, +port and +target+ respectively. def initialize(priority, weight, port, target) @priority = priority.to_int @weight = weight.to_int @port = port.to_int @target = Name.create(target) end # The priority of this target host. # # A client MUST attempt to contact the target host with the # lowest-numbered priority it can reach; target hosts with the same # priority SHOULD be tried in an order defined by the weight field. # The range is 0-65535. Note that it is not widely implemented and # should be set to zero. attr_reader :priority # A server selection mechanism. # # The weight field specifies a relative weight for entries with the # same priority. Larger weights SHOULD be given a proportionately # higher probability of being selected. The range of this number is # 0-65535. Domain administrators SHOULD use Weight 0 when there # isn't any server selection to do, to make the RR easier to read # for humans (less noisy). Note that it is not widely implemented # and should be set to zero. attr_reader :weight # The port on this target host of this service. # # The range is 0-65535. attr_reader :port # The domain name of the target host. # # A target of "." means that the service is decidedly not available # at this domain. attr_reader :target def encode_rdata(msg) # :nodoc: msg.put_pack("n", @priority) msg.put_pack("n", @weight) msg.put_pack("n", @port) msg.put_name(@target, compress: false) end def self.decode_rdata(msg) # :nodoc: priority, = msg.get_unpack("n") weight, = msg.get_unpack("n") port, = msg.get_unpack("n") target = msg.get_name return self.new(priority, weight, port, target) end end ## # Common implementation for SVCB-compatible resource records. class ServiceBinding ## # Create a service binding resource record. def initialize(priority, target, params = []) @priority = priority.to_int @target = Name.create(target) @params = SvcParams.new(params) end ## # The priority of this target host. # # The range is 0-65535. # If set to 0, this RR is in AliasMode. Otherwise, it is in ServiceMode. attr_reader :priority ## # The domain name of the target host. attr_reader :target ## # The service parameters for the target host. attr_reader :params ## # Whether this RR is in AliasMode. def alias_mode? self.priority == 0 end ## # Whether this RR is in ServiceMode. def service_mode? !alias_mode? end def encode_rdata(msg) # :nodoc: msg.put_pack("n", @priority) msg.put_name(@target, compress: false) @params.encode(msg) end def self.decode_rdata(msg) # :nodoc: priority, = msg.get_unpack("n") target = msg.get_name params = SvcParams.decode(msg) return self.new(priority, target, params) end end ## # SVCB resource record [RFC9460] class SVCB < ServiceBinding TypeValue = 64 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: end ## # HTTPS resource record [RFC9460] class HTTPS < ServiceBinding TypeValue = 65 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: end end end end ## # A Gem::Resolv::DNS IPv4 address. class IPv4 ## # Regular expression IPv4 addresses must match. Regex256 = /0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? |[3-9][0-9]?/x Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/ def self.create(arg) case arg when IPv4 return arg when Regex if (0..255) === (a = $1.to_i) && (0..255) === (b = $2.to_i) && (0..255) === (c = $3.to_i) && (0..255) === (d = $4.to_i) return self.new([a, b, c, d].pack("CCCC")) else raise ArgumentError.new("IPv4 address with invalid value: " + arg) end else raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}") end end def initialize(address) # :nodoc: unless address.kind_of?(String) raise ArgumentError, 'IPv4 address must be a string' end unless address.length == 4 raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes" end @address = address end ## # A String representation of this IPv4 address. ## # The raw IPv4 address as a String. attr_reader :address def to_s # :nodoc: return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC")) end def inspect # :nodoc: return "#<#{self.class} #{self}>" end ## # Turns this IPv4 address into a Gem::Resolv::DNS::Name. def to_name return DNS::Name.create( '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse) end def ==(other) # :nodoc: return @address == other.address end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @address.hash end end ## # A Gem::Resolv::DNS IPv6 address. class IPv6 ## # IPv6 address format a:b:c:d:e:f:g:h Regex_8Hex = /\A (?:[0-9A-Fa-f]{1,4}:){7} [0-9A-Fa-f]{1,4} \z/x ## # Compressed IPv6 address format a::b Regex_CompressedHex = /\A ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) \z/x ## # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z Regex_6Hex4Dec = /\A ((?:[0-9A-Fa-f]{1,4}:){6,6}) (\d+)\.(\d+)\.(\d+)\.(\d+) \z/x ## # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z Regex_CompressedHex4Dec = /\A ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: ((?:[0-9A-Fa-f]{1,4}:)*) (\d+)\.(\d+)\.(\d+)\.(\d+) \z/x ## # IPv6 link local address format fe80:b:c:d:e:f:g:h%em1 Regex_8HexLinkLocal = /\A [Ff][Ee]80 (?::[0-9A-Fa-f]{1,4}){7} %[-0-9A-Za-z._~]+ \z/x ## # Compressed IPv6 link local address format fe80::b%em1 Regex_CompressedHexLinkLocal = /\A [Ff][Ee]80: (?: ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) | :((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) )? :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+ \z/x ## # A composite IPv6 address Regexp. Regex = / (?:#{Regex_8Hex}) | (?:#{Regex_CompressedHex}) | (?:#{Regex_6Hex4Dec}) | (?:#{Regex_CompressedHex4Dec}) | (?:#{Regex_8HexLinkLocal}) | (?:#{Regex_CompressedHexLinkLocal}) /x ## # Creates a new IPv6 address from +arg+ which may be: # # IPv6:: returns +arg+. # String:: +arg+ must match one of the IPv6::Regex* constants def self.create(arg) case arg when IPv6 return arg when String address = ''.b if Regex_8Hex =~ arg arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} elsif Regex_CompressedHex =~ arg prefix = $1 suffix = $2 a1 = ''.b a2 = ''.b prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} omitlen = 16 - a1.length - a2.length address << a1 << "\0" * omitlen << a2 elsif Regex_6Hex4Dec =~ arg prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} address << [a, b, c, d].pack('CCCC') else raise ArgumentError.new("not numeric IPv6 address: " + arg) end elsif Regex_CompressedHex4Dec =~ arg prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d a1 = ''.b a2 = ''.b prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} omitlen = 12 - a1.length - a2.length address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC') else raise ArgumentError.new("not numeric IPv6 address: " + arg) end else raise ArgumentError.new("not numeric IPv6 address: " + arg) end return IPv6.new(address) else raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}") end end def initialize(address) # :nodoc: unless address.kind_of?(String) && address.length == 16 raise ArgumentError.new('IPv6 address must be 16 bytes') end @address = address end ## # The raw IPv6 address as a String. attr_reader :address def to_s # :nodoc: sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn")).sub(/(^|:)0(:0)+(:|$)/, '::') end def inspect # :nodoc: return "#<#{self.class} #{self}>" end ## # Turns this IPv6 address into a Gem::Resolv::DNS::Name. #-- # ip6.arpa should be searched too. [RFC3152] def to_name return DNS::Name.new( @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa']) end def ==(other) # :nodoc: return @address == other.address end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @address.hash end end ## # Gem::Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver. It blindly # makes queries to the mDNS addresses without understanding anything about # multicast ports. # # Information taken form the following places: # # * RFC 6762 class MDNS < DNS ## # Default mDNS Port Port = 5353 ## # Default IPv4 mDNS address AddressV4 = '224.0.0.251' ## # Default IPv6 mDNS address AddressV6 = 'ff02::fb' ## # Default mDNS addresses Addresses = [ [AddressV4, Port], [AddressV6, Port], ] ## # Creates a new one-shot Multicast DNS (mDNS) resolver. # # +config_info+ can be: # # nil:: # Uses the default mDNS addresses # # Hash:: # Must contain :nameserver or :nameserver_port like # Gem::Resolv::DNS#initialize. def initialize(config_info=nil) if config_info then super({ nameserver_port: Addresses }.merge(config_info)) else super(nameserver_port: Addresses) end end ## # Iterates over all IP addresses for +name+ retrieved from the mDNS # resolver, provided name ends with "local". If the name does not end in # "local" no records will be returned. # # +name+ can be a Gem::Resolv::DNS::Name or a String. Retrieved addresses will # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 def each_address(name) name = Gem::Resolv::DNS::Name.create(name) return unless name[-1].to_s == 'local' super(name) end def make_udp_requester # :nodoc: nameserver_port = @config.nameserver_port Requester::MDNSOneShot.new(*nameserver_port) end end module LOC ## # A Gem::Resolv::LOC::Size class Size Regex = /^(\d+\.*\d*)[m]$/ ## # Creates a new LOC::Size from +arg+ which may be: # # LOC::Size:: returns +arg+. # String:: +arg+ must match the LOC::Size::Regex constant def self.create(arg) case arg when Size return arg when String scalar = '' if Regex =~ arg scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C") else raise ArgumentError.new("not a properly formed Size string: " + arg) end return Size.new(scalar) else raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}") end end def initialize(scalar) @scalar = scalar end ## # The raw size attr_reader :scalar def to_s # :nodoc: s = @scalar.unpack("H2").join.to_s return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m" end def inspect # :nodoc: return "#<#{self.class} #{self}>" end def ==(other) # :nodoc: return @scalar == other.scalar end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @scalar.hash end end ## # A Gem::Resolv::LOC::Coord class Coord Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/ ## # Creates a new LOC::Coord from +arg+ which may be: # # LOC::Coord:: returns +arg+. # String:: +arg+ must match the LOC::Coord::Regex constant def self.create(arg) case arg when Coord return arg when String coordinates = '' if Regex =~ arg && $1.to_f < 180 m = $~ hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1 coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) + (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N") orientation = m[4][/[NS]/] ? 'lat' : 'lon' else raise ArgumentError.new("not a properly formed Coord string: " + arg) end return Coord.new(coordinates,orientation) else raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}") end end def initialize(coordinates,orientation) unless coordinates.kind_of?(String) raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}") end unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/] raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"') end @coordinates = coordinates @orientation = orientation end ## # The raw coordinates attr_reader :coordinates ## The orientation of the hemisphere as 'lat' or 'lon' attr_reader :orientation def to_s # :nodoc: c = @coordinates.unpack("N").join.to_i val = (c - (2**31)).abs fracsecs = (val % 1e3).to_i.to_s val = val / 1e3 secs = (val % 60).to_i.to_s val = val / 60 mins = (val % 60).to_i.to_s degs = (val / 60).to_i.to_s posi = (c >= 2**31) case posi when true hemi = @orientation[/^lat$/] ? "N" : "E" else hemi = @orientation[/^lon$/] ? "W" : "S" end return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi end def inspect # :nodoc: return "#<#{self.class} #{self}>" end def ==(other) # :nodoc: return @coordinates == other.coordinates end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @coordinates.hash end end ## # A Gem::Resolv::LOC::Alt class Alt Regex = /^([+-]*\d+\.*\d*)[m]$/ ## # Creates a new LOC::Alt from +arg+ which may be: # # LOC::Alt:: returns +arg+. # String:: +arg+ must match the LOC::Alt::Regex constant def self.create(arg) case arg when Alt return arg when String altitude = '' if Regex =~ arg altitude = [($1.to_f*(1e2))+(1e7)].pack("N") else raise ArgumentError.new("not a properly formed Alt string: " + arg) end return Alt.new(altitude) else raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}") end end def initialize(altitude) @altitude = altitude end ## # The raw altitude attr_reader :altitude def to_s # :nodoc: a = @altitude.unpack("N").join.to_i return ((a.to_f/1e2)-1e5).to_s + "m" end def inspect # :nodoc: return "#<#{self.class} #{self}>" end def ==(other) # :nodoc: return @altitude == other.altitude end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @altitude.hash end end end ## # Default resolver to use for Gem::Resolv class methods. DefaultResolver = self.new ## # Replaces the resolvers in the default resolver with +new_resolvers+. This # allows resolvers to be changed for resolv-replace. def DefaultResolver.replace_resolvers new_resolvers @resolvers = new_resolvers end ## # Address Regexp to use for matching IP addresses. AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/ end PK!.V : :"rubygems/vendor/tsort/lib/tsort.rbnu[# frozen_string_literal: true #-- # tsort.rb - provides a module for topological sorting and strongly connected components. #++ # # # Gem::TSort implements topological sorting using Tarjan's algorithm for # strongly connected components. # # Gem::TSort is designed to be able to be used with any object which can be # interpreted as a directed graph. # # Gem::TSort requires two methods to interpret an object as a graph, # tsort_each_node and tsort_each_child. # # * tsort_each_node is used to iterate for all nodes over a graph. # * tsort_each_child is used to iterate for child nodes of a given node. # # The equality of nodes are defined by eql? and hash since # Gem::TSort uses Hash internally. # # == A Simple Example # # The following example demonstrates how to mix the Gem::TSort module into an # existing class (in this case, Hash). Here, we're treating each key in # the hash as a node in the graph, and so we simply alias the required # #tsort_each_node method to Hash's #each_key method. For each key in the # hash, the associated value is an array of the node's child nodes. This # choice in turn leads to our implementation of the required #tsort_each_child # method, which fetches the array of child nodes and then iterates over that # array using the user-supplied block. # # require 'rubygems/vendor/tsort/lib/tsort' # # class Hash # include Gem::TSort # alias tsort_each_node each_key # def tsort_each_child(node, &block) # fetch(node).each(&block) # end # end # # {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort # #=> [3, 2, 1, 4] # # {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components # #=> [[4], [2, 3], [1]] # # == A More Realistic Example # # A very simple `make' like tool can be implemented as follows: # # require 'rubygems/vendor/tsort/lib/tsort' # # class Make # def initialize # @dep = {} # @dep.default = [] # end # # def rule(outputs, inputs=[], &block) # triple = [outputs, inputs, block] # outputs.each {|f| @dep[f] = [triple]} # @dep[triple] = inputs # end # # def build(target) # each_strongly_connected_component_from(target) {|ns| # if ns.length != 1 # fs = ns.delete_if {|n| Array === n} # raise Gem::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") # end # n = ns.first # if Array === n # outputs, inputs, block = n # inputs_time = inputs.map {|f| File.mtime f}.max # begin # outputs_time = outputs.map {|f| File.mtime f}.min # rescue Errno::ENOENT # outputs_time = nil # end # if outputs_time == nil || # inputs_time != nil && outputs_time <= inputs_time # sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i # block.call # end # end # } # end # # def tsort_each_child(node, &block) # @dep[node].each(&block) # end # include Gem::TSort # end # # def command(arg) # print arg, "\n" # system arg # end # # m = Make.new # m.rule(%w[t1]) { command 'date > t1' } # m.rule(%w[t2]) { command 'date > t2' } # m.rule(%w[t3]) { command 'date > t3' } # m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } # m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } # m.build('t5') # # == Bugs # # * 'tsort.rb' is wrong name because this library uses # Tarjan's algorithm for strongly connected components. # Although 'strongly_connected_components.rb' is correct but too long. # # == References # # R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", # SIAM Journal on Computing, Vol. 1, No. 2, pp. 146-160, June 1972. # module Gem::TSort VERSION = "0.2.0" class Cyclic < StandardError end # Returns a topologically sorted array of nodes. # The array is sorted from children to parents, i.e. # the first element has no child and the last node has no parent. # # If there is a cycle, Gem::TSort::Cyclic is raised. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # p graph.tsort #=> [4, 2, 3, 1] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # p graph.tsort # raises Gem::TSort::Cyclic # def tsort each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.tsort(each_node, each_child) end # Returns a topologically sorted array of nodes. # The array is sorted from children to parents, i.e. # the first element has no child and the last node has no parent. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # If there is a cycle, Gem::TSort::Cyclic is raised. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] # # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.tsort(each_node, each_child) # raises Gem::TSort::Cyclic # def self.tsort(each_node, each_child) tsort_each(each_node, each_child).to_a end # The iterator version of the #tsort method. # obj.tsort_each is similar to obj.tsort.each, but # modification of _obj_ during the iteration may lead to unexpected results. # # #tsort_each returns +nil+. # If there is a cycle, Gem::TSort::Cyclic is raised. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # graph.tsort_each {|n| p n } # #=> 4 # # 2 # # 3 # # 1 # def tsort_each(&block) # :yields: node each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.tsort_each(each_node, each_child, &block) end # The iterator version of the Gem::TSort.tsort method. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # Gem::TSort.tsort_each(each_node, each_child) {|n| p n } # #=> 4 # # 2 # # 3 # # 1 # def self.tsort_each(each_node, each_child) # :yields: node return to_enum(__method__, each_node, each_child) unless block_given? each_strongly_connected_component(each_node, each_child) {|component| if component.size == 1 yield component.first else raise Cyclic.new("topological sort failed: #{component.inspect}") end } end # Returns strongly connected components as an array of arrays of nodes. # The array is sorted from children to parents. # Each elements of the array represents a strongly connected component. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] # def strongly_connected_components each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.strongly_connected_components(each_node, each_child) end # Returns strongly connected components as an array of arrays of nodes. # The array is sorted from children to parents. # Each elements of the array represents a strongly connected component. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.strongly_connected_components(each_node, each_child) # #=> [[4], [2], [3], [1]] # # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.strongly_connected_components(each_node, each_child) # #=> [[4], [2, 3], [1]] # def self.strongly_connected_components(each_node, each_child) each_strongly_connected_component(each_node, each_child).to_a end # The iterator version of the #strongly_connected_components method. # obj.each_strongly_connected_component is similar to # obj.strongly_connected_components.each, but # modification of _obj_ during the iteration may lead to unexpected results. # # #each_strongly_connected_component returns +nil+. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # graph.each_strongly_connected_component {|scc| p scc } # #=> [4] # # [2] # # [3] # # [1] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # graph.each_strongly_connected_component {|scc| p scc } # #=> [4] # # [2, 3] # # [1] # def each_strongly_connected_component(&block) # :yields: nodes each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.each_strongly_connected_component(each_node, each_child, &block) end # The iterator version of the Gem::TSort.strongly_connected_components method. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } # #=> [4] # # [2] # # [3] # # [1] # # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } # #=> [4] # # [2, 3] # # [1] # def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes return to_enum(__method__, each_node, each_child) unless block_given? id_map = {} stack = [] each_node.call {|node| unless id_map.include? node each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| yield c } end } nil end # Iterates over strongly connected component in the subgraph reachable from # _node_. # # Return value is unspecified. # # #each_strongly_connected_component_from doesn't call #tsort_each_node. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # graph.each_strongly_connected_component_from(2) {|scc| p scc } # #=> [4] # # [2] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # graph.each_strongly_connected_component_from(2) {|scc| p scc } # #=> [4] # # [2, 3] # def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes Gem::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) end # Iterates over strongly connected components in a graph. # The graph is represented by _node_ and _each_child_. # # _node_ is the first node. # _each_child_ should have +call+ method which takes a node argument # and yields for each child node. # # Return value is unspecified. # # #Gem::TSort.each_strongly_connected_component_from is a class method and # it doesn't need a class to represent a graph which includes Gem::TSort. # # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_child = lambda {|n, &b| graph[n].each(&b) } # Gem::TSort.each_strongly_connected_component_from(1, each_child) {|scc| # p scc # } # #=> [4] # # [2, 3] # # [1] # def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes return to_enum(__method__, node, each_child, id_map, stack) unless block_given? minimum_id = node_id = id_map[node] = id_map.size stack_length = stack.length stack << node each_child.call(node) {|child| if id_map.include? child child_id = id_map[child] minimum_id = child_id if child_id && child_id < minimum_id else sub_minimum_id = each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| yield c } minimum_id = sub_minimum_id if sub_minimum_id < minimum_id end } if node_id == minimum_id component = stack.slice!(stack_length .. -1) component.each {|n| id_map[n] = nil} yield component end minimum_id end # Should be implemented by a extended class. # # #tsort_each_node is used to iterate for all nodes over a graph. # def tsort_each_node # :yields: node raise NotImplementedError.new end # Should be implemented by a extended class. # # #tsort_each_child is used to iterate for child nodes of _node_. # def tsort_each_child(node) # :yields: child raise NotImplementedError.new end end PK!224rubygems/vendor/securerandom/lib/random/formatter.rbnu[# -*- coding: us-ascii -*- # frozen_string_literal: true # == \Random number formatter. # # Formats generated random numbers in many manners. When 'random/formatter' # is required, several methods are added to empty core module Gem::Random::Formatter, # making them available as Random's instance and module methods. # # Standard library Gem::SecureRandom is also extended with the module, and the methods # described below are available as a module methods in it. # # === Examples # # Generate random hexadecimal strings: # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # prng = Random.new # prng.hex(10) #=> "52750b30ffbc7de3b362" # prng.hex(10) #=> "92b15d6c8dc4beb5f559" # prng.hex(13) #=> "39b290146bea6ce975c37cfc23" # # or just # Random.hex #=> "1aed0c631e41be7f77365415541052ee" # # Generate random base64 strings: # # prng.base64(10) #=> "EcmTPZwWRAozdA==" # prng.base64(10) #=> "KO1nIU+p9DKxGg==" # prng.base64(12) #=> "7kJSM/MzBJI+75j8" # Random.base64(4) #=> "bsQ3fQ==" # # Generate random binary strings: # # prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301" # prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" # Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43" # # Generate alphanumeric strings: # # prng.alphanumeric(10) #=> "S8baxMJnPl" # prng.alphanumeric(10) #=> "aOxAg8BAJe" # Random.alphanumeric #=> "TmP9OsJHJLtaZYhP" # # Generate UUIDs: # # prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" # prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" # Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd" # # All methods are available in the standard library Gem::SecureRandom, too: # # Gem::SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf" module Gem::Random::Formatter # Generate a random binary string. # # The argument _n_ specifies the length of the result string. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in future. # # The result may contain any byte: "\x00" - "\xff". # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" # # or # prng = Random.new # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" def random_bytes(n=nil) n = n ? n.to_int : 16 gen_random(n) end # Generate a random hexadecimal string. # # The argument _n_ specifies the length, in bytes, of the random number to be generated. # The length of the resulting hexadecimal string is twice of _n_. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The result may contain 0-9 and a-f. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" # # or # prng = Random.new # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61" def hex(n=nil) random_bytes(n).unpack1("H*") end # Generate a random base64 string. # # The argument _n_ specifies the length, in bytes, of the random number # to be generated. The length of the result string is about 4/3 of _n_. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The result may contain A-Z, a-z, 0-9, "+", "/" and "=". # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" # # or # prng = Random.new # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" # # See RFC 3548 for the definition of base64. def base64(n=nil) [random_bytes(n)].pack("m0") end # Generate a random URL-safe base64 string. # # The argument _n_ specifies the length, in bytes, of the random number # to be generated. The length of the result string is about 4/3 of _n_. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The boolean argument _padding_ specifies the padding. # If it is false or nil, padding is not generated. # Otherwise padding is generated. # By default, padding is not generated because "=" may be used as a URL delimiter. # # The result may contain A-Z, a-z, 0-9, "-" and "_". # "=" is also used if _padding_ is true. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" # # or # prng = Random.new # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" # # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" # prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg==" # # See RFC 3548 for the definition of URL-safe base64. def urlsafe_base64(n=nil, padding=false) s = [random_bytes(n)].pack("m0") s.tr!("+/", "-_") s.delete!("=") unless padding s end # Generate a random v4 UUID (Universally Unique IDentifier). # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" # # or # prng = Random.new # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" # # The version 4 UUID is purely random (except the version). # It doesn't contain meaningful information such as MAC addresses, timestamps, etc. # # The result contains 122 random bits (15.25 random bytes). # # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. # def uuid ary = random_bytes(16).unpack("NnnnnN") ary[2] = (ary[2] & 0x0fff) | 0x4000 ary[3] = (ary[3] & 0x3fff) | 0x8000 "%08x-%04x-%04x-%04x-%04x%08x" % ary end alias uuid_v4 uuid # Generate a random v7 UUID (Universally Unique IDentifier). # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e" # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5" # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23" # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31" # # |<--sorted-->| |<----- random ---->| # # # or # prng = Random.new # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98" # # The version 7 UUID starts with the least significant 48 bits of a 64 bit # Unix timestamp (milliseconds since the epoch) and fills the remaining bits # with random data, excluding the version and variant bits. # # This allows version 7 UUIDs to be sorted by creation time. Time ordered # UUIDs can be used for better database index locality of newly inserted # records, which may have a significant performance benefit compared to random # data inserts. # # The result contains 74 random bits (9.25 random bytes). # # Note that this method cannot be made reproducable because its output # includes not only random bits but also timestamp. # # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/] # for details of UUIDv7. # # ==== Monotonicity # # UUIDv7 has millisecond precision by default, so multiple UUIDs created # within the same millisecond are not issued in monotonically increasing # order. To create UUIDs that are time-ordered with sub-millisecond # precision, up to 12 bits of additional timestamp may added with # +extra_timestamp_bits+. The extra timestamp precision comes at the expense # of random bits. Setting extra_timestamp_bits: 12 provides ~244ns # of precision, but only 62 random bits (7.75 random bytes). # # prng = Random.new # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) } # # => # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a", # "0188d4c7-13da-753b-83a5-7fb9b2afaeea", # "0188d4c7-13da-754a-88ea-ac0baeedd8db", # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"] # # |<--- sorted --->| |<-- random --->| # # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) } # # => # ["0188d4c7-3333-7a95-850a-de6edb858f7e", # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order # "0188d4c7-3333-7af9-87c3-8f612edac82e"] # # |<--- sorted -->||<---- random --->| # # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based # on UTC, which excludes leap seconds and can rollback the clock. To avoid # this, the system clock can synchronize with an NTP server configured to use # a "leap smear" approach. NTP or PTP will also be needed to synchronize # across distributed nodes. # # Counters and other mechanisms for stronger guarantees of monotonicity are # not implemented. Applications with stricter requirements should follow # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters] # of the specification. # def uuid_v7(extra_timestamp_bits: 0) case (extra_timestamp_bits = Integer(extra_timestamp_bits)) when 0 # min timestamp precision ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) rand = random_bytes(10) rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant "%08x-%04x-%s" % [ (ms & 0x0000_ffff_ffff_0000) >> 16, (ms & 0x0000_0000_0000_ffff), rand.unpack("H4H4H12").join("-") ] when 12 # max timestamp precision ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) .divmod(1_000_000) extra_bits = ns * 4096 / 1_000_000 rand = random_bytes(8) rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant "%08x-%04x-7%03x-%s" % [ (ms & 0x0000_ffff_ffff_0000) >> 16, (ms & 0x0000_0000_0000_ffff), extra_bits, rand.unpack("H4H12").join("-") ] when (0..12) # the generic version is slower than the special cases above rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN") rand_mask_bits = 12 - extra_timestamp_bits ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) .divmod(1_000_000) "%08x-%04x-%04x-%04x-%04x%08x" % [ (ms & 0x0000_ffff_ffff_0000) >> 16, (ms & 0x0000_0000_0000_ffff), 0x7000 | ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) | rand_a & ((1 << rand_mask_bits) - 1), 0x8000 | (rand_b1 & 0x3fff), rand_b2, rand_b3 ] else raise ArgumentError, "extra_timestamp_bits must be in 0..12" end end # Internal interface to Random; Generate random data _n_ bytes. private def gen_random(n) self.bytes(n) end # Generate a string that randomly draws from a # source array of characters. # # The argument _source_ specifies the array of characters from which # to generate the string. # The argument _n_ specifies the length, in characters, of the string to be # generated. # # The result may contain whatever characters are in the source array. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron" # prng.choose([*'0'..'9'], 5) #=> "27309" private def choose(source, n) size = source.size m = 1 limit = size while limit * size <= 0x100000000 limit *= size m += 1 end result = ''.dup while m <= n rs = random_number(limit) is = rs.digits(size) (m-is.length).times { is << 0 } result << source.values_at(*is).join('') n -= m end if 0 < n rs = random_number(limit) is = rs.digits(size) if is.length < n (n-is.length).times { is << 0 } else is.pop while n < is.length end result.concat source.values_at(*is).join('') end result end # The default character list for #alphanumeric. ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] # Generate a random alphanumeric string. # # The argument _n_ specifies the length, in characters, of the alphanumeric # string to be generated. # The argument _chars_ specifies the character list which the result is # consist of. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR" # # or # prng = Random.new # prng.alphanumeric(10) #=> "i6K93NdqiH" # # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952" # # or # prng = Random.new # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''." def alphanumeric(n = nil, chars: ALPHANUMERIC) n = 16 if n.nil? choose(chars, n) end end PK!Fhs0rubygems/vendor/securerandom/lib/securerandom.rbnu[# -*- coding: us-ascii -*- # frozen_string_literal: true require_relative 'random/formatter' # == Secure random number generator interface. # # This library is an interface to secure random number generators which are # suitable for generating session keys in HTTP cookies, etc. # # You can use this library in your application by requiring it: # # require 'rubygems/vendor/securerandom/lib/securerandom' # # It supports the following secure random number generators: # # * openssl # * /dev/urandom # * Win32 # # Gem::SecureRandom is extended by the Gem::Random::Formatter module which # defines the following methods: # # * alphanumeric # * base64 # * choose # * gen_random # * hex # * rand # * random_bytes # * random_number # * urlsafe_base64 # * uuid # # These methods are usable as class methods of Gem::SecureRandom such as # +Gem::SecureRandom.hex+. # # If a secure random number generator is not available, # +NotImplementedError+ is raised. module Gem::SecureRandom # The version VERSION = "0.3.1" class << self # Returns a random binary string containing +size+ bytes. # # See Random.bytes def bytes(n) return gen_random(n) end private # :stopdoc: # Implementation using OpenSSL def gen_random_openssl(n) return OpenSSL::Random.random_bytes(n) end # Implementation using system random device def gen_random_urandom(n) ret = Random.urandom(n) unless ret raise NotImplementedError, "No random device" end unless ret.length == n raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes" end ret end begin # Check if Random.urandom is available Random.urandom(1) alias gen_random gen_random_urandom rescue RuntimeError begin require 'openssl' rescue NoMethodError raise NotImplementedError, "No random device" else alias gen_random gen_random_openssl end end # :startdoc: # Generate random data bytes for Gem::Random::Formatter public :gen_random end end Gem::SecureRandom.extend(Gem::Random::Formatter) PK!2<<rubygems/security_option.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" # forward-declare module Gem::Security # :nodoc: class Policy # :nodoc: end end ## # Mixin methods for security option for Gem::Commands module Gem::SecurityOption def add_security_option Gem::OptionParser.accept Gem::Security::Policy do |value| require_relative "security" raise Gem::OptionParser::InvalidArgument, "OpenSSL not installed" unless defined?(Gem::Security::HighSecurity) policy = Gem::Security::Policies[value] unless policy valid = Gem::Security::Policies.keys.sort raise Gem::OptionParser::InvalidArgument, "#{value} (#{valid.join ", "} are valid)" end policy end add_option(:"Install/Update", "-P", "--trust-policy POLICY", Gem::Security::Policy, "Specify gem trust policy") do |value, options| options[:security_policy] = value end end end PK!O!!rubygems/dependency.rbnu[# frozen_string_literal: true ## # The Dependency class holds a Gem name and a Gem::Requirement. class Gem::Dependency ## # Valid dependency types. #-- # When this list is updated, be sure to change # Gem::Specification::CURRENT_SPECIFICATION_VERSION as well. # # REFACTOR: This type of constant, TYPES, indicates we might want # two classes, used via inheritance or duck typing. TYPES = [ :development, :runtime, ].freeze ## # Dependency name or regular expression. attr_accessor :name ## # Allows you to force this dependency to be a prerelease. attr_writer :prerelease ## # Constructs a dependency with +name+ and +requirements+. The last # argument can optionally be the dependency type, which defaults to # :runtime. def initialize(name, *requirements) case name when String then # ok when Regexp then msg = ["NOTE: Dependency.new w/ a regexp is deprecated.", "Dependency.new called from #{Gem.location_of_caller.join(":")}"] warn msg.join("\n") unless Gem::Deprecate.skip else raise ArgumentError, "dependency name must be a String, was #{name.inspect}" end type = Symbol === requirements.last ? requirements.pop : :runtime requirements = requirements.first if requirements.length == 1 # unpack unless TYPES.include? type raise ArgumentError, "Valid types are #{TYPES.inspect}, " \ "not #{type.inspect}" end @name = name @requirement = Gem::Requirement.create requirements @type = type @prerelease = false # This is for Marshal backwards compatibility. See the comments in # +requirement+ for the dirty details. @version_requirements = @requirement end ## # A dependency's hash is the XOR of the hashes of +name+, +type+, # and +requirement+. def hash # :nodoc: name.hash ^ type.hash ^ requirement.hash end def inspect # :nodoc: if prerelease? format("<%s type=%p name=%p requirements=%p prerelease=ok>", self.class, type, name, requirement.to_s) else format("<%s type=%p name=%p requirements=%p>", self.class, type, name, requirement.to_s) end end ## # Does this dependency require a prerelease? def prerelease? @prerelease || requirement.prerelease? end ## # Is this dependency simply asking for the latest version # of a gem? def latest_version? @requirement.none? end def pretty_print(q) # :nodoc: q.group 1, "Gem::Dependency.new(", ")" do q.pp name q.text "," q.breakable q.pp requirement q.text "," q.breakable q.pp type end end ## # What does this dependency require? def requirement return @requirement if defined?(@requirement) && @requirement # @version_requirements and @version_requirement are legacy ivar # names, and supported here because older gems need to keep # working and Dependency doesn't implement marshal_dump and # marshal_load. In a happier world, this would be an # attr_accessor. The horrifying instance_variable_get you see # below is also the legacy of some old restructurings. # # Note also that because of backwards compatibility (loading new # gems in an old RubyGems installation), we can't add explicit # marshaling to this class until we want to make a big # break. Maybe 2.0. # # Children, define explicit marshal and unmarshal behavior for # public classes. Marshal formats are part of your public API. # REFACTOR: See above if defined?(@version_requirement) && @version_requirement version = @version_requirement.instance_variable_get :@version @version_requirement = nil @version_requirements = Gem::Requirement.new version end @requirement = @version_requirements if defined?(@version_requirements) end def requirements_list requirement.as_list end def to_s # :nodoc: if type != :runtime "#{name} (#{requirement}, #{type})" else "#{name} (#{requirement})" end end ## # Dependency type. def type @type ||= :runtime end def runtime? @type == :runtime || !@type end def ==(other) # :nodoc: Gem::Dependency === other && name == other.name && type == other.type && requirement == other.requirement end ## # Dependencies are ordered by name. def <=>(other) name <=> other.name end ## # Uses this dependency as a pattern to compare to +other+. This # dependency will match if the name matches the other's name, and # other has only an equal version requirement that satisfies this # dependency. def =~(other) unless Gem::Dependency === other return unless other.respond_to?(:name) && other.respond_to?(:version) other = Gem::Dependency.new other.name, other.version end return false unless name === other.name reqs = other.requirement.requirements return false unless reqs.length == 1 return false unless reqs.first.first == "=" version = reqs.first.last requirement.satisfied_by? version end alias_method :===, :=~ ## # :call-seq: # dep.match? name => true or false # dep.match? name, version => true or false # dep.match? spec => true or false # # Does this dependency match the specification described by +name+ and # +version+ or match +spec+? # # NOTE: Unlike #matches_spec? this method does not return true when the # version is a prerelease version unless this is a prerelease dependency. def match?(obj, version=nil, allow_prerelease=false) if !version name = obj.name version = obj.version else name = obj end return false unless self.name === name version = Gem::Version.new version return true if requirement.none? && !version.prerelease? return false if version.prerelease? && !allow_prerelease && !prerelease? requirement.satisfied_by? version end ## # Does this dependency match +spec+? # # NOTE: This is not a convenience method. Unlike #match? this method # returns true when +spec+ is a prerelease version even if this dependency # is not a prerelease dependency. def matches_spec?(spec) return false unless name === spec.name return true if requirement.none? requirement.satisfied_by?(spec.version) end ## # Merges the requirements of +other+ into this dependency def merge(other) unless name == other.name raise ArgumentError, "#{self} and #{other} have different names" end default = Gem::Requirement.default self_req = requirement other_req = other.requirement return self.class.new name, self_req if other_req == default return self.class.new name, other_req if self_req == default self.class.new name, self_req.as_list.concat(other_req.as_list) end def matching_specs(platform_only = false) matches = Gem::Specification.find_all_by_name(name, requirement) if platform_only matches.reject! do |spec| spec.nil? || !Gem::Platform.match_spec?(spec) end end matches.reject(&:ignored?) end ## # True if the dependency will not always match the latest version. def specific? @requirement.specific? end def to_specs matches = matching_specs true # TODO: check Gem.activated_spec[self.name] in case matches falls outside if matches.empty? specs = Gem::Specification.stubs_for name if specs.empty? raise Gem::MissingSpecError.new name, requirement else raise Gem::MissingSpecVersionError.new name, requirement, specs end end # TODO: any other resolver validations should go here matches end def to_spec matches = to_specs.compact active = matches.find(&:activated?) return active if active unless prerelease? # Consider prereleases only as a fallback pre, matches = matches.partition {|spec| spec.version.prerelease? } matches = pre if matches.empty? end matches.first end def identity if prerelease? if specific? :complete else :abs_latest end elsif latest_version? :latest else :released end end def encode_with(coder) # :nodoc: coder.add "name", @name coder.add "requirement", @requirement coder.add "type", @type coder.add "prerelease", @prerelease coder.add "version_requirements", @version_requirements end end PK!]B# rubygems/specification_record.rbnu[# frozen_string_literal: true module Gem class SpecificationRecord def self.dirs_from(paths) paths.map do |path| File.join(path, "specifications") end end def self.from_path(path) new(dirs_from([path])) end def initialize(dirs) @all = nil @stubs = nil @stubs_by_name = {} @spec_with_requirable_file = {} @active_stub_with_requirable_file = {} @dirs = dirs end # Sentinel object to represent "not found" stubs NOT_FOUND = Struct.new(:to_spec, :this).new private_constant :NOT_FOUND ## # Returns the list of all specifications in the record def all @all ||= stubs.map(&:to_spec) end ## # Returns a Gem::StubSpecification for every specification in the record def stubs @stubs ||= begin pattern = "*.gemspec" stubs = stubs_for_pattern(pattern, false) @stubs_by_name = stubs.select {|s| Gem::Platform.match_spec? s }.group_by(&:name) stubs end end ## # Returns a Gem::StubSpecification for every specification in the record # named +name+ only returns stubs that match Gem.platforms def stubs_for(name) if @stubs @stubs_by_name[name] || [] else @stubs_by_name[name] ||= stubs_for_pattern("#{name}-*.gemspec").select do |s| s.name == name end end end ## # Finds stub specifications matching a pattern in the record, optionally # filtering out specs not matching the current platform def stubs_for_pattern(pattern, match_platform = true) installed_stubs = installed_stubs(pattern) installed_stubs.select! {|s| Gem::Platform.match_spec? s } if match_platform stubs = installed_stubs + Gem::Specification.default_stubs(pattern) Gem::Specification._resort!(stubs) stubs end ## # Adds +spec+ to the the record, keeping the collection properly sorted. def add_spec(spec) return if all.include? spec all << spec stubs << spec (@stubs_by_name[spec.name] ||= []) << spec Gem::Specification._resort!(@stubs_by_name[spec.name]) Gem::Specification._resort!(stubs) end ## # Removes +spec+ from the record. def remove_spec(spec) all.delete spec.to_spec stubs.delete spec (@stubs_by_name[spec.name] || []).delete spec end ## # Sets the specs known by the record to +specs+. def all=(specs) @stubs_by_name = specs.group_by(&:name) @all = @stubs = specs end ## # Return full names of all specs in the record in sorted order. def all_names all.map(&:full_name) end include Enumerable ## # Enumerate every known spec. def each return enum_for(:each) unless block_given? all.each do |x| yield x end end ## # Returns every spec in the record that matches +name+ and optional +requirements+. def find_all_by_name(name, *requirements) req = Gem::Requirement.create(*requirements) env_req = Gem.env_requirement(name) matches = stubs_for(name).find_all do |spec| req.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version) end.map(&:to_spec) if name == "bundler" && !req.specific? require_relative "bundler_version_finder" Gem::BundlerVersionFinder.prioritize!(matches) end matches end ## # Return the best specification in the record that contains the file matching +path+. def find_by_path(path) path = path.dup.freeze spec = @spec_with_requirable_file[path] ||= stubs.find do |s| s.contains_requirable_file? path end || NOT_FOUND spec.to_spec end ## # Return the best specification in the record that contains the file # matching +path+ amongst the specs that are not activated. def find_inactive_by_path(path) stub = stubs.find do |s| next if s.activated? s.contains_requirable_file? path end stub&.to_spec end ## # Return the best specification in the record that contains the file # matching +path+, among those already activated. def find_active_stub_by_path(path) stub = @active_stub_with_requirable_file[path] ||= stubs.find do |s| s.activated? && s.contains_requirable_file?(path) end || NOT_FOUND stub.this end ## # Return the latest specs in the record, optionally including prerelease # specs if +prerelease+ is true. def latest_specs(prerelease) Gem::Specification._latest_specs stubs, prerelease end ## # Return the latest installed spec in the record for gem +name+. def latest_spec_for(name) latest_specs(true).find {|installed_spec| installed_spec.name == name } end private def installed_stubs(pattern) map_stubs(pattern) do |path, base_dir, gems_dir| Gem::StubSpecification.gemspec_stub(path, base_dir, gems_dir) end end def map_stubs(pattern) @dirs.flat_map do |dir| base_dir = File.dirname dir gems_dir = File.join base_dir, "gems" Gem::Specification.gemspec_stubs_in(dir, pattern) {|path| yield path, base_dir, gems_dir } end end end end PK! oo!rubygems/vendored_securerandom.rbnu[# frozen_string_literal: true module Gem::Random; end require_relative "vendor/securerandom/lib/securerandom" PK!ai4i4rubygems/user_interaction.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "deprecate" require_relative "text" ## # Module that defines the default UserInteraction. Any class including this # module will have access to the +ui+ method that returns the default UI. module Gem::DefaultUserInteraction include Gem::Text ## # The default UI is a class variable of the singleton class for this # module. @ui = nil ## # Return the default UI. def self.ui @ui ||= Gem::ConsoleUI.new end ## # Set the default UI. If the default UI is never explicitly set, a simple # console based UserInteraction will be used automatically. def self.ui=(new_ui) @ui = new_ui end ## # Use +new_ui+ for the duration of +block+. def self.use_ui(new_ui) old_ui = @ui @ui = new_ui yield ensure @ui = old_ui end ## # See DefaultUserInteraction::ui def ui Gem::DefaultUserInteraction.ui end ## # See DefaultUserInteraction::ui= def ui=(new_ui) Gem::DefaultUserInteraction.ui = new_ui end ## # See DefaultUserInteraction::use_ui def use_ui(new_ui, &block) Gem::DefaultUserInteraction.use_ui(new_ui, &block) end end ## # UserInteraction allows RubyGems to interact with the user through standard # methods that can be replaced with more-specific UI methods for different # displays. # # Since UserInteraction dispatches to a concrete UI class you may need to # reference other classes for specific behavior such as Gem::ConsoleUI or # Gem::SilentUI. # # Example: # # class X # include Gem::UserInteraction # # def get_answer # n = ask("What is the meaning of life?") # end # end module Gem::UserInteraction include Gem::DefaultUserInteraction ## # Displays an alert +statement+. Asks a +question+ if given. def alert(statement, question = nil) ui.alert statement, question end ## # Displays an error +statement+ to the error output location. Asks a # +question+ if given. def alert_error(statement, question = nil) ui.alert_error statement, question end ## # Displays a warning +statement+ to the warning output location. Asks a # +question+ if given. def alert_warning(statement, question = nil) ui.alert_warning statement, question end ## # Asks a +question+ and returns the answer. def ask(question) ui.ask question end ## # Asks for a password with a +prompt+ def ask_for_password(prompt) ui.ask_for_password prompt end ## # Asks a yes or no +question+. Returns true for yes, false for no. def ask_yes_no(question, default = nil) ui.ask_yes_no question, default end ## # Asks the user to answer +question+ with an answer from the given +list+. def choose_from_list(question, list) ui.choose_from_list question, list end ## # Displays the given +statement+ on the standard output (or equivalent). def say(statement = "") ui.say statement end ## # Terminates the RubyGems process with the given +exit_code+ def terminate_interaction(exit_code = 0) ui.terminate_interaction exit_code end ## # Calls +say+ with +msg+ or the results of the block if really_verbose # is true. def verbose(msg = nil) say(clean_text(msg || yield)) if Gem.configuration.really_verbose end end ## # Gem::StreamUI implements a simple stream based user interface. class Gem::StreamUI extend Gem::Deprecate ## # The input stream attr_reader :ins ## # The output stream attr_reader :outs ## # The error stream attr_reader :errs ## # Creates a new StreamUI wrapping +in_stream+ for user input, +out_stream+ # for standard output, +err_stream+ for error output. If +usetty+ is true # then special operations (like asking for passwords) will use the TTY # commands to disable character echo. def initialize(in_stream, out_stream, err_stream=$stderr, usetty=true) @ins = in_stream @outs = out_stream @errs = err_stream @usetty = usetty end ## # Returns true if TTY methods should be used on this StreamUI. def tty? @usetty && @ins.tty? end ## # Prints a formatted backtrace to the errors stream if backtraces are # enabled. def backtrace(exception) return unless Gem.configuration.backtrace @errs.puts "\t#{exception.backtrace.join "\n\t"}" end ## # Choose from a list of options. +question+ is a prompt displayed above # the list. +list+ is a list of option strings. Returns the pair # [option_name, option_index]. def choose_from_list(question, list) @outs.puts question list.each_with_index do |item, index| @outs.puts " #{index + 1}. #{item}" end @outs.print "> " @outs.flush result = @ins.gets return nil, nil unless result result = result.strip.to_i - 1 return nil, nil unless (0...list.size) === result [list[result], result] end ## # Ask a question. Returns a true for yes, false for no. If not connected # to a tty, raises an exception if default is nil, otherwise returns # default. def ask_yes_no(question, default=nil) unless tty? if default.nil? raise Gem::OperationNotSupportedError, "Not connected to a tty and no default specified" else return default end end default_answer = case default when nil "yn" when true "Yn" else "yN" end result = nil while result.nil? do result = case ask "#{question} [#{default_answer}]" when /^y/i then true when /^n/i then false when /^$/ then default end end result end ## # Ask a question. Returns an answer if connected to a tty, nil otherwise. def ask(question) return nil unless tty? @outs.print(question + " ") @outs.flush result = @ins.gets result&.chomp! result end ## # Ask for a password. Does not echo response to terminal. def ask_for_password(question) return nil unless tty? @outs.print(question, " ") @outs.flush password = _gets_noecho @outs.puts password&.chomp! password end def require_io_console @require_io_console ||= begin begin require "io/console" rescue LoadError end true end end def _gets_noecho require_io_console @ins.noecho { @ins.gets } end ## # Display a statement. def say(statement="") @outs.puts statement end ## # Display an informational alert. Will ask +question+ if it is not nil. def alert(statement, question=nil) @outs.puts "INFO: #{statement}" ask(question) if question end ## # Display a warning on stderr. Will ask +question+ if it is not nil. def alert_warning(statement, question=nil) @errs.puts "WARNING: #{statement}" ask(question) if question end ## # Display an error message in a location expected to get error messages. # Will ask +question+ if it is not nil. def alert_error(statement, question=nil) @errs.puts "ERROR: #{statement}" ask(question) if question end ## # Terminate the application with exit code +status+, running any exit # handlers that might have been defined. def terminate_interaction(status = 0) close raise Gem::SystemExitException, status end def close end ## # Return a progress reporter object chosen from the current verbosity. def progress_reporter(*args) case Gem.configuration.verbose when nil, false SilentProgressReporter.new(@outs, *args) when true SimpleProgressReporter.new(@outs, *args) else VerboseProgressReporter.new(@outs, *args) end end ## # An absolutely silent progress reporter. class SilentProgressReporter ## # The count of items is never updated for the silent progress reporter. attr_reader :count ## # Creates a silent progress reporter that ignores all input arguments. def initialize(out_stream, size, initial_message, terminal_message = nil) end ## # Does not print +message+ when updated as this object has taken a vow of # silence. def updated(message) end ## # Does not print anything when complete as this object has taken a vow of # silence. def done end end ## # A basic dotted progress reporter. class SimpleProgressReporter include Gem::DefaultUserInteraction ## # The number of progress items counted so far. attr_reader :count ## # Creates a new progress reporter that will write to +out_stream+ for # +size+ items. Shows the given +initial_message+ when progress starts # and the +terminal_message+ when it is complete. def initialize(out_stream, size, initial_message, terminal_message = "complete") @out = out_stream @total = size @count = 0 @terminal_message = terminal_message @out.puts initial_message end ## # Prints out a dot and ignores +message+. def updated(message) @count += 1 @out.print "." @out.flush end ## # Prints out the terminal message. def done @out.puts "\n#{@terminal_message}" end end ## # A progress reporter that prints out messages about the current progress. class VerboseProgressReporter include Gem::DefaultUserInteraction ## # The number of progress items counted so far. attr_reader :count ## # Creates a new progress reporter that will write to +out_stream+ for # +size+ items. Shows the given +initial_message+ when progress starts # and the +terminal_message+ when it is complete. def initialize(out_stream, size, initial_message, terminal_message = "complete") @out = out_stream @total = size @count = 0 @terminal_message = terminal_message @out.puts initial_message end ## # Prints out the position relative to the total and the +message+. def updated(message) @count += 1 @out.puts "#{@count}/#{@total}: #{message}" end ## # Prints out the terminal message. def done @out.puts @terminal_message end end ## # Return a download reporter object chosen from the current verbosity def download_reporter(*args) if [nil, false].include?(Gem.configuration.verbose) || !@outs.tty? SilentDownloadReporter.new(@outs, *args) else ThreadedDownloadReporter.new(@outs, *args) end end ## # An absolutely silent download reporter. class SilentDownloadReporter ## # The silent download reporter ignores all arguments def initialize(out_stream, *args) end ## # The silent download reporter does not display +filename+ or care about # +filesize+ because it is silent. def fetch(filename, filesize) end ## # Nothing can update the silent download reporter. def update(current) end ## # The silent download reporter won't tell you when the download is done. # Because it is silent. def done end end ## # A progress reporter that behaves nicely with threaded downloading. class ThreadedDownloadReporter MUTEX = Thread::Mutex.new ## # The current file name being displayed attr_reader :file_name ## # Creates a new threaded download reporter that will display on # +out_stream+. The other arguments are ignored. def initialize(out_stream, *args) @file_name = nil @out = out_stream end ## # Tells the download reporter that the +file_name+ is being fetched. # The other arguments are ignored. def fetch(file_name, *args) if @file_name.nil? @file_name = file_name locked_puts "Fetching #{@file_name}" end end ## # Updates the threaded download reporter for the given number of +bytes+. def update(bytes) # Do nothing. end ## # Indicates the download is complete. def done # Do nothing. end private def locked_puts(message) MUTEX.synchronize do @out.puts message end end end end ## # Subclass of StreamUI that instantiates the user interaction using $stdin, # $stdout, and $stderr. class Gem::ConsoleUI < Gem::StreamUI ## # The Console UI has no arguments as it defaults to reading input from # stdin, output to stdout and warnings or errors to stderr. def initialize super $stdin, $stdout, $stderr, true end end ## # SilentUI is a UI choice that is absolutely silent. class Gem::SilentUI < Gem::StreamUI ## # The SilentUI has no arguments as it does not use any stream. def initialize io = NullIO.new super io, io, io, false end def close end def download_reporter(*args) # :nodoc: SilentDownloadReporter.new(@outs, *args) end def progress_reporter(*args) # :nodoc: SilentProgressReporter.new(@outs, *args) end ## # An absolutely silent IO. class NullIO def puts(*args) end def print(*args) end def flush end def gets(*args) end def tty? false end end end PK!,8e"rubygems/bundler_version_finder.rbnu[# frozen_string_literal: true module Gem::BundlerVersionFinder def self.bundler_version v = ENV["BUNDLER_VERSION"] v ||= bundle_update_bundler_version return if v == true v ||= lockfile_version return unless v Gem::Version.new(v) end def self.prioritize!(specs) exact_match_index = specs.find_index {|spec| spec.version == bundler_version } return unless exact_match_index specs.unshift(specs.delete_at(exact_match_index)) end def self.bundle_update_bundler_version return unless ["bundle", "bundler"].include? File.basename($0) return unless "update".start_with?(ARGV.first || " ") bundler_version = nil update_index = nil ARGV.each_with_index do |a, i| if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN bundler_version = a end next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ bundler_version = $1 || true update_index = i end bundler_version end private_class_method :bundle_update_bundler_version def self.lockfile_version return unless contents = lockfile_contents regexp = /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ return unless contents =~ regexp $1 end private_class_method :lockfile_version def self.lockfile_contents gemfile = ENV["BUNDLE_GEMFILE"] gemfile = nil if gemfile&.empty? unless gemfile begin Gem::Util.traverse_parents(Dir.pwd) do |directory| next unless gemfile = Gem::GEM_DEP_FILES.find {|f| File.file?(f) } gemfile = File.join directory, gemfile break end rescue Errno::ENOENT return end end return unless gemfile lockfile = case gemfile when "gems.rb" then "gems.locked" else "#{gemfile}.lock" end return unless File.file?(lockfile) File.read(lockfile) end private_class_method :lockfile_contents end PK!IR - -request_set.rbnu[# frozen_string_literal: true require_relative "vendored_tsort" ## # A RequestSet groups a request to activate a set of dependencies. # # nokogiri = Gem::Dependency.new 'nokogiri', '~> 1.6' # pg = Gem::Dependency.new 'pg', '~> 0.14' # # set = Gem::RequestSet.new nokogiri, pg # # requests = set.resolve # # p requests.map { |r| r.full_name } # #=> ["nokogiri-1.6.0", "mini_portile-0.5.1", "pg-0.17.0"] class Gem::RequestSet include Gem::TSort ## # Array of gems to install even if already installed attr_accessor :always_install attr_reader :dependencies attr_accessor :development ## # Errors fetching gems during resolution. attr_reader :errors ## # Set to true if you want to install only direct development dependencies. attr_accessor :development_shallow ## # The set of git gems imported via load_gemdeps. attr_reader :git_set # :nodoc: ## # When true, dependency resolution is not performed, only the requested gems # are installed. attr_accessor :ignore_dependencies attr_reader :install_dir # :nodoc: ## # If true, allow dependencies to match prerelease gems. attr_accessor :prerelease ## # When false no remote sets are used for resolving gems. attr_accessor :remote attr_reader :resolver # :nodoc: ## # Sets used for resolution attr_reader :sets # :nodoc: ## # Treat missing dependencies as silent errors attr_accessor :soft_missing ## # The set of vendor gems imported via load_gemdeps. attr_reader :vendor_set # :nodoc: ## # The set of source gems imported via load_gemdeps. attr_reader :source_set ## # Creates a RequestSet for a list of Gem::Dependency objects, +deps+. You # can then #resolve and #install the resolved list of dependencies. # # nokogiri = Gem::Dependency.new 'nokogiri', '~> 1.6' # pg = Gem::Dependency.new 'pg', '~> 0.14' # # set = Gem::RequestSet.new nokogiri, pg def initialize(*deps) @dependencies = deps @always_install = [] @conservative = false @dependency_names = {} @development = false @development_shallow = false @errors = [] @git_set = nil @ignore_dependencies = false @install_dir = Gem.dir @prerelease = false @remote = true @requests = [] @sets = [] @soft_missing = false @sorted_requests = nil @specs = nil @vendor_set = nil @source_set = nil yield self if block_given? end ## # Declare that a gem of name +name+ with +reqs+ requirements is needed. def gem(name, *reqs) if dep = @dependency_names[name] dep.requirement.concat reqs else dep = Gem::Dependency.new name, *reqs @dependency_names[name] = dep @dependencies << dep end end ## # Add +deps+ Gem::Dependency objects to the set. def import(deps) @dependencies.concat deps end ## # Installs gems for this RequestSet using the Gem::Installer +options+. # # If a +block+ is given an activation +request+ and +installer+ are yielded. # The +installer+ will be +nil+ if a gem matching the request was already # installed. def install(options, &block) # :yields: request, installer if dir = options[:install_dir] requests = install_into dir, false, options, &block return requests end @prerelease = options[:prerelease] requests = [] download_queue = Thread::Queue.new # Create a thread-safe list of gems to download sorted_requests.each do |req| download_queue << req end # Create N threads in a pool, have them download all the gems threads = Array.new(Gem.configuration.concurrent_downloads) do # When a thread pops this item, it knows to stop running. The symbol # is queued here so that there will be one symbol per thread. download_queue << :stop Thread.new do # The pop method will block waiting for items, so the only way # to stop a thread from running is to provide a final item that # means the thread should stop. while req = download_queue.pop break if req == :stop req.spec.download options unless req.installed? end end end # Wait for all the downloads to finish before continuing threads.each(&:value) # Install requested gems after they have been downloaded sorted_requests.each do |req| if req.installed? && @always_install.none? {|spec| spec == req.spec.spec } req.spec.spec.build_extensions yield req, nil if block_given? next end spec = begin req.spec.install options do |installer| yield req, installer if block_given? end rescue Gem::RuntimeRequirementNotMetError => e suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems" suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec) e.suggestion = suggestion raise end requests << spec end return requests if options[:gemdeps] install_hooks requests, options requests end ## # Installs from the gem dependencies files in the +:gemdeps+ option in # +options+, yielding to the +block+ as in #install. # # If +:without_groups+ is given in the +options+, those groups in the gem # dependencies file are not used. See Gem::Installer for other +options+. def install_from_gemdeps(options, &block) gemdeps = options[:gemdeps] @install_dir = options[:install_dir] || Gem.dir @prerelease = options[:prerelease] @remote = options[:domain] != :local @conservative = true if options[:conservative] gem_deps_api = load_gemdeps gemdeps, options[:without_groups], true resolve if options[:explain] puts "Gems to install:" sorted_requests.each do |spec| puts " #{spec.full_name}" end if Gem.configuration.really_verbose @resolver.stats.display end else installed = install options, &block if options.fetch :lock, true lockfile = Gem::RequestSet::Lockfile.build self, gemdeps, gem_deps_api.dependencies lockfile.write end installed end end def install_into(dir, force = true, options = {}) gem_home = ENV["GEM_HOME"] ENV["GEM_HOME"] = dir existing = force ? [] : specs_in(dir) existing.delete_if {|s| @always_install.include? s } dir = File.expand_path dir installed = [] options[:development] = false options[:install_dir] = dir options[:only_install_dir] = true @prerelease = options[:prerelease] sorted_requests.each do |request| spec = request.spec if existing.find {|s| s.full_name == spec.full_name } yield request, nil if block_given? next end spec.install options do |installer| yield request, installer if block_given? end installed << request end install_hooks installed, options installed ensure ENV["GEM_HOME"] = gem_home end ## # Call hooks on installed gems def install_hooks(requests, options) specs = requests.map do |request| case request when Gem::Resolver::ActivationRequest then request.spec.spec else request end end require_relative "dependency_installer" inst = Gem::DependencyInstaller.new options inst.installed_gems.replace specs Gem.done_installing_hooks.each do |hook| hook.call inst, specs end unless Gem.done_installing_hooks.empty? end ## # Load a dependency management file. def load_gemdeps(path, without_groups = [], installing = false) @git_set = Gem::Resolver::GitSet.new @vendor_set = Gem::Resolver::VendorSet.new @source_set = Gem::Resolver::SourceSet.new @git_set.root_dir = @install_dir lock_file = "#{File.expand_path(path)}.lock" begin tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file lock_file parser = tokenizer.make_parser self, [] parser.parse rescue Errno::ENOENT end gf = Gem::RequestSet::GemDependencyAPI.new self, path gf.installing = installing gf.without_groups = without_groups if without_groups gf.load end def pretty_print(q) # :nodoc: q.group 2, "[RequestSet:", "]" do q.breakable if @remote q.text "remote" q.breakable end if @prerelease q.text "prerelease" q.breakable end if @development_shallow q.text "shallow development" q.breakable elsif @development q.text "development" q.breakable end if @soft_missing q.text "soft missing" end q.group 2, "[dependencies:", "]" do q.breakable @dependencies.map do |dep| q.text dep.to_s q.breakable end end q.breakable q.text "sets:" q.breakable q.pp @sets.map(&:class) end end ## # Resolve the requested dependencies and return an Array of Specification # objects to be activated. def resolve(set = Gem::Resolver::BestSet.new) @sets << set @sets << @git_set @sets << @vendor_set @sets << @source_set set = Gem::Resolver.compose_sets(*@sets) set.remote = @remote set.prerelease = @prerelease resolver = Gem::Resolver.new @dependencies, set resolver.development = @development resolver.development_shallow = @development_shallow resolver.ignore_dependencies = @ignore_dependencies resolver.soft_missing = @soft_missing if @conservative installed_gems = {} Gem::Specification.find_all do |spec| (installed_gems[spec.name] ||= []) << spec end resolver.skip_gems = installed_gems end @resolver = resolver @requests = resolver.resolve @errors = set.errors @requests end ## # Resolve the requested dependencies against the gems available via Gem.path # and return an Array of Specification objects to be activated. def resolve_current resolve Gem::Resolver::CurrentSet.new end def sorted_requests @sorted_requests ||= strongly_connected_components.flatten end def specs @specs ||= @requests.map(&:full_spec) end def specs_in(dir) Gem::Util.glob_files_in_dir("*.gemspec", File.join(dir, "specifications")).map do |g| Gem::Specification.load g end end def tsort_each_node(&block) # :nodoc: @requests.each(&block) end def tsort_each_child(node) # :nodoc: node.spec.dependencies.each do |dep| next if dep.type == :development && !@development match = @requests.find do |r| dep.match?(r.spec.name, r.spec.version, r.spec.is_a?(Gem::Resolver::InstalledSpecification) || @prerelease) end unless match next if dep.type == :development && @development_shallow next if @soft_missing raise Gem::DependencyError, "Unresolved dependency found during sorting - #{dep} (requested by #{node.spec.full_name})" end yield match end end end require_relative "request_set/gem_dependency_api" require_relative "request_set/lockfile" require_relative "request_set/lockfile/tokenizer" PK!ov66 version.rbnu[# frozen_string_literal: true ## # The Version class processes string versions into comparable # values. A version string should normally be a series of numbers # separated by periods. Each part (digits separated by periods) is # considered its own number, and these are used for sorting. So for # instance, 3.10 sorts higher than 3.2 because ten is greater than # two. # # If any part contains letters (currently only a-z are supported) then # that version is considered prerelease. Versions with a prerelease # part in the Nth part sort less than versions with N-1 # parts. Prerelease parts are sorted alphabetically using the normal # Ruby string sorting rules. If a prerelease part contains both # letters and numbers, it will be broken into multiple parts to # provide expected sort behavior (1.0.a10 becomes 1.0.a.10, and is # greater than 1.0.a9). # # Prereleases sort between real releases (newest to oldest): # # 1. 1.0 # 2. 1.0.b1 # 3. 1.0.a.2 # 4. 0.9 # # If you want to specify a version restriction that includes both prereleases # and regular releases of the 1.x series this is the best way: # # s.add_dependency 'example', '>= 1.0.0.a', '< 2.0.0' # # == How Software Changes # # Users expect to be able to specify a version constraint that gives them # some reasonable expectation that new versions of a library will work with # their software if the version constraint is true, and not work with their # software if the version constraint is false. In other words, the perfect # system will accept all compatible versions of the library and reject all # incompatible versions. # # Libraries change in 3 ways (well, more than 3, but stay focused here!). # # 1. The change may be an implementation detail only and have no effect on # the client software. # 2. The change may add new features, but do so in a way that client software # written to an earlier version is still compatible. # 3. The change may change the public interface of the library in such a way # that old software is no longer compatible. # # Some examples are appropriate at this point. Suppose I have a Stack class # that supports a push and a pop method. # # === Examples of Category 1 changes: # # * Switch from an array based implementation to a linked-list based # implementation. # * Provide an automatic (and transparent) backing store for large stacks. # # === Examples of Category 2 changes might be: # # * Add a depth method to return the current depth of the stack. # * Add a top method that returns the current top of stack (without # changing the stack). # * Change push so that it returns the item pushed (previously it # had no usable return value). # # === Examples of Category 3 changes might be: # # * Changes pop so that it no longer returns a value (you must use # top to get the top of the stack). # * Rename the methods to push_item and pop_item. # # == RubyGems Rational Versioning # # * Versions shall be represented by three non-negative integers, separated # by periods (e.g. 3.1.4). The first integers is the "major" version # number, the second integer is the "minor" version number, and the third # integer is the "build" number. # # * A category 1 change (implementation detail) will increment the build # number. # # * A category 2 change (backwards compatible) will increment the minor # version number and reset the build number. # # * A category 3 change (incompatible) will increment the major build number # and reset the minor and build numbers. # # * Any "public" release of a gem should have a different version. Normally # that means incrementing the build number. This means a developer can # generate builds all day long, but as soon as they make a public release, # the version must be updated. # # === Examples # # Let's work through a project lifecycle using our Stack example from above. # # Version 0.0.1:: The initial Stack class is release. # Version 0.0.2:: Switched to a linked=list implementation because it is # cooler. # Version 0.1.0:: Added a depth method. # Version 1.0.0:: Added top and made pop return nil # (pop used to return the old top item). # Version 1.1.0:: push now returns the value pushed (it used it # return nil). # Version 1.1.1:: Fixed a bug in the linked list implementation. # Version 1.1.2:: Fixed a bug introduced in the last fix. # # Client A needs a stack with basic push/pop capability. They write to the # original interface (no top), so their version constraint looks like: # # gem 'stack', '>= 0.0' # # Essentially, any version is OK with Client A. An incompatible change to # the library will cause them grief, but they are willing to take the chance # (we call Client A optimistic). # # Client B is just like Client A except for two things: (1) They use the # depth method and (2) they are worried about future # incompatibilities, so they write their version constraint like this: # # gem 'stack', '~> 0.1' # # The depth method was introduced in version 0.1.0, so that version # or anything later is fine, as long as the version stays below version 1.0 # where incompatibilities are introduced. We call Client B pessimistic # because they are worried about incompatible future changes (it is OK to be # pessimistic!). # # == Preventing Version Catastrophe: # # From: https://www.zenspider.com/ruby/2008/10/rubygems-how-to-preventing-catastrophe.html # # Let's say you're depending on the fnord gem version 2.y.z. If you # specify your dependency as ">= 2.0.0" then, you're good, right? What # happens if fnord 3.0 comes out and it isn't backwards compatible # with 2.y.z? Your stuff will break as a result of using ">=". The # better route is to specify your dependency with an "approximate" version # specifier ("~>"). They're a tad confusing, so here is how the dependency # specifiers work: # # Specification From ... To (exclusive) # ">= 3.0" 3.0 ... ∞ # "~> 3.0" 3.0 ... 4.0 # "~> 3.0.0" 3.0.0 ... 3.1 # "~> 3.5" 3.5 ... 4.0 # "~> 3.5.0" 3.5.0 ... 3.6 # "~> 3" 3.0 ... 4.0 # # For the last example, single-digit versions are automatically extended with # a zero to give a sensible result. # Workaround for directly loading Gem::Version in some cases module Gem; end class Gem::Version include Comparable VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc: ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc: RADIX_OPT = [9_500, 3_500, 260_000, 22_227, 24].freeze # :nodoc: ## # A string representation of this Version. def version @version end alias_method :to_s, :version ## # True if the +version+ string matches RubyGems' requirements. def self.correct?(version) version.nil? || ANCHORED_VERSION_PATTERN.match?(version.to_s) end ## # Factory method to create a Version object. Input may be a Version # or a String. Intended to simplify client code. # # ver1 = Version.create('1.3.17') # -> (Version object) # ver2 = Version.create(ver1) # -> (ver1) def self.create(input) if self === input # check yourself before you wreck yourself input else new input end end @@all = {} @@bump = {} @@release = {} def self.new(version) # :nodoc: return super unless self == Gem::Version @@all[version] ||= super end ## # Constructs a Version from the +version+ string. A version string is a # series of digits or ASCII letters separated by dots. def initialize(version) unless self.class.correct?(version) raise ArgumentError, "Malformed version number string #{version}" end # If version is an empty string convert it to 0 version = 0 if version.nil? || (version.is_a?(String) && /\A\s*\Z/.match?(version)) @version = version.to_s # optimization to avoid allocation when given an integer, since we know # it's to_s won't have any spaces or dashes unless version.is_a?(Integer) @version = @version.strip @version.gsub!("-",".pre.") end @version = -@version @segments = nil @sort_key = compute_sort_key end ## # Return a new version object where the next to the last revision # number is one greater (e.g., 5.3.1 => 5.4). # # Pre-release (alpha) parts, e.g, 5.3.1.b.2 => 5.4, are ignored. def bump @@bump[self] ||= begin segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop if segments.size > 1 segments[-1] = segments[-1].succ self.class.new segments.join(".") end end ## # A Version is only eql? to another version if it's specified to the # same precision. Version "1.0" is not the same as version "1". def eql?(other) self.class === other && @version == other.version end def hash # :nodoc: canonical_segments.hash end def init_with(coder) # :nodoc: yaml_initialize coder.tag, coder.map end def inspect # :nodoc: "#<#{self.class} #{version.inspect}>" end ## # Dump only the raw version string, not the complete object. It's a # string for backwards (RubyGems 1.3.5 and earlier) compatibility. def marshal_dump [@version] end ## # Load custom marshal format. It's a string for backwards (RubyGems # 1.3.5 and earlier) compatibility. def marshal_load(array) string = array[0] raise TypeError, "wrong version string" unless string.is_a?(String) initialize string end def yaml_initialize(tag, map) # :nodoc: @version = -map["version"] @segments = nil @hash = nil end def encode_with(coder) # :nodoc: coder.add "version", @version end ## # A version is considered a prerelease if it contains a letter. def prerelease? unless instance_variable_defined? :@prerelease @prerelease = /[a-zA-Z]/.match?(version) end @prerelease end def pretty_print(q) # :nodoc: q.text "Gem::Version.new(#{version.inspect})" end ## # The release for this version (e.g. 1.2.0.a -> 1.2.0). # Non-prerelease versions return themselves. def release @@release[self] ||= if prerelease? segments = self.segments segments.pop while segments.any? {|s| String === s } self.class.new segments.join(".") else self end end def segments # :nodoc: _segments.dup end ## # A recommended version for use with a ~> Requirement. def approximate_recommendation segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 recommendation = "~> #{segments.join(".")}" recommendation += ".a" if prerelease? recommendation end ## # Compares this version with +other+ returning -1, 0, or 1 if the # other version is larger, the same, or smaller than this # one. +other+ must be an instance of Gem::Version, comparing with # other types may raise an exception. def <=>(other) if Gem::Version === other # Fast path for comparison when available. if @sort_key && other.sort_key return @sort_key <=> other.sort_key end return 0 if @version == other.version || canonical_segments == other.canonical_segments lhsegments = canonical_segments rhsegments = other.canonical_segments lhsize = lhsegments.size rhsize = rhsegments.size limit = (lhsize > rhsize ? rhsize : lhsize) i = 0 while i < limit lhs = lhsegments[i] rhs = rhsegments[i] i += 1 next if lhs == rhs return -1 if String === lhs && Numeric === rhs return 1 if Numeric === lhs && String === rhs return lhs <=> rhs end lhs = lhsegments[i] if lhs.nil? rhs = rhsegments[i] while i < rhsize return 1 if String === rhs return -1 unless rhs.zero? rhs = rhsegments[i += 1] end else while i < lhsize return -1 if String === lhs return 1 unless lhs.zero? lhs = lhsegments[i += 1] end end 0 elsif String === other return unless self.class.correct?(other) self <=> self.class.new(other) end end # remove trailing zeros segments before first letter or at the end of the version def canonical_segments @canonical_segments ||= begin # remove trailing 0 segments, using dot or letter as anchor # may leave a trailing dot which will be ignored by partition_segments canonical_version = @version.sub(/(?<=[a-zA-Z.])[.0]+\z/, "") # remove 0 segments before the first letter in a prerelease version canonical_version.sub!(/(?<=\.|\A)[0.]+(?=[a-zA-Z])/, "") if prerelease? partition_segments(canonical_version) end end def freeze prerelease? _segments canonical_segments super end protected attr_reader :sort_key # :nodoc: def compute_sort_key return if prerelease? segments = canonical_segments return if segments.size > 5 key = 0 RADIX_OPT.each_with_index do |radix, i| seg = segments.fetch(i, 0) return nil if seg >= radix key = key * radix + seg end key end def _segments # segments is lazy so it can pick up version values that come from # old marshaled versions, which don't go through marshal_load. # since this version object is cached in @@all, its @segments should be frozen @segments ||= partition_segments(@version) end def partition_segments(ver) ver.scan(/\d+|[a-z]+/i).map! do |s| /\A\d/.match?(s) ? s.to_i : -s end.freeze end end PK!wDDtext.rbnu[# frozen_string_literal: true ## # A collection of text-wrangling methods module Gem::Text ## # Remove any non-printable characters and make the text suitable for # printing. def clean_text(text) text.gsub(/[\000-\b\v-\f\016-\037\177]/, ".") end def truncate_text(text, description, max_length = 100_000) raise ArgumentError, "max_length must be positive" unless max_length > 0 return text if text.size <= max_length "Truncating #{description} to #{max_length.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse} characters:\n" + text[0, max_length] end ## # Wraps +text+ to +wrap+ characters and optionally indents by +indent+ # characters def format_text(text, wrap, indent = 0) result = [] work = clean_text(text) while work.length > wrap do if work =~ /^(.{0,#{wrap}})[ \n]/ result << $1.rstrip work.slice!(0, $&.length) else result << work.slice!(0, wrap) end end result << work if work.length.nonzero? result.join("\n").gsub(/^/, " " * indent) end def min3(a, b, c) # :nodoc: if a < b && a < c a elsif b < c b else c end end # Returns a value representing the "cost" of transforming str1 into str2 # Vendored version of DidYouMean::Levenshtein.distance from the ruby/did_you_mean gem @ 1.4.0 # https://github.com/ruby/did_you_mean/blob/2ddf39b874808685965dbc47d344cf6c7651807c/lib/did_you_mean/levenshtein.rb#L7-L37 def levenshtein_distance(str1, str2) n = str1.length m = str2.length return m if n.zero? return n if m.zero? d = (0..m).to_a x = nil # to avoid duplicating an enumerable object, create it outside of the loop str2_codepoints = str2.codepoints str1.each_codepoint.with_index(1) do |char1, i| j = 0 while j < m cost = char1 == str2_codepoints[j] ? 0 : 1 x = min3( d[j + 1] + 1, # insertion i + 1, # deletion d[j] + cost # substitution ) d[j] = i i = x j += 1 end d[m] = x end x end end PK!`W request/connection_pools.rbnu[# frozen_string_literal: true class Gem::Request::ConnectionPools # :nodoc: @client = Gem::Net::HTTP class << self attr_accessor :client end def initialize(proxy_uri, cert_files, pool_size = 1) @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} @pool_mutex = Thread::Mutex.new @pool_size = pool_size end def pool_for(uri) http_args = net_http_args(uri, @proxy_uri) key = http_args + [https?(uri)] @pool_mutex.synchronize do @pools[key] ||= if https? uri Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri, @pool_size) else Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri, @pool_size) end end end def close_all @pools.each_value(&:close_all) end private ## # Returns list of no_proxy entries (if any) from the environment def get_no_proxy_from_env env_no_proxy = ENV["no_proxy"] || ENV["NO_PROXY"] return [] if env_no_proxy.nil? || env_no_proxy.empty? env_no_proxy.split(/\s*,\s*/) end def https?(uri) uri.scheme.casecmp("https").zero? end def no_proxy?(host, env_no_proxy) host = host.downcase env_no_proxy.any? do |pattern| env_no_proxy_pattern = pattern.downcase.dup # Remove dot in front of pattern for wildcard matching env_no_proxy_pattern[0] = "" if env_no_proxy_pattern[0] == "." host_tokens = host.split(".") pattern_tokens = env_no_proxy_pattern.split(".") intersection = (host_tokens - pattern_tokens) | (pattern_tokens - host_tokens) # When we do the split into tokens we miss a dot character, so add it back if we need it missing_dot = intersection.length > 0 ? 1 : 0 start = intersection.join(".").size + missing_dot no_proxy_host = host[start..-1] env_no_proxy_pattern == no_proxy_host end end def net_http_args(uri, proxy_uri) hostname = uri.hostname net_http_args = [hostname, uri.port] no_proxy = get_no_proxy_from_env if proxy_uri && !no_proxy?(hostname, no_proxy) proxy_hostname = proxy_uri.respond_to?(:hostname) ? proxy_uri.hostname : proxy_uri.host net_http_args + [ proxy_hostname, proxy_uri.port, Gem::UriFormatter.new(proxy_uri.user).unescape, Gem::UriFormatter.new(proxy_uri.password).unescape, ] elsif no_proxy? hostname, no_proxy net_http_args + [nil, nil] else net_http_args end end end PK!fޟrequest/https_pool.rbnu[# frozen_string_literal: true class Gem::Request::HTTPSPool < Gem::Request::HTTPPool # :nodoc: private def setup_connection(connection) Gem::Request.configure_connection_for_https(connection, @cert_files) super end end PK!R~~request/http_pool.rbnu[# frozen_string_literal: true ## # A connection "pool" that only manages one connection for now. Provides # thread safe `checkout` and `checkin` methods. The pool consists of one # connection that corresponds to `http_args`. This class is private, do not # use it. class Gem::Request::HTTPPool # :nodoc: attr_reader :cert_files, :proxy_uri def initialize(http_args, cert_files, proxy_uri, pool_size) @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri @pool_size = pool_size @queue = Thread::SizedQueue.new @pool_size setup_queue end def checkout @queue.pop || make_connection end def checkin(connection) @queue.push connection end def close_all until @queue.empty? if (connection = @queue.pop(true)) && connection.started? connection.finish end end setup_queue end private def make_connection setup_connection Gem::Request::ConnectionPools.client.new(*@http_args) end def setup_connection(connection) connection.start connection end def setup_queue @pool_size.times { @queue.push(nil) } end end PK!&k22 platform.rbnu[# frozen_string_literal: true ## # Available list of platforms for targeting Gem installations. # # See `gem help platform` for information on platform matching. class Gem::Platform @local = nil attr_accessor :cpu, :os, :version def self.local(refresh: false) return @local if @local && !refresh @local = begin arch = Gem.target_rbconfig["arch"] arch = "#{arch}_60" if /mswin(?:32|64)$/.match?(arch) new(arch) end end def self.match_platforms?(platform, platforms) platform = Gem::Platform.new(platform) unless platform.is_a?(Gem::Platform) platforms.any? do |local_platform| platform.nil? || local_platform == platform || (local_platform != Gem::Platform::RUBY && platform =~ local_platform) end end private_class_method :match_platforms? def self.match_spec?(spec) match_gem?(spec.platform, spec.name) end if RUBY_ENGINE == "truffleruby" def self.match_gem?(platform, gem_name) raise "Not a string: #{gem_name.inspect}" unless String === gem_name if REUSE_AS_BINARY_ON_TRUFFLERUBY.include?(gem_name) match_platforms?(platform, [Gem::Platform::RUBY, Gem::Platform.local]) else match_platforms?(platform, Gem.platforms) end end else def self.match_gem?(platform, gem_name) match_platforms?(platform, Gem.platforms) end end def self.sort_priority(platform) platform == Gem::Platform::RUBY ? -1 : 1 end def self.installable?(spec) if spec.respond_to? :installable_platform? spec.installable_platform? else match_spec? spec end end def self.new(arch) # :nodoc: case arch when Gem::Platform::CURRENT then Gem::Platform.local when Gem::Platform::RUBY, nil, "" then Gem::Platform::RUBY else super end end def initialize(arch) case arch when Array then @cpu, @os, @version = arch when String then cpu, os = arch.sub(/-+$/, "").split("-", 2) @cpu = if cpu&.match?(/i\d86/) "x86" else cpu end if os.nil? @cpu = nil os = cpu end # legacy jruby @os, @version = case os when /aix-?(\d+)?/ then ["aix", $1] when /cygwin/ then ["cygwin", nil] when /darwin-?(\d+)?/ then ["darwin", $1] when "macruby" then ["macruby", nil] when /^macruby-?(\d+(?:\.\d+)*)?/ then ["macruby", $1] when /freebsd-?(\d+)?/ then ["freebsd", $1] when "java", "jruby" then ["java", nil] when /^java-?(\d+(?:\.\d+)*)?/ then ["java", $1] when /^dalvik-?(\d+)?$/ then ["dalvik", $1] when /^dotnet$/ then ["dotnet", nil] when /^dotnet-?(\d+(?:\.\d+)*)?/ then ["dotnet", $1] when /linux-?(\w+)?/ then ["linux", $1] when /mingw32/ then ["mingw32", nil] when /mingw-?(\w+)?/ then ["mingw", $1] when /(mswin\d+)(?:[_-](\d+))?/ then os = $1 version = $2 @cpu = "x86" if @cpu.nil? && os.end_with?("32") [os, version] when /netbsdelf/ then ["netbsdelf", nil] when /openbsd-?(\d+\.\d+)?/ then ["openbsd", $1] when /solaris-?(\d+\.\d+)?/ then ["solaris", $1] when /wasi/ then ["wasi", nil] # test when /^(\w+_platform)-?(\d+)?/ then [$1, $2] else ["unknown", nil] end when Gem::Platform then @cpu = arch.cpu @os = arch.os @version = arch.version else raise ArgumentError, "invalid argument #{arch.inspect}" end end def to_a [@cpu, @os, @version] end def to_s to_a.compact.join(@cpu.nil? ? "" : "-") end ## # Deconstructs the platform into an array for pattern matching. # Returns [cpu, os, version]. # # Gem::Platform.new("x86_64-linux").deconstruct #=> ["x86_64", "linux", nil] # # This enables array pattern matching: # # case Gem::Platform.new("arm64-darwin-21") # in ["arm64", "darwin", version] # # version => "21" # end alias_method :deconstruct, :to_a ## # Deconstructs the platform into a hash for pattern matching. # Returns a hash with keys +:cpu+, +:os+, and +:version+. # # Gem::Platform.new("x86_64-darwin-20").deconstruct_keys(nil) # #=> { cpu: "x86_64", os: "darwin", version: "20" } # # This enables hash pattern matching: # # case Gem::Platform.new("x86_64-linux") # in cpu: "x86_64", os: "linux" # # Matches Linux on x86_64 # end def deconstruct_keys(keys) { cpu: @cpu, os: @os, version: @version } end ## # Is +other+ equal to this platform? Two platforms are equal if they have # the same CPU, OS and version. def ==(other) self.class === other && to_a == other.to_a end alias_method :eql?, :== def hash # :nodoc: to_a.hash end ## # Does +other+ match this platform? Two platforms match if they have the # same CPU, or either has a CPU of 'universal', they have the same OS, and # they have the same version, or either one has no version # # Additionally, the platform will match if the local CPU is 'arm' and the # other CPU starts with "armv" (for generic 32-bit ARM family support). # # Of note, this method is not commutative. Indeed the OS 'linux' has a # special case: the version is the libc name, yet while "no version" stands # as a wildcard for a binary gem platform (as for other OSes), for the # runtime platform "no version" stands for 'gnu'. To be able to distinguish # these, the method receiver is the gem platform, while the argument is # the runtime platform. # #-- # NOTE: Until it can be removed, changes to this method must also be reflected in `bundler/lib/bundler/rubygems_ext.rb` def ===(other) return nil unless Gem::Platform === other # universal-mingw32 matches x64-mingw-ucrt return true if (@cpu == "universal" || other.cpu == "universal") && @os.start_with?("mingw") && other.os.start_with?("mingw") # cpu ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu || (@cpu == "arm" && other.cpu.start_with?("armv"))) && # os @os == other.os && # version ( (@os != "linux" && (@version.nil? || other.version.nil?)) || (@os == "linux" && (normalized_linux_version == other.normalized_linux_version || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) || @version == other.version ) end #-- # NOTE: Until it can be removed, changes to this method must also be reflected in `bundler/lib/bundler/rubygems_ext.rb` def normalized_linux_version return nil unless @version without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "") return nil if without_gnu_nor_abi_modifiers.empty? without_gnu_nor_abi_modifiers end ## # Does +other+ match this platform? If +other+ is a String it will be # converted to a Gem::Platform first. See #=== for matching rules. def =~(other) case other when Gem::Platform then # nop when String then # This data is from http://gems.rubyforge.org/gems/yaml on 19 Aug 2007 other = case other when /^i686-darwin(\d)/ then ["x86", "darwin", $1] when /^i\d86-linux/ then ["x86", "linux", nil] when "java", "jruby" then [nil, "java", nil] when /^dalvik(\d+)?$/ then [nil, "dalvik", $1] when /dotnet(\-(\d+\.\d+))?/ then ["universal","dotnet", $2] when /mswin32(\_(\d+))?/ then ["x86", "mswin32", $2] when /mswin64(\_(\d+))?/ then ["x64", "mswin64", $2] when "powerpc-darwin" then ["powerpc", "darwin", nil] when /powerpc-darwin(\d)/ then ["powerpc", "darwin", $1] when /sparc-solaris2.8/ then ["sparc", "solaris", "2.8"] when /universal-darwin(\d)/ then ["universal", "darwin", $1] else other end other = Gem::Platform.new other else return nil end self === other end ## # A pure-Ruby gem that may use Gem::Specification#extensions to build # binary files. RUBY = "ruby" ## # A platform-specific gem that is built for the packaging Ruby's platform. # This will be replaced with Gem::Platform::local. CURRENT = "current" JAVA = Gem::Platform.new("java") # :nodoc: MSWIN = Gem::Platform.new("mswin32") # :nodoc: MSWIN64 = Gem::Platform.new("mswin64") # :nodoc: MINGW = Gem::Platform.new("x86-mingw32") # :nodoc: X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc: X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc: UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc: WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc: X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc: X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc: GENERICS = [JAVA, *WINDOWS].freeze # :nodoc: private_constant :GENERICS GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc: private_constant :GENERIC_CACHE class << self ## # Returns the generic platform for the given platform. def generic(platform) return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY GENERIC_CACHE[platform] ||= begin found = GENERICS.find do |match| platform === match end found || Gem::Platform::RUBY end end ## # Returns the platform specificity match for the given spec platform and user platform. def platform_specificity_match(spec_platform, user_platform) return -1 if spec_platform == user_platform return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY os_match(spec_platform, user_platform) + cpu_match(spec_platform, user_platform) * 10 + version_match(spec_platform, user_platform) * 100 end ## # Sorts and filters the best platform match for the given matching specs and platform. def sort_and_filter_best_platform_match(matching, platform) return matching if matching.one? exact = matching.select {|spec| spec.platform == platform } return exact if exact.any? sorted_matching = sort_best_platform_match(matching, platform) exemplary_spec = sorted_matching.first sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) } end ## # Sorts the best platform match for the given matching specs and platform. def sort_best_platform_match(matching, platform) matching.sort_by.with_index do |spec, i| [ platform_specificity_match(spec.platform, platform), i, # for stable sort ] end end private def same_specificity?(platform, spec, exemplary_spec) platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) end def same_deps?(spec, exemplary_spec) spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version && spec.dependencies.sort == exemplary_spec.dependencies.sort end def os_match(spec_platform, user_platform) if spec_platform.os == user_platform.os 0 else 1 end end def cpu_match(spec_platform, user_platform) if spec_platform.cpu == user_platform.cpu 0 elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") 0 elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" 1 else 2 end end def version_match(spec_platform, user_platform) if spec_platform.version == user_platform.version 0 elsif spec_platform.version.nil? 1 else 2 end end end end PK!6 !!query_utils.rbnu[# frozen_string_literal: true require_relative "local_remote_options" require_relative "spec_fetcher" require_relative "version_option" require_relative "text" module Gem::QueryUtils include Gem::Text include Gem::LocalRemoteOptions include Gem::VersionOption def add_query_options add_option("-i", "--[no-]installed", "Check for installed gem") do |value, options| options[:installed] = value end add_option("-I", "Equivalent to --no-installed") do |_value, options| options[:installed] = false end add_version_option command, "for use with --installed" add_option("-d", "--[no-]details", "Display detailed information of gem(s)") do |value, options| options[:details] = value end add_option("--[no-]versions", "Display only gem names") do |value, options| options[:versions] = value options[:details] = false unless value end add_option("-a", "--all", "Display all gem versions") do |value, options| options[:all] = value end add_option("-e", "--exact", "Name of gem(s) to query on matches the", "provided STRING") do |value, options| options[:exact] = value end add_option("--[no-]prerelease", "Display prerelease versions") do |value, options| options[:prerelease] = value end add_local_remote_options end def defaults_str # :nodoc: "--local --no-details --versions --no-installed" end def execute gem_names = if args.empty? [options[:name]] else options[:exact] ? args.map {|arg| /\A#{Regexp.escape(arg)}\Z/ } : args.map {|arg| /#{arg}/i } end terminate_interaction(check_installed_gems(gem_names)) if check_installed_gems? gem_names.each {|n| show_gems(n) } end private def check_installed_gems(gem_names) exit_code = 0 if args.empty? && !gem_name? alert_error "You must specify a gem name" exit_code = 4 elsif gem_names.count > 1 alert_error "You must specify only ONE gem!" exit_code = 4 else installed = installed?(gem_names.first, options[:version]) installed = !installed unless options[:installed] say(installed) exit_code = 1 unless installed end exit_code end def check_installed_gems? !options[:installed].nil? end def gem_name? !options[:name].nil? end def prerelease options[:prerelease] end def show_prereleases? prerelease.nil? || prerelease end def args options[:args].to_a end def display_header(type) if (ui.outs.tty? && Gem.configuration.verbose) || both? say say "*** #{type} GEMS ***" say end end # Guts of original execute def show_gems(name) show_local_gems(name) if local? show_remote_gems(name) if remote? end def show_local_gems(name, req = Gem::Requirement.default) display_header("LOCAL") specs = Gem::Specification.find_all do |s| name_matches = name ? s.name =~ name : true version_matches = show_prereleases? || !s.version.prerelease? name_matches && version_matches end.uniq(&:full_name) spec_tuples = specs.map do |spec| [spec.name_tuple, spec] end output_query_results(spec_tuples) end def show_remote_gems(name) display_header("REMOTE") fetcher = Gem::SpecFetcher.fetcher spec_tuples = if name.nil? fetcher.detect(specs_type) { true } else fetcher.detect(specs_type) do |name_tuple| name === name_tuple.name && options[:version].satisfied_by?(name_tuple.version) end end output_query_results(spec_tuples) end def specs_type if options[:all] || options[:version].specific? if options[:prerelease] :complete else :released end elsif options[:prerelease] :prerelease else :latest end end ## # Check if gem +name+ version +version+ is installed. def installed?(name, req = Gem::Requirement.default) Gem::Specification.any? {|s| s.name =~ name && req =~ s.version } end def output_query_results(spec_tuples) output = [] versions = Hash.new {|h,name| h[name] = [] } spec_tuples.each do |spec_tuple, source| versions[spec_tuple.name] << [spec_tuple, source] end versions = versions.sort_by do |(n,_),_| n.downcase end output_versions output, versions say output.join(options[:details] ? "\n\n" : "\n") end def output_versions(output, versions) versions.each do |_gem_name, matching_tuples| matching_tuples = matching_tuples.sort_by {|n,_| n.version }.reverse platforms = Hash.new {|h,version| h[version] = [] } matching_tuples.each do |n, _| platforms[n.version] << n.platform if n.platform end seen = {} matching_tuples.delete_if do |n,_| if seen[n.version] true else seen[n.version] = true false end end output << clean_text(make_entry(matching_tuples, platforms)) end end def entry_details(entry, detail_tuple, specs, platforms) return unless options[:details] name_tuple, spec = detail_tuple spec = spec.fetch_spec(name_tuple)if spec.respond_to?(:fetch_spec) entry << "\n" spec_platforms entry, platforms spec_authors entry, spec spec_homepage entry, spec spec_license entry, spec spec_loaded_from entry, spec, specs spec_summary entry, spec end def entry_versions(entry, name_tuples, platforms, specs) return unless options[:versions] list = if platforms.empty? || options[:details] name_tuples.map(&:version).uniq else platforms.sort.reverse.map do |version, pls| out = version.to_s if options[:domain] == :local default = specs.any? do |s| !s.is_a?(Gem::Source) && s.version == version && s.default_gem? end out = "default: #{out}" if default end if pls != [Gem::Platform::RUBY] platform_list = [pls.delete(Gem::Platform::RUBY), *pls.sort].compact out = platform_list.unshift(out).join(" ") end out end end entry << " (#{list.join ", "})" end def make_entry(entry_tuples, platforms) detail_tuple = entry_tuples.first name_tuples, specs = entry_tuples.flatten.partition do |item| Gem::NameTuple === item end entry = [name_tuples.first.name] entry_versions(entry, name_tuples, platforms, specs) entry_details(entry, detail_tuple, specs, platforms) entry.join end def spec_authors(entry, spec) authors = "Author#{spec.authors.length > 1 ? "s" : ""}: ".dup authors << spec.authors.join(", ") entry << format_text(authors, 68, 4) end def spec_homepage(entry, spec) return if spec.homepage.nil? || spec.homepage.empty? entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4) end def spec_license(entry, spec) return if spec.license.nil? || spec.license.empty? licenses = "License#{spec.licenses.length > 1 ? "s" : ""}: ".dup licenses << spec.licenses.join(", ") entry << "\n" << format_text(licenses, 68, 4) end def spec_loaded_from(entry, spec, specs) return unless spec.loaded_from if specs.length == 1 default = spec.default_gem? ? " (default)" : nil entry << "\n" << " Installed at#{default}: #{spec.base_dir}" else label = "Installed at" specs.each do |s| version = s.version.to_s default = s.default_gem? ? ", default" : "" entry << "\n" << " #{label} (#{version}#{default}): #{s.base_dir}" label = " " * label.length end end end def spec_platforms(entry, platforms) non_ruby = platforms.any? do |_, pls| pls.any? {|pl| pl != Gem::Platform::RUBY } end return unless non_ruby if platforms.length == 1 title = platforms.values.length == 1 ? "Platform" : "Platforms" entry << " #{title}: #{platforms.values.sort.join(", ")}\n" else entry << " Platforms:\n" sorted_platforms = platforms.sort sorted_platforms.each do |version, pls| label = " #{version}: " data = format_text pls.sort.join(", "), 68, label.length data[0, label.length] = label entry << data << "\n" end end end def spec_summary(entry, spec) summary = truncate_text(spec.summary, "the summary for #{spec.full_name}") entry << "\n\n" << format_text(summary, 68, 4) end end PK!bi errors.rbnu[# frozen_string_literal: true #-- # This file contains all the various exceptions and other errors that are used # inside of RubyGems. # # DOC: Confirm _all_ #++ module Gem ## # Raised when RubyGems is unable to load or activate a gem. Contains the # name and version requirements of the gem that either conflicts with # already activated gems or that RubyGems is otherwise unable to activate. class LoadError < ::LoadError # Name of gem attr_accessor :name # Version requirement of gem attr_accessor :requirement end ## # Raised when trying to activate a gem, and that gem does not exist on the # system. Instead of rescuing from this class, make sure to rescue from the # superclass Gem::LoadError to catch all types of load errors. class MissingSpecError < Gem::LoadError def initialize(name, requirement, extra_message = nil) @name = name @requirement = requirement @extra_message = extra_message super(message) end def message # :nodoc: build_message + "Checked in 'GEM_PATH=#{Gem.path.join(File::PATH_SEPARATOR)}' #{@extra_message}, execute `gem env` for more information" end private def build_message total = Gem::Specification.stubs.size "Could not find '#{name}' (#{requirement}) among #{total} total gem(s)\n" end end ## # Raised when trying to activate a gem, and the gem exists on the system, but # not the requested version. Instead of rescuing from this class, make sure to # rescue from the superclass Gem::LoadError to catch all types of load errors. class MissingSpecVersionError < MissingSpecError attr_reader :specs def initialize(name, requirement, specs) @specs = specs super(name, requirement) end private def build_message names = specs.map(&:full_name) "Could not find '#{name}' (#{requirement}) - did find: [#{names.join ","}]\n" end end # Raised when there are conflicting gem specs loaded class ConflictError < LoadError ## # A Hash mapping conflicting specifications to the dependencies that # caused the conflict attr_reader :conflicts ## # The specification that had the conflict attr_reader :target def initialize(target, conflicts) @target = target @conflicts = conflicts @name = target.name reason = conflicts.map do |act, dependencies| "#{act.full_name} conflicts with #{dependencies.join(", ")}" end.join ", " # TODO: improve message by saying who activated `con` super("Unable to activate #{target.full_name}, because #{reason}") end end class ErrorReason; end # Generated when trying to lookup a gem to indicate that the gem # was found, but that it isn't usable on the current platform. # # fetch and install read these and report them to the user to aid # in figuring out why a gem couldn't be installed. # class PlatformMismatch < ErrorReason ## # the name of the gem attr_reader :name ## # the version attr_reader :version ## # The platforms that are mismatched attr_reader :platforms def initialize(name, version) @name = name @version = version @platforms = [] end ## # append a platform to the list of mismatched platforms. # # Platforms are added via this instead of injected via the constructor # so that we can loop over a list of mismatches and just add them rather # than perform some kind of calculation mismatch summary before creation. def add_platform(platform) @platforms << platform end ## # A wordy description of the error. def wordy format("Found %s (%s), but was for platform%s %s", @name, @version, @platforms.size == 1 ? "" : "s", @platforms.join(" ,")) end end ## # An error that indicates we weren't able to fetch some # data from a source class SourceFetchProblem < ErrorReason ## # Creates a new SourceFetchProblem for the given +source+ and +error+. def initialize(source, error) @source = source @error = error end ## # The source that had the fetch problem. attr_reader :source ## # The fetch error which is an Exception subclass. attr_reader :error ## # An English description of the error. def wordy "Unable to download data from #{Gem::Uri.redact(@source.uri)} - #{@error.message}" end ## # The "exception" alias allows you to call raise on a SourceFetchProblem. alias_method :exception, :error end end PK!8'--gemcutter_utilities.rbnu[# frozen_string_literal: true require_relative "remote_fetcher" require_relative "text" require_relative "gemcutter_utilities/webauthn_listener" require_relative "gemcutter_utilities/webauthn_poller" ## # Utility methods for using the RubyGems API. module Gem::GemcutterUtilities ERROR_CODE = 1 API_SCOPES = [:index_rubygems, :push_rubygem, :yank_rubygem, :add_owner, :remove_owner, :access_webhooks].freeze EXCLUSIVELY_API_SCOPES = [:show_dashboard].freeze include Gem::Text attr_writer :host attr_writer :scope ## # Add the --key option def add_key_option add_option("-k", "--key KEYNAME", Symbol, "Use the given API key", "from #{Gem.configuration.credentials_path}") do |value,options| options[:key] = value end end ## # Add the --otp option def add_otp_option add_option("--otp CODE", "Digit code for multifactor authentication", "You can also use the environment variable GEM_HOST_OTP_CODE") do |value, options| options[:otp] = value end end ## # The API key from the command options or from the user's configuration. def api_key if ENV["GEM_HOST_API_KEY"] ENV["GEM_HOST_API_KEY"] elsif options[:key] verify_api_key options[:key] elsif Gem.configuration.api_keys.key?(host) Gem.configuration.api_keys[host] else Gem.configuration.rubygems_api_key end end ## # The OTP code from the command options or from the user's configuration. def otp options[:otp] || ENV["GEM_HOST_OTP_CODE"] end def webauthn_enabled? options[:webauthn] end ## # The host to connect to either from the RUBYGEMS_HOST environment variable # or from the user's configuration def host configured_host = Gem.host unless Gem.configuration.disable_default_gem_server @host ||= begin env_rubygems_host = ENV["RUBYGEMS_HOST"] env_rubygems_host = nil if env_rubygems_host&.empty? env_rubygems_host || configured_host end end ## # Creates an RubyGems API to +host+ and +path+ with the given HTTP +method+. # # If +allowed_push_host+ metadata is present, then it will only allow that host. def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, credentials: {}, &block) require_relative "vendored_net_http" self.host = host if host unless self.host alert_error "You must specify a gem server" terminate_interaction(ERROR_CODE) end if allowed_push_host allowed_host_uri = Gem::URI.parse(allowed_push_host) host_uri = Gem::URI.parse(self.host) unless (host_uri.scheme == allowed_host_uri.scheme) && (host_uri.host == allowed_host_uri.host) alert_error "#{self.host.inspect} is not allowed by the gemspec, which only allows #{allowed_push_host.inspect}" terminate_interaction(ERROR_CODE) end end uri = Gem::URI.parse "#{self.host}/#{path}" response = request_with_otp(method, uri, &block) if mfa_unauthorized?(response) fetch_otp(credentials) response = request_with_otp(method, uri, &block) end if api_key_forbidden?(response) update_scope(scope) request_with_otp(method, uri, &block) else response end end def mfa_unauthorized?(response) response.is_a?(Gem::Net::HTTPUnauthorized) && response.body.start_with?("You have enabled multifactor authentication") end def update_scope(scope) sign_in_host = host pretty_host = pretty_host(sign_in_host) update_scope_params = { scope => true } say "The existing key doesn't have access of #{scope} on #{pretty_host}. Please sign in to update access." identifier = ask "Username/email: " password = ask_for_password " Password: " response = rubygems_api_request(:put, "api/v1/api_key", sign_in_host, scope: scope) do |request| request.basic_auth identifier, password request.body = Gem::URI.encode_www_form({ api_key: api_key }.merge(update_scope_params)) end with_response response do |_resp| say "Added #{scope} scope to the existing API key" end end ## # Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API # key. def sign_in(sign_in_host = nil, scope: nil) sign_in_host ||= host pretty_host = pretty_host(sign_in_host) if api_key say "You are already signed in on #{pretty_host}." return end say "Enter your #{pretty_host} credentials." say "Don't have an account yet? " \ "Create one at #{sign_in_host}/sign_up" identifier = ask "Username/email: " password = ask_for_password " Password: " say "\n" key_name = get_key_name(scope) scope_params = get_scope_params(scope) profile = get_user_profile(identifier, password) mfa_params = get_mfa_params(profile) all_params = scope_params.merge(mfa_params) warning = profile["warning"] credentials = { identifier: identifier, password: password } say "#{warning}\n" if warning response = rubygems_api_request(:post, "api/v1/api_key", sign_in_host, credentials: credentials, scope: scope) do |request| request.basic_auth identifier, password request.body = Gem::URI.encode_www_form({ name: key_name }.merge(all_params)) end with_response response do |resp| say "Signed in with API key: #{key_name}." set_api_key host, resp.body end end ## # Retrieves the pre-configured API key +key+ or terminates interaction with # an error. def verify_api_key(key) if Gem.configuration.api_keys.key? key Gem.configuration.api_keys[key] else alert_error "No such API key. Please add it to your configuration (done automatically on initial `gem push`)." terminate_interaction(ERROR_CODE) end end ## # If +response+ is an HTTP Success (2XX) response, yields the response if a # block was given or shows the response body to the user. # # If the response was not successful, shows an error to the user including # the +error_prefix+ and the response body. If the response was a permanent redirect, # shows an error to the user including the redirect location. def with_response(response, error_prefix = nil) case response when Gem::Net::HTTPSuccess then if block_given? yield response else say clean_text(response.body) end when Gem::Net::HTTPPermanentRedirect, Gem::Net::HTTPRedirection then message = "The request has redirected permanently to #{response["location"]}. Please check your defined push host URL." message = "#{error_prefix}: #{message}" if error_prefix say clean_text(message) terminate_interaction(ERROR_CODE) else message = response.body message = "#{error_prefix}: #{message}" if error_prefix say clean_text(message) terminate_interaction(ERROR_CODE) end end ## # Returns true when the user has enabled multifactor authentication from # +response+ text and no otp provided by options. def set_api_key(host, key) if default_host? Gem.configuration.rubygems_api_key = key else Gem.configuration.set_api_key host, key end end private def request_with_otp(method, uri, &block) request_method = Gem::Net::HTTP.const_get method.to_s.capitalize Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req| req["OTP"] = otp if otp block.call(req) end ensure options[:otp] = nil if webauthn_enabled? end def fetch_otp(credentials) options[:otp] = if webauthn_url = webauthn_verification_url(credentials) server = TCPServer.new 0 port = server.addr[1].to_s url_with_port = "#{webauthn_url}?port=#{port}" say "You have enabled multi-factor authentication. Please visit the following URL to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option." say "" say url_with_port say "" threads = [WebauthnListener.listener_thread(host, server), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)] otp_thread = wait_for_otp_thread(*threads) threads.each(&:join) if error = otp_thread[:error] alert_error error.message terminate_interaction(1) end options[:webauthn] = true say "You are verified with a security device. You may close the browser window." otp_thread[:otp] else say "You have enabled multi-factor authentication. Please enter OTP code." ask "Code: " end end def wait_for_otp_thread(*threads) loop do threads.each do |otp_thread| return otp_thread unless otp_thread.alive? end sleep 0.1 end ensure threads.each(&:exit) end def webauthn_verification_url(credentials) response = rubygems_api_request(:post, "api/v1/webauthn_verification") do |request| if credentials.empty? request.add_field "Authorization", api_key else request.basic_auth credentials[:identifier], credentials[:password] end end response.is_a?(Gem::Net::HTTPSuccess) ? response.body : nil end def pretty_host(host) if default_host? "RubyGems.org" else host end end def get_scope_params(scope) scope_params = { index_rubygems: true, push_rubygem: true } if scope scope_params = { scope => true } else say "The default access scope is:" scope_params.each do |k, _v| say " #{k}: y" end say "\n" customise = ask_yes_no("Do you want to customise scopes?", false) if customise EXCLUSIVELY_API_SCOPES.each do |excl_scope| selected = ask_yes_no("#{excl_scope} (exclusive scope, answering yes will not prompt for other scopes)", false) next unless selected return { excl_scope => true } end scope_params = {} API_SCOPES.each do |s| selected = ask_yes_no(s.to_s, false) scope_params[s] = true if selected end end say "\n" end scope_params end def default_host? host == Gem::DEFAULT_HOST end def get_user_profile(identifier, password) return {} unless default_host? response = rubygems_api_request(:get, "api/v1/profile/me.yaml") do |request| request.basic_auth identifier, password end with_response response do |resp| Gem::ConfigFile.load_with_rubygems_config_hash(clean_text(resp.body)) end end def get_mfa_params(profile) mfa_level = profile["mfa"] params = {} if ["ui_only", "ui_and_gem_signin"].include?(mfa_level) selected = ask_yes_no("Would you like to enable MFA for this key? (strongly recommended)") params["mfa"] = true if selected end params end def get_key_name(scope) hostname = Socket.gethostname || "unknown-host" user = ENV["USER"] || ENV["USERNAME"] || "unknown-user" ts = Time.now.strftime("%Y%m%d%H%M%S") default_key_name = "#{hostname}-#{user}-#{ts}" key_name = ask "API Key name [#{default_key_name}]: " unless scope if key_name.nil? || key_name.empty? default_key_name else key_name end end def api_key_forbidden?(response) response.is_a?(Gem::Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access") end end PK!Ղ &&dependency_installer.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "dependency_list" require_relative "package" require_relative "installer" require_relative "spec_fetcher" require_relative "user_interaction" require_relative "available_set" ## # Installs a gem along with all its dependencies from local and remote gems. class Gem::DependencyInstaller include Gem::UserInteraction DEFAULT_OPTIONS = { # :nodoc: env_shebang: false, document: %w[ri], domain: :both, # HACK: dup force: false, format_executable: false, # HACK: dup ignore_dependencies: false, prerelease: false, security_policy: nil, # HACK: NoSecurity requires OpenSSL. AlmostNo? Low? wrappers: true, build_args: nil, build_docs_in_background: false, }.freeze ## # Documentation types. For use by the Gem.done_installing hook attr_reader :document ## # Errors from SpecFetcher while searching for remote specifications attr_reader :errors ## # List of gems installed by #install in alphabetic order attr_reader :installed_gems ## # Creates a new installer instance. # # Options are: # :cache_dir:: Alternate repository path to store .gem files in. # :domain:: :local, :remote, or :both. :local only searches gems in the # current directory. :remote searches only gems in Gem::sources. # :both searches both. # :env_shebang:: See Gem::Installer::new. # :force:: See Gem::Installer#install. # :format_executable:: See Gem::Installer#initialize. # :ignore_dependencies:: Don't install any dependencies. # :install_dir:: See Gem::Installer#install. # :prerelease:: Allow prerelease versions. See #install. # :security_policy:: See Gem::Installer::new and Gem::Security. # :user_install:: See Gem::Installer.new # :wrappers:: See Gem::Installer::new # :build_args:: See Gem::Installer::new def initialize(options = {}) @only_install_dir = !options[:install_dir].nil? @install_dir = options[:install_dir] || Gem.dir @build_root = options[:build_root] options = DEFAULT_OPTIONS.merge options @bin_dir = options[:bin_dir] @dev_shallow = options[:dev_shallow] @development = options[:development] @document = options[:document] @domain = options[:domain] @env_shebang = options[:env_shebang] @force = options[:force] @format_executable = options[:format_executable] @ignore_dependencies = options[:ignore_dependencies] @prerelease = options[:prerelease] @security_policy = options[:security_policy] @user_install = options[:user_install] @wrappers = options[:wrappers] @build_args = options[:build_args] @build_jobs = options[:build_jobs] @build_docs_in_background = options[:build_docs_in_background] @dir_mode = options[:dir_mode] @data_mode = options[:data_mode] @prog_mode = options[:prog_mode] # Indicates that we should not try to update any deps unless # we absolutely must. @minimal_deps = options[:minimal_deps] @available = nil @installed_gems = [] @toplevel_specs = nil @cache_dir = options[:cache_dir] || @install_dir @errors = [] end ## # Indicated, based on the requested domain, if local # gems should be considered. def consider_local? @domain == :both || @domain == :local end ## # Indicated, based on the requested domain, if remote # gems should be considered. def consider_remote? @domain == :both || @domain == :remote end def in_background(what) # :nodoc: fork_happened = false if @build_docs_in_background && Process.respond_to?(:fork) begin Process.fork do yield end fork_happened = true say "#{what} in a background process." rescue NotImplementedError end end yield unless fork_happened end ## # Installs the gem +dep_or_name+ and all its dependencies. Returns an Array # of installed gem specifications. # # If the +:prerelease+ option is set and there is a prerelease for # +dep_or_name+ the prerelease version will be installed. # # Unless explicitly specified as a prerelease dependency, prerelease gems # that +dep_or_name+ depend on will not be installed. # # If c-1.a depends on b-1 and a-1.a and there is a gem b-1.a available then # c-1.a, b-1 and a-1.a will be installed. b-1.a will need to be installed # separately. def install(dep_or_name, version = Gem::Requirement.default) request_set = resolve_dependencies dep_or_name, version @installed_gems = [] options = { bin_dir: @bin_dir, build_args: @build_args, build_jobs: @build_jobs, document: @document, env_shebang: @env_shebang, force: @force, format_executable: @format_executable, ignore_dependencies: @ignore_dependencies, prerelease: @prerelease, security_policy: @security_policy, user_install: @user_install, wrappers: @wrappers, build_root: @build_root, dir_mode: @dir_mode, data_mode: @data_mode, prog_mode: @prog_mode, } options[:install_dir] = @install_dir if @only_install_dir request_set.install options do |_, installer| @installed_gems << installer.spec if installer end @installed_gems.sort! # Since this is currently only called for docs, we can be lazy and just say # it's documentation. Ideally the hook adder could decide whether to be in # the background or not, and what to call it. in_background "Installing documentation" do Gem.done_installing_hooks.each do |hook| hook.call self, @installed_gems end end unless Gem.done_installing_hooks.empty? @installed_gems end def install_development_deps # :nodoc: if @development && @dev_shallow :shallow elsif @development :all else :none end end def resolve_dependencies(dep_or_name, version) # :nodoc: request_set = Gem::RequestSet.new request_set.development = @development request_set.development_shallow = @dev_shallow request_set.soft_missing = @force request_set.prerelease = @prerelease installer_set = Gem::Resolver::InstallerSet.new @domain installer_set.ignore_installed = (@minimal_deps == false) || @only_install_dir installer_set.force = @force if consider_local? if dep_or_name =~ /\.gem$/ && File.file?(dep_or_name) src = Gem::Source::SpecificFile.new dep_or_name installer_set.add_local dep_or_name, src.spec, src version = src.spec.version if version == Gem::Requirement.default elsif dep_or_name =~ /\.gem$/ # rubocop:disable Performance/RegexpMatch Dir[dep_or_name].each do |name| src = Gem::Source::SpecificFile.new name installer_set.add_local dep_or_name, src.spec, src rescue Gem::Package::FormatError end # else This is a dependency. InstallerSet handles this case end end dependency = if spec = installer_set.local?(dep_or_name) installer_set.remote = nil if spec.dependencies.none? Gem::Dependency.new spec.name, version elsif String === dep_or_name Gem::Dependency.new dep_or_name, version else dep_or_name end dependency.prerelease = @prerelease request_set.import [dependency] installer_set.add_always_install dependency request_set.always_install = installer_set.always_install request_set.remote = installer_set.consider_remote? if @ignore_dependencies installer_set.ignore_dependencies = true request_set.ignore_dependencies = true request_set.soft_missing = true end request_set.resolve installer_set @errors.concat request_set.errors request_set end end PK!"2.command_manager.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "command" require_relative "user_interaction" require_relative "text" ## # The command manager registers and installs all the individual sub-commands # supported by the gem command. # # Extra commands can be provided by writing a rubygems_plugin.rb # file in an installed gem. You should register your command against the # Gem::CommandManager instance, like this: # # # file rubygems_plugin.rb # require 'rubygems/command_manager' # # Gem::CommandManager.instance.register_command :edit # # You should put the implementation of your command in rubygems/commands. # # # file rubygems/commands/edit_command.rb # class Gem::Commands::EditCommand < Gem::Command # # ... # end # # See Gem::Command for instructions on writing gem commands. class Gem::CommandManager include Gem::Text include Gem::UserInteraction BUILTIN_COMMANDS = [ # :nodoc: :build, :cert, :check, :cleanup, :contents, :dependency, :environment, :exec, :fetch, :generate_index, :help, :info, :install, :list, :lock, :mirror, :open, :outdated, :owner, :pristine, :push, :rdoc, :rebuild, :search, :server, :signin, :signout, :sources, :specification, :stale, :uninstall, :unpack, :update, :which, :yank, ].freeze ALIAS_COMMANDS = { "i" => "install", "login" => "signin", "logout" => "signout", }.freeze ## # Return the authoritative instance of the command manager. def self.instance @instance ||= new end ## # Returns self. Allows a CommandManager instance to stand # in for the class itself. def instance self end ## # Reset the authoritative instance of the command manager. def self.reset @instance = nil end ## # Register all the subcommands supported by the gem command. def initialize require_relative "vendored_timeout" @commands = {} BUILTIN_COMMANDS.each do |name| register_command name end end ## # Register the Symbol +command+ as a gem command. def register_command(command, obj = false) @commands[command] = obj end ## # Unregister the Symbol +command+ as a gem command. def unregister_command(command) @commands.delete command end ## # Returns a Command instance for +command_name+ def [](command_name) command_name = command_name.intern return nil if @commands[command_name].nil? @commands[command_name] ||= load_and_instantiate(command_name) end ## # Return a sorted list of all command names as strings. def command_names @commands.keys.collect(&:to_s).sort end ## # Run the command specified by +args+. def run(args, build_args = nil) process_args(args, build_args) rescue StandardError, Gem::Timeout::Error => ex if ex.respond_to?(:detailed_message) msg = ex.detailed_message(highlight: false).sub(/\A(.*?)(?: \(.+?\))/) { $1 } else msg = ex.message end alert_error clean_text("While executing gem ... (#{ex.class})\n #{msg}") ui.backtrace ex terminate_interaction(1) rescue Interrupt alert_error clean_text("Interrupted") terminate_interaction(1) end def process_args(args, build_args = nil) if args.empty? say Gem::Command::HELP terminate_interaction 1 end case args.first when "-h", "--help" then say Gem::Command::HELP terminate_interaction 0 when "-v", "--version" then say Gem::VERSION terminate_interaction 0 when "-C" then args.shift start_point = args.shift if Dir.exist?(start_point) Dir.chdir(start_point) { invoke_command(args, build_args) } else alert_error clean_text("#{start_point} isn't a directory.") terminate_interaction 1 end when /^-/ then alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.") terminate_interaction 1 else invoke_command(args, build_args) end end def find_command(cmd_name) cmd_name = find_alias_command cmd_name possibilities = find_command_possibilities cmd_name if possibilities.size > 1 raise Gem::CommandLineError, "Ambiguous command #{cmd_name} matches [#{possibilities.join(", ")}]" elsif possibilities.empty? raise Gem::UnknownCommandError.new(cmd_name) end self[possibilities.first] end def find_alias_command(cmd_name) alias_name = ALIAS_COMMANDS[cmd_name] alias_name ? alias_name : cmd_name end def find_command_possibilities(cmd_name) len = cmd_name.length found = command_names.select {|name| cmd_name == name[0, len] } exact = found.find {|name| name == cmd_name } exact ? [exact] : found end private def load_and_instantiate(command_name) command_name = command_name.to_s const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase } << "Command" begin begin require "rubygems/commands/#{command_name}_command" rescue LoadError # it may have been defined from a rubygems_plugin.rb file end Gem::Commands.const_get(const_name).new rescue StandardError => e alert_error clean_text("Loading command: #{command_name} (#{e.class})\n\t#{e}") ui.backtrace e end end def invoke_command(args, build_args) cmd_name = args.shift.downcase cmd = find_command cmd_name terminate_interaction 1 unless cmd cmd.deprecation_warning if cmd.deprecated? cmd.invoke_with_build_args args, build_args end end PK!(CCinstall_message.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "user_interaction" ## # A default post-install hook that displays "Successfully installed # some_gem-1.0" Gem.post_install do |installer| ui = Gem::DefaultUserInteraction.ui ui.say "Successfully installed #{installer.spec.full_name}" end PK!Vbnn gem_runner.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" require_relative "command_manager" ## # Run an instance of the gem program. # # Gem::GemRunner is only intended for internal use by RubyGems itself. It # does not form any public API and may change at any time for any reason. # # If you would like to duplicate functionality of `gem` commands, use the # classes they call directly. class Gem::GemRunner def initialize @command_manager_class = Gem::CommandManager @config_file_class = Gem::ConfigFile end ## # Run the gem command with the following arguments. def run(args) validate_encoding args build_args = extract_build_args args do_configuration args begin Gem.load_env_plugins rescue StandardError nil end Gem.load_plugins cmd = @command_manager_class.instance cmd.command_names.each do |command_name| config_args = Gem.configuration[command_name] config_args = case config_args when String config_args.split " " else Array(config_args) end Gem::Command.add_specific_extra_args command_name, config_args end cmd.run Gem.configuration.args, build_args end ## # Separates the build arguments (those following --) from the # other arguments in the list. def extract_build_args(args) # :nodoc: return [] unless offset = args.index("--") build_args = args.slice!(offset...args.length) build_args.shift build_args end private def validate_encoding(args) invalid_arg = args.find {|arg| !arg.valid_encoding? } if invalid_arg raise Gem::OptionParser::InvalidArgument.new("'#{invalid_arg.scrub}' has invalid encoding") end end def do_configuration(args) Gem.configuration = @config_file_class.new(args) Gem.use_paths Gem.configuration[:gemhome], Gem.configuration[:gempath] Gem::Command.extra_args = Gem.configuration[:gem] end end PK!path_support.rbnu[# frozen_string_literal: true ## # # Gem::PathSupport facilitates the GEM_HOME and GEM_PATH environment settings # to the rest of RubyGems. # class Gem::PathSupport ## # The default system path for managing Gems. attr_reader :home ## # Array of paths to search for Gems. attr_reader :path ## # Directory with spec cache attr_reader :spec_cache_dir # :nodoc: ## # # Constructor. Takes a single argument which is to be treated like a # hashtable, or defaults to ENV, the system environment. # def initialize(env) @home = normalize_home_dir(env["GEM_HOME"] || Gem.default_dir) @path = split_gem_path env["GEM_PATH"], @home @spec_cache_dir = env["GEM_SPEC_CACHE"] || Gem.default_spec_cache_dir end private def normalize_home_dir(home) if File::ALT_SEPARATOR home = home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) end expand(home) end ## # Split the Gem search path (as reported by Gem.path). def split_gem_path(gpaths, home) # FIX: it should be [home, *path], not [*path, home] gem_path = [] if gpaths gem_path = gpaths.split(Gem.path_separator) # Handle the path_separator being set to a regexp, which will cause # end_with? to error if /#{Gem.path_separator}\z/.match?(gpaths) gem_path += default_path end if File::ALT_SEPARATOR gem_path.map! do |this_path| this_path.gsub File::ALT_SEPARATOR, File::SEPARATOR end end gem_path << home else gem_path = default_path end gem_path.map {|path| expand(path) }.uniq end # Return the default Gem path def default_path Gem.default_path + [@home] end def expand(path) if File.directory?(path) File.realpath(path) else path end end end PK!mo"" request.rbnu[# frozen_string_literal: true require_relative "vendored_net_http" require_relative "user_interaction" require_relative "uri_formatter" class Gem::Request extend Gem::UserInteraction include Gem::UserInteraction ### # Legacy. This is used in tests. def self.create_with_proxy(uri, request_class, last_modified, proxy) # :nodoc: cert_files = get_cert_files proxy ||= get_proxy_from_env(uri.scheme) pool = ConnectionPools.new proxy_uri(proxy), cert_files new(uri, request_class, last_modified, pool.pool_for(uri)) end def self.proxy_uri(proxy) # :nodoc: require_relative "vendor/uri/lib/uri" case proxy when :no_proxy then nil when Gem::URI::HTTP then proxy else Gem::URI.parse(proxy) end end def initialize(uri, request_class, last_modified, pool) @uri = uri @request_class = request_class @last_modified = last_modified @requests = Hash.new(0).compare_by_identity @user_agent = user_agent @connection_pool = pool end def proxy_uri @connection_pool.proxy_uri end def cert_files @connection_pool.cert_files end def self.get_cert_files pattern = File.expand_path("./ssl_certs/*/*.pem", __dir__) Dir.glob(pattern) end def self.configure_connection_for_https(connection, cert_files) raise Gem::Exception.new("OpenSSL is not available. Install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources") unless Gem::HAVE_OPENSSL connection.use_ssl = true connection.verify_mode = Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER store = OpenSSL::X509::Store.new if Gem.configuration.ssl_client_cert pem = File.read Gem.configuration.ssl_client_cert connection.cert = OpenSSL::X509::Certificate.new pem connection.key = OpenSSL::PKey::RSA.new pem end store.set_default_paths cert_files.each do |ssl_cert_file| store.add_file ssl_cert_file end if Gem.configuration.ssl_ca_cert if File.directory? Gem.configuration.ssl_ca_cert store.add_path Gem.configuration.ssl_ca_cert else store.add_file Gem.configuration.ssl_ca_cert end end connection.cert_store = store connection.verify_callback = proc do |preverify_ok, store_context| verify_certificate store_context unless preverify_ok preverify_ok end connection end def self.verify_certificate(store_context) depth = store_context.error_depth error = store_context.error_string number = store_context.error cert = store_context.current_cert ui.alert_error "SSL verification error at depth #{depth}: #{error} (#{number})" extra_message = verify_certificate_message number, cert ui.alert_error extra_message if extra_message end def self.verify_certificate_message(error_number, cert) return unless cert case error_number when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED then require "time" "Certificate #{cert.subject} expired at #{cert.not_after.iso8601}" when OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID then require "time" "Certificate #{cert.subject} not valid until #{cert.not_before.iso8601}" when OpenSSL::X509::V_ERR_CERT_REJECTED then "Certificate #{cert.subject} is rejected" when OpenSSL::X509::V_ERR_CERT_UNTRUSTED then "Certificate #{cert.subject} is not trusted" when OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT then "Certificate #{cert.issuer} is not trusted" when OpenSSL::X509::V_ERR_INVALID_CA then "Certificate #{cert.subject} is an invalid CA certificate" when OpenSSL::X509::V_ERR_INVALID_PURPOSE then "Certificate #{cert.subject} has an invalid purpose" when OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN then "Root certificate is not trusted (#{cert.subject})" when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY then "You must add #{cert.issuer} to your local trusted store" when OpenSSL::X509::V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE then "Cannot verify certificate issued by #{cert.issuer}" end end ## # Creates or an HTTP connection based on +uri+, or retrieves an existing # connection, using a proxy if needed. def connection_for(uri) @connection_pool.checkout rescue Gem::HAVE_OPENSSL ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN, Errno::EHOSTDOWN => e raise Gem::RemoteFetcher::FetchError.new(e.message, uri) end def fetch request = @request_class.new @uri.request_uri unless @uri.nil? || @uri.user.nil? || @uri.user.empty? request.basic_auth Gem::UriFormatter.new(@uri.user).unescape, Gem::UriFormatter.new(@uri.password).unescape end request.add_field "User-Agent", @user_agent request.add_field "Connection", "keep-alive" request.add_field "Keep-Alive", "30" if @last_modified require "time" request.add_field "If-Modified-Since", @last_modified.httpdate end yield request if block_given? perform_request request end ## # Returns a proxy URI for the given +scheme+ if one is set in the # environment variables. def self.get_proxy_from_env(scheme = "http") downcase_scheme = scheme.downcase upcase_scheme = scheme.upcase env_proxy = ENV["#{downcase_scheme}_proxy"] || ENV["#{upcase_scheme}_PROXY"] no_env_proxy = env_proxy.nil? || env_proxy.empty? if no_env_proxy return ["https", "http"].include?(downcase_scheme) ? :no_proxy : get_proxy_from_env("http") end require "uri" uri = Gem::URI(Gem::UriFormatter.new(env_proxy).normalize) if uri && uri.user.nil? && uri.password.nil? user = ENV["#{downcase_scheme}_proxy_user"] || ENV["#{upcase_scheme}_PROXY_USER"] password = ENV["#{downcase_scheme}_proxy_pass"] || ENV["#{upcase_scheme}_PROXY_PASS"] uri.user = Gem::UriFormatter.new(user).escape uri.password = Gem::UriFormatter.new(password).escape end uri end def perform_request(request) # :nodoc: connection = connection_for @uri retried = false bad_response = false begin @requests[connection] += 1 verbose "#{request.method} #{Gem::Uri.redact(@uri)}" file_name = File.basename(@uri.path) # perform download progress reporter only for gems if request.response_body_permitted? && file_name =~ /\.gem$/ reporter = ui.download_reporter response = connection.request(request) do |incomplete_response| if Gem::Net::HTTPOK === incomplete_response reporter.fetch(file_name, incomplete_response.content_length) downloaded = 0 data = String.new incomplete_response.read_body do |segment| data << segment downloaded += segment.length reporter.update(downloaded) end reporter.done if incomplete_response.respond_to? :body= incomplete_response.body = data else incomplete_response.instance_variable_set(:@body, data) end end end else response = connection.request request end verbose "#{response.code} #{response.message}" rescue Gem::Net::HTTPBadResponse verbose "bad response" reset connection raise Gem::RemoteFetcher::FetchError.new("too many bad responses", @uri) if bad_response bad_response = true retry rescue Gem::Net::HTTPFatalError verbose "fatal error" raise Gem::RemoteFetcher::FetchError.new("fatal error", @uri) # HACK: work around EOFError bug in Gem::Net::HTTP # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible # to install gems. rescue EOFError, Gem::Timeout::Error, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE requests = @requests[connection] verbose "connection reset after #{requests} requests, retrying" raise Gem::RemoteFetcher::FetchError.new("too many connection resets", @uri) if retried reset connection retried = true retry end response ensure @connection_pool.checkin connection end ## # Resets HTTP connection +connection+. def reset(connection) @requests.delete connection connection.finish connection.start end def user_agent ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}".dup ruby_version = RUBY_VERSION ruby_version += "dev" if RUBY_PATCHLEVEL == -1 ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}" if RUBY_PATCHLEVEL >= 0 ua << " patchlevel #{RUBY_PATCHLEVEL}" else ua << " revision #{RUBY_REVISION}" end ua << ")" ua << " #{RUBY_ENGINE}" if RUBY_ENGINE != "ruby" ua end end require_relative "request/http_pool" require_relative "request/https_pool" require_relative "request/connection_pools" PK!">OQQvendored_molinillo.rbnu[# frozen_string_literal: true require_relative "vendor/molinillo/lib/molinillo" PK!ђSÛ unknown_command_spell_checker.rbnu[# frozen_string_literal: true class Gem::UnknownCommandSpellChecker attr_reader :error def initialize(error) @error = error end def corrections @corrections ||= spell_checker.correct(error.unknown_command).map(&:inspect) end private def spell_checker dictionary = Gem::CommandManager.instance.command_names DidYouMean::SpellChecker.new(dictionary: dictionary) end end PK!HNN package.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments require_relative "win_platform" require_relative "security" require_relative "user_interaction" ## # Example using a Gem::Package # # Builds a .gem file given a Gem::Specification. A .gem file is a tarball # which contains a data.tar.gz, metadata.gz, checksums.yaml.gz and possibly # signatures. # # require 'rubygems' # require 'rubygems/package' # # spec = Gem::Specification.new do |s| # s.summary = "Ruby based make-like utility." # s.name = 'rake' # s.version = PKG_VERSION # s.requirements << 'none' # s.files = PKG_FILES # s.description = <<-EOF # Rake is a Make-like program implemented in Ruby. Tasks # and dependencies are specified in standard Ruby syntax. # EOF # end # # Gem::Package.build spec # # Reads a .gem file. # # require 'rubygems' # require 'rubygems/package' # # the_gem = Gem::Package.new(path_to_dot_gem) # the_gem.contents # get the files in the gem # the_gem.extract_files destination_directory # extract the gem into a directory # the_gem.spec # get the spec out of the gem # the_gem.verify # check the gem is OK (contains valid gem specification, contains a not corrupt contents archive) # # #files are the files in the .gem tar file, not the Ruby files in the gem # #extract_files and #contents automatically call #verify class Gem::Package include Gem::UserInteraction class Error < Gem::Exception; end class FormatError < Error attr_reader :path def initialize(message, source = nil) if source @path = source.is_a?(String) ? source : source.path message += " in #{path}" if path end super message end end class PathError < Error def initialize(destination, destination_dir) super format("installing into parent path %s of %s is not allowed", destination, destination_dir) end end class SymlinkError < Error def initialize(name, destination, destination_dir) super format("installing symlink '%s' pointing to parent path %s of %s is not allowed", name, destination, destination_dir) end end class NonSeekableIO < Error; end class TooLongFileName < Error; end ## # Raised when a tar file is corrupt class TarInvalidError < Error; end attr_accessor :build_time # :nodoc: ## # Checksums for the contents of the package attr_reader :checksums ## # The files in this package. This is not the contents of the gem, just the # files in the top-level container. attr_reader :files ## # Reference to the gem being packaged. attr_reader :gem ## # The security policy used for verifying the contents of this package. attr_accessor :security_policy ## # Sets the Gem::Specification to use to build this package. attr_writer :spec ## # Permission for directories attr_accessor :dir_mode ## # Permission for program files attr_accessor :prog_mode ## # Permission for other files attr_accessor :data_mode def self.build(spec, skip_validation = false, strict_validation = false, file_name = nil) gem_file = file_name || spec.file_name package = new gem_file package.spec = spec package.build skip_validation, strict_validation gem_file end ## # Creates a new Gem::Package for the file at +gem+. +gem+ can also be # provided as an IO object. # # If +gem+ is an existing file in the old format a Gem::Package::Old will be # returned. def self.new(gem, security_policy = nil) gem = if gem.is_a?(Gem::Package::Source) gem elsif gem.respond_to? :read Gem::Package::IOSource.new gem else Gem::Package::FileSource.new gem end return super unless self == Gem::Package return super unless gem.present? return super unless gem.start return super unless gem.start.include? "MD5SUM =" Gem::Package::Old.new gem end ## # Extracts the Gem::Specification and raw metadata from the .gem file at # +path+. #-- def self.raw_spec(path, security_policy = nil) format = new(path, security_policy) spec = format.spec metadata = nil File.open path, Gem.binary_mode do |io| tar = Gem::Package::TarReader.new io tar.each_entry do |entry| case entry.full_name when "metadata" then metadata = entry.read when "metadata.gz" then metadata = Gem::Util.gunzip entry.read end end end [spec, metadata] end ## # Creates a new package that will read or write to the file +gem+. def initialize(gem, security_policy) # :notnew: require "zlib" @gem = gem @build_time = Gem.source_date_epoch @checksums = {} @contents = nil @digests = Hash.new {|h, algorithm| h[algorithm] = {} } @files = nil @security_policy = security_policy @signatures = {} @signer = nil @spec = nil end ## # Copies this package to +path+ (if possible) def copy_to(path) FileUtils.cp @gem.path, path unless File.exist? path end ## # Adds a checksum for each entry in the gem to checksums.yaml.gz. def add_checksums(tar) Gem.load_yaml checksums_by_algorithm = Hash.new {|h, algorithm| h[algorithm] = {} } @checksums.each do |name, digests| digests.each do |algorithm, digest| checksums_by_algorithm[algorithm][name] = digest.hexdigest end end tar.add_file_signed "checksums.yaml.gz", 0o444, @signer do |io| gzip_to io do |gz_io| Psych.dump checksums_by_algorithm, gz_io end end end ## # Adds the files listed in the packages's Gem::Specification to data.tar.gz # and adds this file to the +tar+. def add_contents(tar) # :nodoc: digests = tar.add_file_signed "data.tar.gz", 0o444, @signer do |io| gzip_to io do |gz_io| Gem::Package::TarWriter.new gz_io do |data_tar| add_files data_tar end end end @checksums["data.tar.gz"] = digests end ## # Adds files included the package's Gem::Specification to the +tar+ file def add_files(tar) # :nodoc: @spec.files.each do |file| stat = File.lstat file if stat.symlink? tar.add_symlink file, File.readlink(file), stat.mode end next unless stat.file? tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| copy_stream(src_io, dst_io, stat.size) end end end end ## # Adds the package's Gem::Specification to the +tar+ file def add_metadata(tar) # :nodoc: digests = tar.add_file_signed "metadata.gz", 0o444, @signer do |io| gzip_to io do |gz_io| gz_io.write @spec.to_yaml end end @checksums["metadata.gz"] = digests end ## # Builds this package based on the specification set by #spec= def build(skip_validation = false, strict_validation = false) raise ArgumentError, "skip_validation = true and strict_validation = true are incompatible" if skip_validation && strict_validation Gem.load_yaml @spec.validate true, strict_validation unless skip_validation setup_signer( signer_options: { expiration_length_days: Gem.configuration.cert_expiration_length_days, } ) @gem.with_write_io do |gem_io| Gem::Package::TarWriter.new gem_io do |gem| add_metadata gem add_contents gem add_checksums gem end end say <<-EOM Successfully built RubyGem Name: #{@spec.name} Version: #{@spec.version} File: #{File.basename @gem.path} EOM ensure @signer = nil end ## # A list of file names contained in this gem def contents return @contents if @contents verify unless @spec @contents = [] @gem.with_read_io do |io| gem_tar = Gem::Package::TarReader.new io gem_tar.each do |entry| next unless entry.full_name == "data.tar.gz" open_tar_gz entry do |pkg_tar| pkg_tar.each do |contents_entry| @contents << contents_entry.full_name end end return @contents end end rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end ## # Creates a digest of the TarEntry +entry+ from the digest algorithm set by # the security policy. def digest(entry) # :nodoc: algorithms = if @checksums @checksums.to_h {|algorithm, _| [algorithm, Gem::Security.create_digest(algorithm)] } elsif Gem::Security::DIGEST_NAME { Gem::Security::DIGEST_NAME => Gem::Security.create_digest(Gem::Security::DIGEST_NAME) } end return @digests if algorithms.nil? || algorithms.empty? buf = String.new(capacity: 16_384, encoding: Encoding::BINARY) until entry.eof? entry.readpartial(16_384, buf) algorithms.each_value {|digester| digester << buf } end entry.rewind algorithms.each do |algorithm, digester| @digests[algorithm][entry.full_name] = digester end @digests end ## # Extracts the files in this package into +destination_dir+ # # If +pattern+ is specified, only entries matching that glob will be # extracted. def extract_files(destination_dir, pattern = "*") verify unless @spec FileUtils.mkdir_p destination_dir, mode: dir_mode && 0o755 @gem.with_read_io do |io| reader = Gem::Package::TarReader.new io reader.each do |entry| next unless entry.full_name == "data.tar.gz" extract_tar_gz entry, destination_dir, pattern break # ignore further entries end end rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end ## # Extracts all the files in the gzipped tar archive +io+ into # +destination_dir+. # # If an entry in the archive contains a relative path above # +destination_dir+ or an absolute path is encountered an exception is # raised. # # If +pattern+ is specified, only entries matching that glob will be # extracted. def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: destination_dir = File.realpath(destination_dir) directories = [] symlinks = [] open_tar_gz io do |tar| tar.each do |entry| full_name = entry.full_name next unless File.fnmatch pattern, full_name, File::FNM_DOTMATCH destination = install_location full_name, destination_dir if entry.symlink? link_target = entry.header.linkname real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination)) raise Gem::Package::SymlinkError.new(full_name, real_destination, destination_dir) unless normalize_path(real_destination).start_with? normalize_path(destination_dir + "/") symlinks << [full_name, link_target, destination, real_destination] end mkdir = if entry.directory? destination else File.dirname destination end unless directories.include?(mkdir) FileUtils.mkdir_p mkdir, mode: dir_mode ? 0o755 : (entry.header.mode if entry.directory?) directories << mkdir end if entry.file? File.open(destination, "wb") do |out| copy_stream(tar.io, out, entry.size) # Flush needs to happen before chmod because there could be data # in the IO buffer that needs to be written, and that could be # written after the chmod (on close) which would mess up the perms out.flush out.chmod file_mode(entry.header.mode) & ~File.umask end end verbose destination end end symlinks.each do |name, target, destination, real_destination| if File.exist?(real_destination) File.symlink(target, destination) else alert_warning "#{@spec.full_name} ships with a dangling symlink named #{name} pointing to missing #{target} file. Ignoring" end end if dir_mode File.chmod(dir_mode, *directories) end end def file_mode(mode) # :nodoc: ((mode & 0o111).zero? ? data_mode : prog_mode) || # If we're not using one of the default modes, then we're going to fall # back to the mode from the tarball. In this case we need to mask it down # to fit into 2^16 bits (the maximum value for a mode in CRuby since it # gets put into an unsigned short). (mode & ((1 << 16) - 1)) end ## # Gzips content written to +gz_io+ to +io+. #-- # Also sets the gzip modification time to the package build time to ease # testing. def gzip_to(io) # :yields: gz_io gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION gz_io.mtime = @build_time yield gz_io ensure gz_io.close end ## # Returns the full path for installing +filename+. # # If +filename+ is not inside +destination_dir+ an exception is raised. def install_location(filename, destination_dir) # :nodoc: raise Gem::Package::PathError.new(filename, destination_dir) if filename.start_with? "/" destination_dir = File.realpath(destination_dir) destination = File.expand_path(filename, destination_dir) raise Gem::Package::PathError.new(destination, destination_dir) unless normalize_path(destination).start_with? normalize_path(destination_dir + "/") destination end if Gem.win_platform? def normalize_path(pathname) # :nodoc: pathname.downcase end else def normalize_path(pathname) # :nodoc: pathname end end ## # Loads a Gem::Specification from the TarEntry +entry+ def load_spec(entry) # :nodoc: limit = 10 * 1024 * 1024 case entry.full_name when "metadata" then @spec = Gem::Specification.from_yaml limit_read(entry, "metadata", limit) when "metadata.gz" then Zlib::GzipReader.wrap(entry, external_encoding: Encoding::UTF_8) do |gzio| @spec = Gem::Specification.from_yaml limit_read(gzio, "metadata.gz", limit) end end end ## # Opens +io+ as a gzipped tar archive def open_tar_gz(io) # :nodoc: Zlib::GzipReader.wrap io do |gzio| tar = Gem::Package::TarReader.new gzio yield tar ensure # Consume remaining gzip data to prevent the # "attempt to close unfinished zstream; reset forced" warning # when the GzipReader is closed with unconsumed compressed data. begin IO.copy_stream(gzio, IO::NULL) rescue Zlib::GzipFile::Error, IOError nil end end end ## # Reads and loads checksums.yaml.gz from the tar file +gem+ def read_checksums(gem) Gem.load_yaml @checksums = gem.seek "checksums.yaml.gz" do |entry| Zlib::GzipReader.wrap entry do |gz_io| Gem::SafeYAML.safe_load limit_read(gz_io, "checksums.yaml.gz", 10 * 1024 * 1024) end end end ## # Prepares the gem for signing and checksum generation. If a signing # certificate and key are not present only checksum generation is set up. def setup_signer(signer_options: {}) passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"] if @spec.signing_key @signer = Gem::Security::Signer.new( @spec.signing_key, @spec.cert_chain, passphrase, signer_options ) @spec.signing_key = nil @spec.cert_chain = @signer.cert_chain.map(&:to_s) else @signer = Gem::Security::Signer.new nil, nil, passphrase @spec.cert_chain = @signer.cert_chain.map(&:to_pem) if @signer.cert_chain end end ## # The spec for this gem. # # If this is a package for a built gem the spec is loaded from the # gem and returned. If this is a package for a gem being built the provided # spec is returned. def spec verify unless @spec @spec end ## # Verifies that this gem: # # * Contains a valid gem specification # * Contains a contents archive # * The contents archive is not corrupt # # After verification the gem specification from the gem is available from # #spec def verify @files = [] @spec = nil @gem.with_read_io do |io| Gem::Package::TarReader.new io do |reader| read_checksums reader verify_files reader end end verify_checksums @digests, @checksums @security_policy&.verify_signatures @spec, @digests, @signatures true rescue Gem::Security::Exception @spec = nil @files = [] raise rescue Errno::ENOENT => e raise Gem::Package::FormatError.new e.message rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end private ## # Verifies the +checksums+ against the +digests+. This check is not # cryptographically secure. Missing checksums are ignored. def verify_checksums(digests, checksums) # :nodoc: return unless checksums checksums.sort.each do |algorithm, gem_digests| gem_digests.sort.each do |file_name, gem_hexdigest| computed_digest = digests[algorithm][file_name] unless computed_digest.hexdigest == gem_hexdigest raise Gem::Package::FormatError.new \ "#{algorithm} checksum mismatch for #{file_name}", @gem end end end end ## # Verifies +entry+ in a .gem file. def verify_entry(entry) file_name = entry.full_name @files << file_name case file_name when /\.sig$/ then @signatures[$`] = limit_read(entry, file_name, 1024 * 1024) if @security_policy return else digest entry end case file_name when "metadata", "metadata.gz" then load_spec entry when "data.tar.gz" then verify_gz entry end rescue StandardError warn "Exception while verifying #{@gem.path}" raise end ## # Verifies the files of the +gem+ def verify_files(gem) gem.each do |entry| verify_entry entry end unless @spec raise Gem::Package::FormatError.new "package metadata is missing", @gem end unless @files.include? "data.tar.gz" raise Gem::Package::FormatError.new \ "package content (data.tar.gz) is missing", @gem end if (duplicates = @files.group_by {|f| f }.select {|_k,v| v.size > 1 }.map(&:first)) && duplicates.any? raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(", ")})" end end ## # Verifies that +entry+ is a valid gzipped file. def verify_gz(entry) # :nodoc: Zlib::GzipReader.wrap entry do |gzio| # TODO: read into a buffer once zlib supports it gzio.read 16_384 until gzio.eof? # gzip checksum verification end rescue Zlib::GzipFile::Error => e raise Gem::Package::FormatError.new(e.message, entry.full_name) end if RUBY_ENGINE == "truffleruby" def copy_stream(src, dst, size) # :nodoc: dst.write src.read(size) end else def copy_stream(src, dst, size) # :nodoc: IO.copy_stream(src, dst, size) end end def limit_read(io, name, limit) bytes = io.read(limit + 1) raise Gem::Package::FormatError, "#{name} is too big (over #{limit} bytes)" if bytes.size > limit bytes end end require_relative "package/digest_io" require_relative "package/source" require_relative "package/file_source" require_relative "package/io_source" require_relative "package/old" require_relative "package/tar_header" require_relative "package/tar_reader" require_relative "package/tar_reader/entry" require_relative "package/tar_writer" PK!]Y(version_option.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" ## # Mixin methods for --version and --platform Gem::Command options. module Gem::VersionOption ## # Add the --platform option to the option parser. def add_platform_option(task = command, *wrap) Gem::OptionParser.accept Gem::Platform do |value| if value == Gem::Platform::RUBY value else Gem::Platform.new value end end add_option("--platform PLATFORM", Gem::Platform, "Specify the platform of gem to #{task}", *wrap) do |value, options| unless options[:added_platform] Gem.platforms = [Gem::Platform::RUBY] options[:added_platform] = true end Gem.platforms << value unless Gem.platforms.include? value end end ## # Add the --prerelease option to the option parser. def add_prerelease_option(*wrap) add_option("--[no-]prerelease", "Allow prerelease versions of a gem", *wrap) do |value, options| options[:prerelease] = value options[:explicit_prerelease] = true end end ## # Add the --version option to the option parser. def add_version_option(task = command, *wrap) Gem::OptionParser.accept Gem::Requirement do |value| Gem::Requirement.new(*value.split(/\s*,\s*/)) end add_option("-v", "--version VERSION", Gem::Requirement, "Specify version of gem to #{task}", *wrap) do |value, options| # Allow handling for multiple --version operators if options[:version] && !options[:version].none? options[:version].concat([value]) else options[:version] = value end explicit_prerelease_set = !options[:explicit_prerelease].nil? options[:explicit_prerelease] = false unless explicit_prerelease_set options[:prerelease] = value.prerelease? unless options[:explicit_prerelease] end end ## # Extract platform given on the command line def get_platform_from_requirements(requirements) Gem.platforms[1].to_s if requirements.key? :added_platform end end PK!= deprecate.rbnu[# frozen_string_literal: true module Gem ## # Provides 3 methods for declaring when something is going away. # # deprecate(name, repl, year, month): # Indicate something may be removed on/after a certain date. # # rubygems_deprecate(name, replacement=:none): # Indicate something will be removed in the next major RubyGems version, # and (optionally) a replacement for it. # # +rubygems_deprecate_command+: # Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be # removed in the next RubyGems version. # # Also provides +skip_during+ for temporarily turning off deprecation warnings. # This is intended to be used in the test suite, so deprecation warnings # don't cause test failures if you need to make sure stderr is otherwise empty. # # # Example usage of +deprecate+ and +rubygems_deprecate+: # # class Legacy # def self.some_class_method # # ... # end # # def some_instance_method # # ... # end # # def some_old_method # # ... # end # # extend Gem::Deprecate # deprecate :some_instance_method, "X.z", 2011, 4 # rubygems_deprecate :some_old_method, "Modern#some_new_method" # # class << self # extend Gem::Deprecate # deprecate :some_class_method, :none, 2011, 4 # end # end # # # Example usage of +rubygems_deprecate_command+: # # class Gem::Commands::QueryCommand < Gem::Command # extend Gem::Deprecate # rubygems_deprecate_command # # # ... # end # # # Example usage of +skip_during+: # # class TestSomething < Gem::Testcase # def test_some_thing_with_deprecations # Gem::Deprecate.skip_during do # actual_stdout, actual_stderr = capture_output do # Gem.something_deprecated # end # assert_empty actual_stdout # assert_equal(expected, actual_stderr) # end # end # end module Deprecate def self.skip # :nodoc: @skip ||= false end def self.skip=(v) # :nodoc: @skip = v end ## # Temporarily turn off warnings. Intended for tests only. def skip_during original = Gem::Deprecate.skip Gem::Deprecate.skip = true yield ensure Gem::Deprecate.skip = original end def self.next_rubygems_major_version # :nodoc: Gem::Version.new(Gem.rubygems_version.segments.first).bump end ## # Simple deprecation method that deprecates +name+ by wrapping it up # in a dummy method. It warns on each call to the dummy method # telling the user of +repl+ (unless +repl+ is :none) and the # year/month that it is planned to go away. def deprecate(name, repl, year, month) class_eval do old = "_deprecated_#{name}" alias_method old, name define_method name do |*args, &block| klass = is_a? Module target = klass ? "#{self}." : "#{self.class}#" msg = [ "NOTE: #{target}#{name} is deprecated", repl == :none ? " with no replacement" : "; use #{repl} instead", format(". It will be removed on or after %4d-%02d.", year, month), "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", ] warn "#{msg.join}." unless Gem::Deprecate.skip send old, *args, &block end ruby2_keywords name if respond_to?(:ruby2_keywords, true) end end ## # Simple deprecation method that deprecates +name+ by wrapping it up # in a dummy method. It warns on each call to the dummy method # telling the user of +repl+ (unless +repl+ is :none) and the # Rubygems version that it is planned to go away. def rubygems_deprecate(name, replacement = :none, version = nil) class_eval do old = "_deprecated_#{name}" alias_method old, name define_method name do |*args, &block| klass = is_a? Module target = klass ? "#{self}." : "#{self.class}#" version ||= Gem::Deprecate.next_rubygems_major_version msg = [ "NOTE: #{target}#{name} is deprecated", replacement == :none ? " with no replacement" : "; use #{replacement} instead", ". It will be removed in Rubygems #{version}", "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", ] warn "#{msg.join}." unless Gem::Deprecate.skip send old, *args, &block end ruby2_keywords name if respond_to?(:ruby2_keywords, true) end end # Deprecation method to deprecate Rubygems commands def rubygems_deprecate_command(version = nil) class_eval do define_method "deprecated?" do true end define_method "deprecation_warning" do version ||= Gem::Deprecate.next_rubygems_major_version msg = [ "#{command} command is deprecated", ". It will be removed in Rubygems #{version}.\n", ] alert_warning msg.join.to_s unless Gem::Deprecate.skip end end end module_function :rubygems_deprecate, :rubygems_deprecate_command, :skip_during end end PK!<security/signer.rbnu[# frozen_string_literal: true ## # Basic OpenSSL-based package signing class. require_relative "../user_interaction" class Gem::Security::Signer include Gem::UserInteraction ## # The chain of certificates for signing including the signing certificate attr_accessor :cert_chain ## # The private key for the signing certificate attr_accessor :key ## # The digest algorithm used to create the signature attr_reader :digest_algorithm ## # The name of the digest algorithm, used to pull digests out of the hash by # name. attr_reader :digest_name # :nodoc: ## # Gem::Security::Signer options attr_reader :options DEFAULT_OPTIONS = { expiration_length_days: 365, }.freeze ## # Attempts to re-sign an expired cert with a given private key def self.re_sign_cert(expired_cert, expired_cert_path, private_key) return unless expired_cert.not_after < Time.now expiry = expired_cert.not_after.strftime("%Y%m%d%H%M%S") expired_cert_file = "#{File.basename(expired_cert_path)}.expired.#{expiry}" new_expired_cert_path = File.join(Gem.user_home, ".gem", expired_cert_file) Gem::Security.write(expired_cert, new_expired_cert_path) re_signed_cert = Gem::Security.re_sign( expired_cert, private_key, Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days ) Gem::Security.write(re_signed_cert, expired_cert_path) yield(expired_cert_path, new_expired_cert_path) if block_given? end ## # Creates a new signer with an RSA +key+ or path to a key, and a certificate # +chain+ containing X509 certificates, encoding certificates or paths to # certificates. def initialize(key, cert_chain, passphrase = nil, options = {}) @cert_chain = cert_chain @key = key @passphrase = passphrase @options = DEFAULT_OPTIONS.merge(options) unless @key default_key = File.join Gem.default_key_path @key = default_key if File.exist? default_key end unless @cert_chain default_cert = File.join Gem.default_cert_path @cert_chain = [default_cert] if File.exist? default_cert end @digest_name = Gem::Security::DIGEST_NAME @digest_algorithm = Gem::Security.create_digest(@digest_name) if @key && !@key.is_a?(OpenSSL::PKey::PKey) @key = OpenSSL::PKey.read(File.read(@key), @passphrase) end if @cert_chain @cert_chain = @cert_chain.compact.map do |cert| next cert if OpenSSL::X509::Certificate === cert cert = File.read cert if File.exist? cert OpenSSL::X509::Certificate.new cert end load_cert_chain end end ## # Extracts the full name of +cert+. If the certificate has a subjectAltName # this value is preferred, otherwise the subject is used. def extract_name(cert) # :nodoc: subject_alt_name = cert.extensions.find {|e| e.oid == "subjectAltName" } if subject_alt_name /\Aemail:/ =~ subject_alt_name.value # rubocop:disable Performance/StartWith $' || subject_alt_name.value else cert.subject end end ## # Loads any missing issuers in the cert chain from the trusted certificates. # # If the issuer does not exist it is ignored as it will be checked later. def load_cert_chain # :nodoc: return if @cert_chain.empty? while @cert_chain.first.issuer.to_s != @cert_chain.first.subject.to_s do issuer = Gem::Security.trust_dir.issuer_of @cert_chain.first break unless issuer # cert chain is verified later @cert_chain.unshift issuer end end ## # Sign data with given digest algorithm def sign(data) return unless @key raise Gem::Security::Exception, "no certs provided" if @cert_chain.empty? if @cert_chain.length == 1 && @cert_chain.last.not_after < Time.now alert("Your certificate has expired, trying to re-sign it...") re_sign_key( expiration_length: (Gem::Security::ONE_DAY * options[:expiration_length_days]) ) end full_name = extract_name @cert_chain.last Gem::Security::SigningPolicy.verify @cert_chain, @key, {}, {}, full_name @key.sign @digest_algorithm.new, data end ## # Attempts to re-sign the private key if the signing certificate is expired. # # The key will be re-signed if: # * The expired certificate is self-signed # * The expired certificate is saved at ~/.gem/gem-public_cert.pem # and the private key is saved at ~/.gem/gem-private_key.pem # * There is no file matching the expiry date at # ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S # # If the signing certificate can be re-signed the expired certificate will # be saved as ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S where the # expiry time (not after) is used for the timestamp. def re_sign_key(expiration_length: Gem::Security::ONE_YEAR) # :nodoc: old_cert = @cert_chain.last disk_cert_path = File.join(Gem.default_cert_path) disk_cert = begin File.read(disk_cert_path) rescue StandardError nil end disk_key_path = File.join(Gem.default_key_path) disk_key = begin OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue StandardError nil end return unless disk_key if disk_key.to_pem == @key.to_pem && disk_cert == old_cert.to_pem expiry = old_cert.not_after.strftime("%Y%m%d%H%M%S") old_cert_file = "gem-public_cert.pem.expired.#{expiry}" old_cert_path = File.join(Gem.user_home, ".gem", old_cert_file) unless File.exist?(old_cert_path) Gem::Security.write(old_cert, old_cert_path) cert = Gem::Security.re_sign(old_cert, @key, expiration_length) Gem::Security.write(cert, disk_cert_path) alert("Your cert: #{disk_cert_path} has been auto re-signed with the key: #{disk_key_path}") alert("Your expired cert will be located at: #{old_cert_path}") @cert_chain = [cert] end end end end PK!@D security/trust_dir.rbnu[# frozen_string_literal: true ## # The TrustDir manages the trusted certificates for gem signature # verification. class Gem::Security::TrustDir ## # Default permissions for the trust directory and its contents DEFAULT_PERMISSIONS = { trust_dir: 0o700, trusted_cert: 0o600, }.freeze ## # The directory where trusted certificates will be stored. attr_reader :dir ## # Creates a new TrustDir using +dir+ where the directory and file # permissions will be checked according to +permissions+ def initialize(dir, permissions = DEFAULT_PERMISSIONS) @dir = dir @permissions = permissions @digester = Gem::Security.create_digest end ## # Returns the path to the trusted +certificate+ def cert_path(certificate) name_path certificate.subject end ## # Enumerates trusted certificates. def each_certificate return enum_for __method__ unless block_given? glob = File.join @dir, "*.pem" Dir[glob].each do |certificate_file| certificate = load_certificate certificate_file yield certificate, certificate_file rescue OpenSSL::X509::CertificateError next # HACK: warn end end ## # Returns the issuer certificate of the given +certificate+ if it exists in # the trust directory. def issuer_of(certificate) path = name_path certificate.issuer return unless File.exist? path load_certificate path end ## # Returns the path to the trusted certificate with the given ASN.1 +name+ def name_path(name) digest = @digester.hexdigest name.to_s File.join @dir, "cert-#{digest}.pem" end ## # Loads the given +certificate_file+ def load_certificate(certificate_file) pem = File.read certificate_file OpenSSL::X509::Certificate.new pem end ## # Add a certificate to trusted certificate list. def trust_cert(certificate) verify destination = cert_path certificate File.open destination, "wb", 0o600 do |io| io.write certificate.to_pem io.chmod(@permissions[:trusted_cert]) end end ## # Make sure the trust directory exists. If it does exist, make sure it's # actually a directory. If not, then create it with the appropriate # permissions. def verify require "fileutils" if File.exist? @dir raise Gem::Security::Exception, "trust directory #{@dir} is not a directory" unless File.directory? @dir FileUtils.chmod 0o700, @dir else FileUtils.mkdir_p @dir, mode: @permissions[:trust_dir] end end end PK!Qhz==security/policy.rbnu[# frozen_string_literal: true require_relative "../user_interaction" ## # A Gem::Security::Policy object encapsulates the settings for verifying # signed gem files. This is the base class. You can either declare an # instance of this or use one of the preset security policies in # Gem::Security::Policies. class Gem::Security::Policy include Gem::UserInteraction attr_reader :name attr_accessor :only_signed attr_accessor :only_trusted attr_accessor :verify_chain attr_accessor :verify_data attr_accessor :verify_root attr_accessor :verify_signer ## # Create a new Gem::Security::Policy object with the given mode and # options. def initialize(name, policy = {}, opt = {}) @name = name @opt = opt # Default to security @only_signed = true @only_trusted = true @verify_chain = true @verify_data = true @verify_root = true @verify_signer = true policy.each_pair do |key, val| case key when :verify_data then @verify_data = val when :verify_signer then @verify_signer = val when :verify_chain then @verify_chain = val when :verify_root then @verify_root = val when :only_trusted then @only_trusted = val when :only_signed then @only_signed = val end end end ## # Verifies each certificate in +chain+ has signed the following certificate # and is valid for the given +time+. def check_chain(chain, time) raise Gem::Security::Exception, "missing signing chain" unless chain raise Gem::Security::Exception, "empty signing chain" if chain.empty? begin chain.each_cons 2 do |issuer, cert| check_cert cert, issuer, time end true rescue Gem::Security::Exception => e raise Gem::Security::Exception, "invalid signing chain: #{e.message}" end end ## # Verifies that +data+ matches the +signature+ created by +public_key+ and # the +digest+ algorithm. def check_data(public_key, digest, signature, data) raise Gem::Security::Exception, "invalid signature" unless public_key.verify digest, signature, data.digest true end ## # Ensures that +signer+ is valid for +time+ and was signed by the +issuer+. # If the +issuer+ is +nil+ no verification is performed. def check_cert(signer, issuer, time) raise Gem::Security::Exception, "missing signing certificate" unless signer message = "certificate #{signer.subject}" if (not_before = signer.not_before) && not_before > time raise Gem::Security::Exception, "#{message} not valid before #{not_before}" end if (not_after = signer.not_after) && not_after < time raise Gem::Security::Exception, "#{message} not valid after #{not_after}" end if issuer && !signer.verify(issuer.public_key) raise Gem::Security::Exception, "#{message} was not issued by #{issuer.subject}" end true end ## # Ensures the public key of +key+ matches the public key in +signer+ def check_key(signer, key) unless signer && key return true unless @only_signed raise Gem::Security::Exception, "missing key or signature" end raise Gem::Security::Exception, "certificate #{signer.subject} does not match the signing key" unless signer.check_private_key(key) true end ## # Ensures the root certificate in +chain+ is self-signed and valid for # +time+. def check_root(chain, time) raise Gem::Security::Exception, "missing signing chain" unless chain root = chain.first raise Gem::Security::Exception, "missing root certificate" unless root raise Gem::Security::Exception, "root certificate #{root.subject} is not self-signed " \ "(issuer #{root.issuer})" if root.issuer != root.subject check_cert root, root, time end ## # Ensures the root of +chain+ has a trusted certificate in Gem::Security.trust_dir and # the digests of the two certificates match according to +digester+ def check_trust(chain, digester, trust_dir) raise Gem::Security::Exception, "missing signing chain" unless chain root = chain.first raise Gem::Security::Exception, "missing root certificate" unless root path = Gem::Security.trust_dir.cert_path root unless File.exist? path message = "root cert #{root.subject} is not trusted".dup message << " (root of signing cert #{chain.last.subject})" if chain.length > 1 raise Gem::Security::Exception, message end save_cert = OpenSSL::X509::Certificate.new File.read path save_dgst = digester.digest save_cert.public_key.to_pem pkey_str = root.public_key.to_pem cert_dgst = digester.digest pkey_str raise Gem::Security::Exception, "trusted root certificate #{root.subject} checksum " \ "does not match signing root certificate checksum" unless save_dgst == cert_dgst true end ## # Extracts the email or subject from +certificate+ def subject(certificate) # :nodoc: certificate.extensions.each do |extension| next unless extension.oid == "subjectAltName" return extension.value end certificate.subject.to_s end def inspect # :nodoc: format("[Policy: %s - data: %p signer: %p chain: %p root: %p " \ "signed-only: %p trusted-only: %p]", @name, @verify_chain, @verify_data, @verify_root, @verify_signer, @only_signed, @only_trusted) end ## # For +full_name+, verifies the certificate +chain+ is valid, the +digests+ # match the signatures +signatures+ created by the signer depending on the # +policy+ settings. # # If +key+ is given it is used to validate the signing certificate. def verify(chain, key = nil, digests = {}, signatures = {}, full_name = "(unknown)") if signatures.empty? if @only_signed raise Gem::Security::Exception, "unsigned gems are not allowed by the #{name} policy" elsif digests.empty? # lack of signatures is irrelevant if there is nothing to check # against else alert_warning "#{full_name} is not signed" return end end opt = @opt digester = Gem::Security.create_digest trust_dir = opt[:trust_dir] time = Time.now _, signer_digests = digests.find do |_algorithm, file_digests| file_digests.values.first.name == Gem::Security::DIGEST_NAME end if @verify_data raise Gem::Security::Exception, "no digests provided (probable bug)" if signer_digests.nil? || signer_digests.empty? else signer_digests = {} end signer = chain.last check_key signer, key if key check_cert signer, nil, time if @verify_signer check_chain chain, time if @verify_chain check_root chain, time if @verify_root if @only_trusted check_trust chain, digester, trust_dir elsif signatures.empty? && digests.empty? # trust is irrelevant if there's no signatures to verify else alert_warning "#{subject signer} is not trusted for #{full_name}" end signatures.each do |file, _| digest = signer_digests[file] raise Gem::Security::Exception, "missing digest for #{file}" unless digest end signer_digests.each do |file, digest| signature = signatures[file] raise Gem::Security::Exception, "missing signature for #{file}" unless signature check_data signer.public_key, digester, signature, digest if @verify_data end true end ## # Extracts the certificate chain from the +spec+ and calls #verify to ensure # the signatures and certificate chain is valid according to the policy.. def verify_signatures(spec, digests, signatures) chain = spec.cert_chain.map do |cert_pem| OpenSSL::X509::Certificate.new cert_pem end verify chain, nil, digests, signatures, spec.full_name true end alias_method :to_s, :name # :nodoc: end PK!ҽ? ? security/policies.rbnu[# frozen_string_literal: true module Gem::Security ## # No security policy: all package signature checks are disabled. NoSecurity = Policy.new( "No Security", verify_data: false, verify_signer: false, verify_chain: false, verify_root: false, only_trusted: false, only_signed: false ) ## # AlmostNo security policy: only verify that the signing certificate is the # one that actually signed the data. Make no attempt to verify the signing # certificate chain. # # This policy is basically useless. better than nothing, but can still be # easily spoofed, and is not recommended. AlmostNoSecurity = Policy.new( "Almost No Security", verify_data: true, verify_signer: false, verify_chain: false, verify_root: false, only_trusted: false, only_signed: false ) ## # Low security policy: only verify that the signing certificate is actually # the gem signer, and that the signing certificate is valid. # # This policy is better than nothing, but can still be easily spoofed, and # is not recommended. LowSecurity = Policy.new( "Low Security", verify_data: true, verify_signer: true, verify_chain: false, verify_root: false, only_trusted: false, only_signed: false ) ## # Medium security policy: verify the signing certificate, verify the signing # certificate chain all the way to the root certificate, and only trust root # certificates that we have explicitly allowed trust for. # # This security policy is reasonable, but it allows unsigned packages, so a # malicious person could simply delete the package signature and pass the # gem off as unsigned. MediumSecurity = Policy.new( "Medium Security", verify_data: true, verify_signer: true, verify_chain: true, verify_root: true, only_trusted: true, only_signed: false ) ## # High security policy: only allow signed gems to be installed, verify the # signing certificate, verify the signing certificate chain all the way to # the root certificate, and only trust root certificates that we have # explicitly allowed trust for. # # This security policy is significantly more difficult to bypass, and offers # a reasonable guarantee that the contents of the gem have not been altered. HighSecurity = Policy.new( "High Security", verify_data: true, verify_signer: true, verify_chain: true, verify_root: true, only_trusted: true, only_signed: true ) ## # Policy used to verify a certificate and key when signing a gem SigningPolicy = Policy.new( "Signing Policy", verify_data: false, verify_signer: true, verify_chain: true, verify_root: true, only_trusted: false, only_signed: false ) ## # Hash of configured security policies Policies = { "NoSecurity" => NoSecurity, "AlmostNoSecurity" => AlmostNoSecurity, "LowSecurity" => LowSecurity, "MediumSecurity" => MediumSecurity, "HighSecurity" => HighSecurity, # SigningPolicy is not intended for use by `gem -P` so do not list it }.freeze end PK!('}݇ validator.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "package" require_relative "installer" ## # Validator performs various gem file and gem database validation class Gem::Validator include Gem::UserInteraction def initialize # :nodoc: require "find" end private def find_files_for_gem(gem_directory) installed_files = [] Find.find gem_directory do |file_name| fn = file_name[gem_directory.size..file_name.size - 1].sub(%r{^/}, "") installed_files << fn unless fn.empty? || fn.include?("CVS") || File.directory?(file_name) end installed_files end public ## # Describes a problem with a file in a gem. ErrorData = Struct.new :path, :problem do def <=>(other) # :nodoc: return nil unless self.class === other [path, problem] <=> [other.path, other.problem] end end ## # Checks the gem directory for the following potential # inconsistencies/problems: # # * Checksum gem itself # * For each file in each gem, check consistency of installed versions # * Check for files that aren't part of the gem but are in the gems directory # * 1 cache - 1 spec - 1 directory. # # returns a hash of ErrorData objects, keyed on the problem gem's name. #-- # TODO needs further cleanup def alien(gems = []) errors = Hash.new {|h,k| h[k] = {} } Gem::Specification.each do |spec| unless gems.empty? next unless gems.include? spec.name end next if spec.default_gem? gem_name = spec.file_name gem_path = spec.cache_file spec_path = spec.spec_file gem_directory = spec.full_gem_path unless File.directory? gem_directory errors[gem_name][spec.full_name] = "Gem registered but doesn't exist at #{gem_directory}" next end unless File.exist? spec_path errors[gem_name][spec_path] = "Spec file missing for installed gem" end begin unless File.readable?(gem_path) raise Gem::VerificationError, "missing gem file #{gem_path}" end good, gone, unreadable = nil, nil, nil, nil File.open gem_path, Gem.binary_mode do |_file| package = Gem::Package.new gem_path good, gone = package.contents.partition do |file_name| File.exist? File.join(gem_directory, file_name) end gone.sort.each do |path| errors[gem_name][path] = "Missing file" end good, unreadable = good.partition do |file_name| File.readable? File.join(gem_directory, file_name) end unreadable.sort.each do |path| errors[gem_name][path] = "Unreadable file" end good.each do |entry, data| next unless data # HACK: `gem check -a mkrf` source = File.join gem_directory, entry["path"] File.open source, Gem.binary_mode do |f| unless f.read == data errors[gem_name][entry["path"]] = "Modified from original" end end end end installed_files = find_files_for_gem(gem_directory) extras = installed_files - good - unreadable extras.each do |extra| errors[gem_name][extra] = "Extra file" end rescue Gem::VerificationError => e errors[gem_name][gem_path] = e.message end end errors.each do |name, subhash| errors[name] = subhash.map do |path, msg| ErrorData.new path, msg end.sort end errors end end PK!ǀext.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ ## # Classes for building C extensions live here. module Gem::Ext; end require_relative "ext/build_error" require_relative "ext/builder" require_relative "ext/configure_builder" require_relative "ext/ext_conf_builder" require_relative "ext/rake_builder" require_relative "ext/cmake_builder" require_relative "ext/cargo_builder" PK!aGudependency_list.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "vendored_tsort" ## # Gem::DependencyList is used for installing and uninstalling gems in the # correct order to avoid conflicts. #-- # TODO: It appears that all but topo-sort functionality is being duplicated # (or is planned to be duplicated) elsewhere in rubygems. Is the majority of # this class necessary anymore? Especially #ok?, #why_not_ok? class Gem::DependencyList attr_reader :specs include Enumerable include Gem::TSort ## # Allows enabling/disabling use of development dependencies attr_accessor :development ## # Creates a DependencyList from the current specs. def self.from_specs list = new list.add(*Gem::Specification.to_a) list end ## # Creates a new DependencyList. If +development+ is true, development # dependencies will be included. def initialize(development = false) @specs = [] @development = development end ## # Adds +gemspecs+ to the dependency list. def add(*gemspecs) @specs.concat gemspecs end def clear @specs.clear end ## # Return a list of the gem specifications in the dependency list, sorted in # order so that no gemspec in the list depends on a gemspec earlier in the # list. # # This is useful when removing gems from a set of installed gems. By # removing them in the returned order, you don't get into as many dependency # issues. # # If there are circular dependencies (yuck!), then gems will be returned in # order until only the circular dependents and anything they reference are # left. Then arbitrary gemspecs will be returned until the circular # dependency is broken, after which gems will be returned in dependency # order again. def dependency_order sorted = strongly_connected_components.flatten result = [] seen = {} sorted.each do |spec| if index = seen[spec.name] if result[index].version < spec.version result[index] = spec end else seen[spec.name] = result.length result << spec end end result.reverse end ## # Iterator over dependency_order def each(&block) dependency_order.each(&block) end def find_name(full_name) @specs.find {|spec| spec.full_name == full_name } end def inspect # :nodoc: format("%s %p>", super[0..-2], map(&:full_name)) end ## # Are all the dependencies in the list satisfied? def ok? why_not_ok?(:quick).empty? end def why_not_ok?(quick = false) unsatisfied = Hash.new {|h,k| h[k] = [] } each do |spec| spec.runtime_dependencies.each do |dep| inst = Gem::Specification.any? do |installed_spec| dep.name == installed_spec.name && dep.requirement.satisfied_by?(installed_spec.version) end unless inst || @specs.find {|s| s.satisfies_requirement? dep } unsatisfied[spec.name] << dep return unsatisfied if quick end end end unsatisfied end ## # It is ok to remove a gemspec from the dependency list? # # If removing the gemspec creates breaks a currently ok dependency, then it # is NOT ok to remove the gemspec. def ok_to_remove?(full_name, check_dev = true) gem_to_remove = find_name full_name # If the state is inconsistent, at least don't crash return true unless gem_to_remove siblings = @specs.find_all do |s| s.name == gem_to_remove.name && s.full_name != gem_to_remove.full_name end deps = [] @specs.each do |spec| check = check_dev ? spec.dependencies : spec.runtime_dependencies check.each do |dep| deps << dep if gem_to_remove.satisfies_requirement?(dep) end end deps.all? do |dep| siblings.any? do |s| s.satisfies_requirement? dep end end end ## # Remove everything in the DependencyList that matches but doesn't # satisfy items in +dependencies+ (a hash of gem names to arrays of # dependencies). def remove_specs_unsatisfied_by(dependencies) specs.reject! do |spec| dep = dependencies[spec.name] dep && !dep.requirement.satisfied_by?(spec.version) end end ## # Removes the gemspec matching +full_name+ from the dependency list def remove_by_name(full_name) @specs.delete_if {|spec| spec.full_name == full_name } end ## # Return a hash of predecessors. result[spec] is an Array of # gemspecs that have a dependency satisfied by the named gemspec. def spec_predecessors result = Hash.new {|h,k| h[k] = [] } specs = @specs.sort.reverse specs.each do |spec| specs.each do |other| next if spec == other other.dependencies.each do |dep| if spec.satisfies_requirement? dep result[spec] << other end end end end result end def tsort_each_node(&block) @specs.each(&block) end def tsort_each_child(node) specs = @specs.sort.reverse dependencies = node.runtime_dependencies dependencies.push(*node.development_dependencies) if @development dependencies.each do |dep| specs.each do |spec| if spec.satisfies_requirement? dep yield spec break end end end end private ## # Count the number of gemspecs in the list +specs+ that are not in # +ignored+. def active_count(specs, ignored) specs.count {|spec| ignored[spec.full_name].nil? } end end PK!Uspec_fetcher.rbnu[# frozen_string_literal: true require_relative "remote_fetcher" require_relative "user_interaction" require_relative "errors" require_relative "text" require_relative "name_tuple" ## # SpecFetcher handles metadata updates from remote gem repositories. class Gem::SpecFetcher include Gem::UserInteraction include Gem::Text ## # Cache of latest specs attr_reader :latest_specs # :nodoc: ## # Sources for this SpecFetcher attr_reader :sources # :nodoc: ## # Cache of all released specs attr_reader :specs # :nodoc: ## # Cache of prerelease specs attr_reader :prerelease_specs # :nodoc: @fetcher = nil ## # Default fetcher instance. Use this instead of ::new to reduce object # allocation. def self.fetcher @fetcher ||= new end def self.fetcher=(fetcher) # :nodoc: @fetcher = fetcher end ## # Creates a new SpecFetcher. Ordinarily you want to use the default fetcher # from Gem::SpecFetcher::fetcher which uses the Gem.sources. # # If you need to retrieve specifications from a different +source+, you can # send it as an argument. def initialize(sources = nil) @sources = sources || Gem.sources @update_cache = begin File.stat(Gem.user_home).uid == Process.uid rescue Errno::EACCES, Errno::ENOENT false end @specs = {} @latest_specs = {} @prerelease_specs = {} @caches = { latest: @latest_specs, prerelease: @prerelease_specs, released: @specs, } @fetcher = Gem::RemoteFetcher.fetcher end ## # # Find and fetch gem name tuples that match +dependency+. # # If +matching_platform+ is false, gems for all platforms are returned. def search_for_dependency(dependency, matching_platform = true) found = {} rejected_specs = {} list, errors = available_specs(dependency.identity) list.each do |source, specs| if dependency.name.is_a?(String) && specs.respond_to?(:bsearch) start_index = (0...specs.length).bsearch {|i| specs[i].name >= dependency.name } end_index = (0...specs.length).bsearch {|i| specs[i].name > dependency.name } specs = specs[start_index...end_index] if start_index && end_index end found[source] = specs.select do |tup| if dependency.match?(tup) if matching_platform && !Gem::Platform.match_gem?(tup.platform, tup.name) pm = ( rejected_specs[dependency] ||= \ Gem::PlatformMismatch.new(tup.name, tup.version)) pm.add_platform tup.platform false else true end end end end errors += rejected_specs.values tuples = [] found.each do |source, specs| specs.each do |s| tuples << [s, source] end end tuples = tuples.sort_by {|x| x[0].version } [tuples, errors] end ## # Return all gem name tuples who's names match +obj+ def detect(type = :complete) tuples = [] list, _ = available_specs(type) list.each do |source, specs| specs.each do |tup| if yield(tup) tuples << [tup, source] end end end tuples end ## # Find and fetch specs that match +dependency+. # # If +matching_platform+ is false, gems for all platforms are returned. def spec_for_dependency(dependency, matching_platform = true) tuples, errors = search_for_dependency(dependency, matching_platform) specs = [] tuples.each do |tup, source| spec = source.fetch_spec(tup) rescue Gem::RemoteFetcher::FetchError => e errors << Gem::SourceFetchProblem.new(source, e) else specs << [spec, source] end [specs, errors] end ## # Suggests gems based on the supplied +gem_name+. Returns an array of # alternative gem names. def suggest_gems_from_name(gem_name, type = :latest, num_results = 5) gem_name = gem_name.downcase.tr("_-", "") # All results for 3-character-or-shorter (minus hyphens/underscores) gem # names get rejected, so we just return an empty array immediately instead. return [] if gem_name.length <= 3 max = gem_name.size / 2 names = available_specs(type).first.values.flatten(1) min_length = gem_name.length - max max_length = gem_name.length + max gem_name_with_postfix = "#{gem_name}ruby" gem_name_with_prefix = "ruby#{gem_name}" matches = names.filter_map do |n| len = n.name.length # If the gem doesn't support the current platform, bail early. next unless n.match_platform? # If the length is min_length or shorter, we've done `max` deletions. # This would be rejected later, so we skip it for performance. next if len <= min_length # The candidate name, normalized the same as gem_name. normalized_name = n.name.downcase normalized_name.tr!("_-", "") # If the gem is "{NAME}-ruby" and "ruby-{NAME}", we want to return it. # But we already removed hyphens, so we check "{NAME}ruby" and "ruby{NAME}". next [n.name, 0] if normalized_name == gem_name_with_postfix next [n.name, 0] if normalized_name == gem_name_with_prefix # If the length is max_length or longer, we've done `max` insertions. # This would be rejected later, so we skip it for performance. next if len >= max_length # If we found an exact match (after stripping underscores and hyphens), # that's our most likely candidate. # Return it immediately, and skip the rest of the loop. return [n.name] if normalized_name == gem_name distance = levenshtein_distance gem_name, normalized_name # Skip current candidate, if the edit distance is greater than allowed. next if distance >= max # If all else fails, return the name and the calculated distance. [n.name, distance] end matches = if matches.empty? && type != :prerelease suggest_gems_from_name gem_name, :prerelease else matches.uniq.sort_by {|_name, dist| dist } end matches.map {|name, _dist| name }.uniq.first(num_results) end ## # Returns a list of gems available for each source in Gem::sources. # # +type+ can be one of 3 values: # :released => Return the list of all released specs # :complete => Return the list of all specs # :latest => Return the list of only the highest version of each gem # :prerelease => Return the list of all prerelease only specs # def available_specs(type) errors = [] list = {} @sources.each_source do |source| names = case type when :latest tuples_for source, :latest when :released tuples_for source, :released when :complete names = tuples_for(source, :prerelease, true) + tuples_for(source, :released) names.sort when :abs_latest names = tuples_for(source, :prerelease, true) + tuples_for(source, :latest) names.sort when :prerelease tuples_for(source, :prerelease) else raise Gem::Exception, "Unknown type - :#{type}" end rescue Gem::RemoteFetcher::FetchError => e errors << Gem::SourceFetchProblem.new(source, e) else list[source] = names end [list, errors] end ## # Retrieves NameTuples from +source+ of the given +type+ (:prerelease, # etc.). If +gracefully_ignore+ is true, errors are ignored. def tuples_for(source, type, gracefully_ignore = false) # :nodoc: @caches[type][source.uri] ||= source.load_specs(type).sort_by(&:name) rescue Gem::RemoteFetcher::FetchError raise unless gracefully_ignore [] end end PK!-hci_detector.rbnu[# frozen_string_literal: true module Gem module CIDetector # NOTE: Any changes made here will need to be made to both lib/rubygems/ci_detector.rb and # bundler/lib/bundler/ci_detector.rb (which are enforced duplicates). # TODO: Drop that duplication once bundler drops support for RubyGems 3.4 # # ## Recognized CI providers, their signifiers, and the relevant docs ## # # Travis CI - CI, TRAVIS https://docs.travis-ci.com/user/environment-variables/#default-environment-variables # Cirrus CI - CI, CIRRUS_CI https://cirrus-ci.org/guide/writing-tasks/#environment-variables # Circle CI - CI, CIRCLECI https://circleci.com/docs/variables/#built-in-environment-variables # Gitlab CI - CI, GITLAB_CI https://docs.gitlab.com/ee/ci/variables/ # AppVeyor - CI, APPVEYOR https://www.appveyor.com/docs/environment-variables/ # CodeShip - CI_NAME https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables#_default_environment_variables # dsari - CI, DSARI https://github.com/rfinnie/dsari#running # Jenkins - BUILD_NUMBER https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables # TeamCity - TEAMCITY_VERSION https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters # Appflow - CI_BUILD_ID https://ionic.io/docs/appflow/automation/environments#predefined-environments # TaskCluster - TASKCLUSTER_ROOT_URL https://docs.taskcluster.net/docs/manual/design/env-vars # Semaphore - CI, SEMAPHORE https://docs.semaphoreci.com/ci-cd-environment/environment-variables/ # BuildKite - CI, BUILDKITE https://buildkite.com/docs/pipelines/environment-variables # GoCD - GO_SERVER_URL https://docs.gocd.org/current/faq/dev_use_current_revision_in_build.html # GH Actions - CI, GITHUB_ACTIONS https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables # # ### Some "standard" ENVs that multiple providers may set ### # # * CI - this is set by _most_ (but not all) CI providers now; it's approaching a standard. # * CI_NAME - Not as frequently used, but some providers set this to specify their own name # Any of these being set is a reasonably reliable indicator that we are # executing in a CI environment. ENV_INDICATORS = [ "CI", "CI_NAME", "CONTINUOUS_INTEGRATION", "BUILD_NUMBER", "CI_APP_ID", "CI_BUILD_ID", "CI_BUILD_NUMBER", "RUN_ID", "TASKCLUSTER_ROOT_URL", ].freeze # For each CI, this env suffices to indicate that we're on _that_ CI's # containers. (A few of them only supply a CI_NAME variable, which is also # nice). And if they set "CI" but we can't tell which one they are, we also # want to know that - a bare "ci" without another token tells us as much. ENV_DESCRIPTORS = { "TRAVIS" => "travis", "CIRCLECI" => "circle", "CIRRUS_CI" => "cirrus", "DSARI" => "dsari", "SEMAPHORE" => "semaphore", "JENKINS_URL" => "jenkins", "BUILDKITE" => "buildkite", "GO_SERVER_URL" => "go", "GITLAB_CI" => "gitlab", "GITHUB_ACTIONS" => "github", "TASKCLUSTER_ROOT_URL" => "taskcluster", "CI" => "ci", }.freeze def self.ci? ENV_INDICATORS.any? {|var| ENV.include?(var) } end def self.ci_strings matching_names = ENV_DESCRIPTORS.select {|env, _| ENV[env] }.values matching_names << ENV["CI_NAME"].downcase if ENV["CI_NAME"] matching_names.reject(&:empty?).sort.uniq end end end PK!resolver/lock_set.rbnu[# frozen_string_literal: true ## # A set of gems from a gem dependencies lockfile. class Gem::Resolver::LockSet < Gem::Resolver::Set attr_reader :specs # :nodoc: ## # Creates a new LockSet from the given +sources+ def initialize(sources) super() @sources = sources.map do |source| Gem::Source::Lock.new source end @specs = [] end ## # Creates a new IndexSpecification in this set using the given +name+, # +version+ and +platform+. # # The specification's set will be the current set, and the source will be # the current set's source. def add(name, version, platform) # :nodoc: version = Gem::Version.new version specs = [ Gem::Resolver::LockSpecification.new(self, name, version, @sources, platform), ] @specs.concat specs specs end ## # Returns an Array of IndexSpecification objects matching the # DependencyRequest +req+. def find_all(req) @specs.select do |spec| req.match? spec end end ## # Loads a Gem::Specification with the given +name+, +version+ and # +platform+. +source+ is ignored. def load_spec(name, version, platform, source) # :nodoc: dep = Gem::Dependency.new name, version found = @specs.find do |spec| dep.matches_spec?(spec) && spec.platform == platform end tuple = Gem::NameTuple.new found.name, found.version, found.platform found.source.fetch_spec tuple end def pretty_print(q) # :nodoc: q.group 2, "[LockSet", "]" do q.breakable q.text "source:" q.breakable q.pp @source q.breakable q.text "specs:" q.breakable q.pp @specs.map(&:full_name) end end end PK!h  resolver/dependency_request.rbnu[# frozen_string_literal: true ## # Used Internally. Wraps a Dependency object to also track which spec # contained the Dependency. class Gem::Resolver::DependencyRequest ## # The wrapped Gem::Dependency attr_reader :dependency ## # The request for this dependency. attr_reader :requester ## # Creates a new DependencyRequest for +dependency+ from +requester+. # +requester may be nil if the request came from a user. def initialize(dependency, requester) @dependency = dependency @requester = requester end def ==(other) # :nodoc: case other when Gem::Dependency @dependency == other when Gem::Resolver::DependencyRequest @dependency == other.dependency else false end end ## # Is this dependency a development dependency? def development? @dependency.type == :development end ## # Does this dependency request match +spec+? # # NOTE: #match? only matches prerelease versions when #dependency is a # prerelease dependency. def match?(spec, allow_prerelease = false) @dependency.match? spec, nil, allow_prerelease end ## # Does this dependency request match +spec+? # # NOTE: #matches_spec? matches prerelease versions. See also #match? def matches_spec?(spec) @dependency.matches_spec? spec end ## # The name of the gem this dependency request is requesting. def name @dependency.name end def type @dependency.type end ## # Indicate that the request is for a gem explicitly requested by the user def explicit? @requester.nil? end ## # Indicate that the request is for a gem requested as a dependency of # another gem def implicit? !explicit? end ## # Return a String indicating who caused this request to be added (only # valid for implicit requests) def request_context @requester ? @requester.request : "(unknown)" end def pretty_print(q) # :nodoc: q.group 2, "[Dependency request ", "]" do q.breakable q.text @dependency.to_s q.breakable q.text " requested by " q.pp @requester end end ## # The version requirement for this dependency request def requirement @dependency.requirement end def to_s # :nodoc: @dependency.to_s end end PK!3=##resolver/local_specification.rbnu[# frozen_string_literal: true ## # A LocalSpecification comes from a .gem file on the local filesystem. class Gem::Resolver::LocalSpecification < Gem::Resolver::SpecSpecification ## # Returns +true+ if this gem is installable for the current platform. def installable_platform? return true if @source.is_a? Gem::Source::SpecificFile super end def local? # :nodoc: true end def pretty_print(q) # :nodoc: q.group 2, "[LocalSpecification", "]" do q.breakable q.text "name: #{name}" q.breakable q.text "version: #{version}" q.breakable q.text "platform: #{platform}" q.breakable q.text "dependencies:" q.breakable q.pp dependencies q.breakable q.text "source: #{@source.path}" end end end PK!v_ resolver/api_specification.rbnu[# frozen_string_literal: true ## # Represents a specification retrieved via the Compact Index API. # # This is used to avoid loading the full Specification object when all we need # is the name, version, and dependencies. class Gem::Resolver::APISpecification < Gem::Resolver::Specification ## # We assume that all instances of this class are immutable; # so avoid duplicated generation for performance. @@cache = {} def self.new(set, api_data) cache_key = [set, api_data] cache = @@cache[cache_key] return cache if cache @@cache[cache_key] = super end ## # Creates an APISpecification for the given +set+ from the Compact Index API # +api_data+. # # See https://guides.rubygems.org/rubygems-org-compact-index-api for the # format of the +api_data+. def initialize(set, api_data) super() @set = set @name = api_data[:name] @version = Gem::Version.new(api_data[:number]).freeze @platform = Gem::Platform.new(api_data[:platform]).freeze @original_platform = api_data[:platform].freeze @dependencies = api_data[:dependencies].map do |name, ver| Gem::Dependency.new(name, ver.split(/\s*,\s*/)).freeze end.freeze @required_ruby_version = Gem::Requirement.new(api_data.dig(:requirements, :ruby)).freeze @required_rubygems_version = Gem::Requirement.new(api_data.dig(:requirements, :rubygems)).freeze end def ==(other) # :nodoc: self.class === other && @set == other.set && @name == other.name && @version == other.version && @platform == other.platform end def hash @set.hash ^ @name.hash ^ @version.hash ^ @platform.hash end def fetch_development_dependencies # :nodoc: spec = source.fetch_spec Gem::NameTuple.new @name, @version, @platform @dependencies = spec.dependencies end def installable_platform? # :nodoc: Gem::Platform.match_gem? @platform, @name end def pretty_print(q) # :nodoc: q.group 2, "[APISpecification", "]" do q.breakable q.text "name: #{name}" q.breakable q.text "version: #{version}" q.breakable q.text "platform: #{platform}" q.breakable q.text "dependencies:" q.breakable q.pp @dependencies q.breakable q.text "set uri: #{@set.dep_uri}" end end ## # Fetches a Gem::Specification for this APISpecification. def spec # :nodoc: @spec ||= begin tuple = Gem::NameTuple.new @name, @version, @platform source.fetch_spec tuple rescue Gem::RemoteFetcher::FetchError raise if @original_platform == @platform tuple = Gem::NameTuple.new @name, @version, @original_platform source.fetch_spec tuple end end def source # :nodoc: @set.source end end PK!Kjresolver/stats.rbnu[# frozen_string_literal: true class Gem::Resolver::Stats def initialize @max_depth = 0 @max_requirements = 0 @requirements = 0 @backtracking = 0 @iterations = 0 end def record_depth(stack) if stack.size > @max_depth @max_depth = stack.size end end def record_requirements(reqs) if reqs.size > @max_requirements @max_requirements = reqs.size end end def requirement! @requirements += 1 end def backtracking! @backtracking += 1 end def iteration! @iterations += 1 end PATTERN = "%20s: %d\n" def display $stdout.puts "=== Resolver Statistics ===" $stdout.printf PATTERN, "Max Depth", @max_depth $stdout.printf PATTERN, "Total Requirements", @requirements $stdout.printf PATTERN, "Max Requirements", @max_requirements $stdout.printf PATTERN, "Backtracking #", @backtracking $stdout.printf PATTERN, "Iteration #", @iterations end end PK!Mrresolver/set.rbnu[# frozen_string_literal: true ## # Resolver sets are used to look up specifications (and their # dependencies) used in resolution. This set is abstract. class Gem::Resolver::Set ## # Set to true to disable network access for this set attr_accessor :remote ## # Errors encountered when resolving gems attr_accessor :errors ## # When true, allows matching of requests to prerelease gems. attr_accessor :prerelease def initialize # :nodoc: @prerelease = false @remote = true @errors = [] end ## # The find_all method must be implemented. It returns all Resolver # Specification objects matching the given DependencyRequest +req+. def find_all(req) raise NotImplementedError end ## # The #prefetch method may be overridden, but this is not necessary. This # default implementation does nothing, which is suitable for sets where # looking up a specification is cheap (such as installed gems). # # When overridden, the #prefetch method should look up specifications # matching +reqs+. def prefetch(reqs) end ## # When true, this set is allowed to access the network when looking up # specifications or dependencies. def remote? # :nodoc: @remote end end PK!w w resolver/index_specification.rbnu[# frozen_string_literal: true ## # Represents a possible Specification object returned from IndexSet. Used to # delay needed to download full Specification objects when only the +name+ # and +version+ are needed. class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification ## # An IndexSpecification is created from the index format described in `gem # help generate_index`. # # The +set+ contains other specifications for this (URL) +source+. # # The +name+, +version+ and +platform+ are the name, version and platform of # the gem. def initialize(set, name, version, source, platform) super() @set = set @name = name @version = version @source = source @platform = Gem::Platform.new(platform.to_s) @original_platform = platform.to_s @spec = nil end ## # The dependencies of the gem for this specification def dependencies spec.dependencies end ## # The required_ruby_version constraint for this specification # # A fallback is included because when generated, some marshalled specs have it # set to +nil+. def required_ruby_version spec.required_ruby_version || Gem::Requirement.default end ## # The required_rubygems_version constraint for this specification # # A fallback is included because the original version of the specification # API didn't include that field, so some marshalled specs in the index have it # set to +nil+. def required_rubygems_version spec.required_rubygems_version || Gem::Requirement.default end def ==(other) self.class === other && @name == other.name && @version == other.version && @platform == other.platform end def hash @name.hash ^ @version.hash ^ @platform.hash end def inspect # :nodoc: format("#<%s %s source %s>", self.class, full_name, @source) end def pretty_print(q) # :nodoc: q.group 2, "[Index specification", "]" do q.breakable q.text full_name unless @platform == Gem::Platform::RUBY q.breakable q.text @platform.to_s end q.breakable q.text "source " q.pp @source end end ## # Fetches a Gem::Specification for this IndexSpecification from the #source. def spec # :nodoc: @spec ||= begin tuple = Gem::NameTuple.new @name, @version, @original_platform @source.fetch_spec tuple end end end PK!"~resolver/index_set.rbnu[# frozen_string_literal: true ## # The global rubygems pool represented via the traditional # source index. class Gem::Resolver::IndexSet < Gem::Resolver::Set def initialize(source = nil) # :nodoc: super() @f = if source sources = Gem::SourceList.from [source] Gem::SpecFetcher.new sources else Gem::SpecFetcher.fetcher end @all = Hash.new {|h,k| h[k] = [] } list, errors = @f.available_specs :complete @errors.concat errors list.each do |uri, specs| specs.each do |n| @all[n.name] << [uri, n] end end @specs = {} end ## # Return an array of IndexSpecification objects matching # DependencyRequest +req+. def find_all(req) res = [] return res unless @remote name = req.dependency.name @all[name].each do |uri, n| next unless req.match? n, @prerelease res << Gem::Resolver::IndexSpecification.new( self, n.name, n.version, uri, n.platform ) end res end def pretty_print(q) # :nodoc: q.group 2, "[IndexSet", "]" do q.breakable q.text "sources:" q.breakable q.pp @f.sources q.breakable q.text "specs:" q.breakable names = @all.values.flat_map do |tuples| tuples.map do |_, tuple| tuple.full_name end end q.seplist names do |name| q.text name end end end end PK!A=resolver/api_set/gem_parser.rbnu[# frozen_string_literal: true class Gem::Resolver::APISet::GemParser def parse(line) version_and_platform, rest = line.split(" ", 2) version, platform = version_and_platform.split("-", 2) dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : [] requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : [] [version, platform, dependencies, requirements] end private def parse_dependency(string) dependency = string.split(":") dependency[-1] = dependency[-1].split("&") if dependency.size > 1 dependency[0] = -dependency[0] dependency end end PK!Ϙ resolver/activation_request.rbnu[# frozen_string_literal: true ## # Specifies a Specification object that should be activated. Also contains a # dependency that was used to introduce this activation. class Gem::Resolver::ActivationRequest ## # The parent request for this activation request. attr_reader :request ## # The specification to be activated. attr_reader :spec ## # Creates a new ActivationRequest that will activate +spec+. The parent # +request+ is used to provide diagnostics in case of conflicts. def initialize(spec, request) @spec = spec @request = request end def ==(other) # :nodoc: case other when Gem::Specification @spec == other when Gem::Resolver::ActivationRequest @spec == other.spec else false end end def eql?(other) self == other end def hash @spec.hash end ## # Is this activation request for a development dependency? def development? @request.development? end ## # Downloads a gem at +path+ and returns the file path. def download(path) Gem.ensure_gem_subdirectories path if @spec.respond_to? :sources exception = nil path = @spec.sources.find do |source| source.download full_spec, path rescue exception end return path if path raise exception if exception elsif @spec.respond_to? :source source = @spec.source source.download full_spec, path else source = Gem.sources.first source.download full_spec, path end end ## # The full name of the specification to be activated. def full_name name_tuple.full_name end alias_method :to_s, :full_name ## # The Gem::Specification for this activation request. def full_spec Gem::Specification === @spec ? @spec : @spec.spec end def inspect # :nodoc: format("#<%s for %p from %s>", self.class, @spec, @request) end ## # True if the requested gem has already been installed. def installed? case @spec when Gem::Resolver::VendorSpecification then true else this_spec = full_spec Gem::Specification.any? do |s| s == this_spec && s.base_dir == this_spec.base_dir end end end ## # The name of this activation request's specification def name @spec.name end ## # Return the ActivationRequest that contained the dependency # that we were activated for. def parent @request.requester end def pretty_print(q) # :nodoc: q.group 2, "[Activation request", "]" do q.breakable q.pp @spec q.breakable q.text " for " q.pp @request end end ## # The version of this activation request's specification def version @spec.version end ## # The platform of this activation request's specification def platform @spec.platform end private def name_tuple @name_tuple ||= Gem::NameTuple.new(name, version, platform) end end PK!NBBresolver/lock_specification.rbnu[# frozen_string_literal: true ## # The LockSpecification comes from a lockfile (Gem::RequestSet::Lockfile). # # A LockSpecification's dependency information is pre-filled from the # lockfile. class Gem::Resolver::LockSpecification < Gem::Resolver::Specification attr_reader :sources def initialize(set, name, version, sources, platform) super() @name = name @platform = platform @set = set @source = sources.first @sources = sources @version = version @dependencies = [] @spec = nil end ## # This is a null install as a locked specification is considered installed. # +options+ are ignored. def install(options = {}) destination = options[:install_dir] || Gem.dir if File.exist? File.join(destination, "specifications", spec.spec_name) yield nil return end super end ## # Adds +dependency+ from the lockfile to this specification def add_dependency(dependency) # :nodoc: @dependencies << dependency end def pretty_print(q) # :nodoc: q.group 2, "[LockSpecification", "]" do q.breakable q.text "name: #{@name}" q.breakable q.text "version: #{@version}" unless @platform == Gem::Platform::RUBY q.breakable q.text "platform: #{@platform}" end unless @dependencies.empty? q.breakable q.text "dependencies:" q.breakable q.pp @dependencies end end end ## # A specification constructed from the lockfile is returned def spec @spec ||= Gem::Specification.find do |spec| spec.name == @name && spec.version == @version end @spec ||= Gem::Specification.new do |s| s.name = @name s.version = @version s.platform = @platform s.dependencies.concat @dependencies end end end PK!jaMG^ ^ resolver/git_set.rbnu[# frozen_string_literal: true ## # A GitSet represents gems that are sourced from git repositories. # # This is used for gem dependency file support. # # Example: # # set = Gem::Resolver::GitSet.new # set.add_git_gem 'rake', 'git://example/rake.git', tag: 'rake-10.1.0' class Gem::Resolver::GitSet < Gem::Resolver::Set ## # The root directory for git gems in this set. This is usually Gem.dir, the # installation directory for regular gems. attr_accessor :root_dir ## # Contains repositories needing submodules attr_reader :need_submodules # :nodoc: ## # A Hash containing git gem names for keys and a Hash of repository and # git commit reference as values. attr_reader :repositories # :nodoc: ## # A hash of gem names to Gem::Resolver::GitSpecifications attr_reader :specs # :nodoc: def initialize # :nodoc: super() @need_submodules = {} @repositories = {} @root_dir = Gem.dir @specs = {} end def add_git_gem(name, repository, reference, submodules) # :nodoc: @repositories[name] = [repository, reference] @need_submodules[repository] = submodules end ## # Adds and returns a GitSpecification with the given +name+ and +version+ # which came from a +repository+ at the given +reference+. If +submodules+ # is true they are checked out along with the repository. # # This fills in the prefetch information as enough information about the gem # is present in the arguments. def add_git_spec(name, version, repository, reference, submodules) # :nodoc: add_git_gem name, repository, reference, submodules source = Gem::Source::Git.new name, repository, reference source.root_dir = @root_dir spec = Gem::Specification.new do |s| s.name = name s.version = version end git_spec = Gem::Resolver::GitSpecification.new self, spec, source @specs[spec.name] = git_spec git_spec end ## # Finds all git gems matching +req+ def find_all(req) prefetch nil specs.values.select do |spec| req.match? spec end end ## # Prefetches specifications from the git repositories in this set. def prefetch(reqs) return unless @specs.empty? @repositories.each do |name, (repository, reference)| source = Gem::Source::Git.new name, repository, reference source.root_dir = @root_dir source.remote = @remote source.specs.each do |spec| git_spec = Gem::Resolver::GitSpecification.new self, spec, source @specs[spec.name] = git_spec end end end def pretty_print(q) # :nodoc: q.group 2, "[GitSet", "]" do next if @repositories.empty? q.breakable repos = @repositories.map do |name, (repository, reference)| "#{name}: #{repository}@#{reference}" end q.seplist repos do |repo| q.text repo end end end end PK!resolver/composed_set.rbnu[# frozen_string_literal: true ## # A ComposedSet allows multiple sets to be queried like a single set. # # To create a composed set with any number of sets use: # # Gem::Resolver.compose_sets set1, set2 # # This method will eliminate nesting of composed sets. class Gem::Resolver::ComposedSet < Gem::Resolver::Set attr_reader :sets # :nodoc: ## # Creates a new ComposedSet containing +sets+. Use # Gem::Resolver::compose_sets instead. def initialize(*sets) super() @sets = sets end ## # When +allow_prerelease+ is set to +true+ prereleases gems are allowed to # match dependencies. def prerelease=(allow_prerelease) super sets.each do |set| set.prerelease = allow_prerelease end end ## # Sets the remote network access for all composed sets. def remote=(remote) super @sets.each {|set| set.remote = remote } end def errors @errors + @sets.flat_map(&:errors) end ## # Finds all specs matching +req+ in all sets. def find_all(req) @sets.flat_map do |s| s.find_all req end end ## # Prefetches +reqs+ in all sets. def prefetch(reqs) @sets.each {|s| s.prefetch(reqs) } end end PK!resolver/source_set.rbnu[# frozen_string_literal: true ## # The SourceSet chooses the best available method to query a remote index. # # Kind off like BestSet but filters the sources for gems class Gem::Resolver::SourceSet < Gem::Resolver::Set ## # Creates a SourceSet for the given +sources+ or Gem::sources if none are # specified. +sources+ must be a Gem::SourceList. def initialize super() @links = {} @sets = {} end def find_all(req) # :nodoc: if set = get_set(req.dependency.name) set.find_all req else [] end end # potentially no-op def prefetch(reqs) # :nodoc: reqs.each do |req| if set = get_set(req.dependency.name) set.prefetch reqs end end end def add_source_gem(name, source) @links[name] = source end private def get_set(name) link = @links[name] @sets[link] ||= Gem::Source.new(link).dependency_resolver_set(@prerelease) if link end end PK!͞AA resolver/vendor_specification.rbnu[# frozen_string_literal: true ## # A VendorSpecification represents a gem that has been unpacked into a project # and is being loaded through a gem dependencies file through the +path:+ # option. class Gem::Resolver::VendorSpecification < Gem::Resolver::SpecSpecification def ==(other) # :nodoc: self.class === other && @set == other.set && @spec == other.spec && @source == other.source end ## # This is a null install as this gem was unpacked into a directory. # +options+ are ignored. def install(options = {}) yield nil end end PK!qXXresolver/requirement_list.rbnu[# frozen_string_literal: true ## # The RequirementList is used to hold the requirements being considered # while resolving a set of gems. # # The RequirementList acts like a queue where the oldest items are removed # first. class Gem::Resolver::RequirementList include Enumerable ## # Creates a new RequirementList. def initialize @exact = [] @list = [] end def initialize_copy(other) # :nodoc: @exact = @exact.dup @list = @list.dup end ## # Adds Resolver::DependencyRequest +req+ to this requirements list. def add(req) if req.requirement.exact? @exact.push req else @list.push req end req end ## # Enumerates requirements in the list def each # :nodoc: return enum_for __method__ unless block_given? @exact.each do |requirement| yield requirement end @list.each do |requirement| yield requirement end end ## # How many elements are in the list def size @exact.size + @list.size end ## # Is the list empty? def empty? @exact.empty? && @list.empty? end ## # Remove the oldest DependencyRequest from the list. def remove return @exact.shift unless @exact.empty? @list.shift end ## # Returns the oldest five entries from the list. def next5 x = @exact[0,5] x + @list[0,5 - x.size] end end PK!֚resolver/vendor_set.rbnu[# frozen_string_literal: true ## # A VendorSet represents gems that have been unpacked into a specific # directory that contains a gemspec. # # This is used for gem dependency file support. # # Example: # # set = Gem::Resolver::VendorSet.new # # set.add_vendor_gem 'rake', 'vendor/rake' # # The directory vendor/rake must contain an unpacked rake gem along with a # rake.gemspec (watching the given name). class Gem::Resolver::VendorSet < Gem::Resolver::Set ## # The specifications for this set. attr_reader :specs # :nodoc: def initialize # :nodoc: super() @directories = {} @specs = {} end ## # Adds a specification to the set with the given +name+ which has been # unpacked into the given +directory+. def add_vendor_gem(name, directory) # :nodoc: gemspec = File.join directory, "#{name}.gemspec" spec = Gem::Specification.load gemspec raise Gem::GemNotFoundException, "unable to find #{gemspec} for gem #{name}" unless spec spec.full_gem_path = File.expand_path directory @specs[spec.name] = spec @directories[spec] = directory spec end ## # Returns an Array of VendorSpecification objects matching the # DependencyRequest +req+. def find_all(req) @specs.values.select do |spec| req.match? spec end.map do |spec| source = Gem::Source::Vendor.new @directories[spec] Gem::Resolver::VendorSpecification.new self, spec, source end end ## # Loads a spec with the given +name+. +version+, +platform+ and +source+ are # ignored. def load_spec(name, version, platform, source) # :nodoc: @specs.fetch name end def pretty_print(q) # :nodoc: q.group 2, "[VendorSet", "]" do next if @directories.empty? q.breakable dirs = @directories.map do |spec, directory| "#{spec.full_name}: #{directory}" end q.seplist dirs do |dir| q.text dir end end end end PK!Vå resolver/specification.rbnu[# frozen_string_literal: true ## # A Resolver::Specification contains a subset of the information # contained in a Gem::Specification. Only the information necessary for # dependency resolution in the resolver is included. class Gem::Resolver::Specification ## # The dependencies of the gem for this specification attr_reader :dependencies ## # The name of the gem for this specification attr_reader :name ## # The platform this gem works on. attr_reader :platform ## # The set this specification came from. attr_reader :set ## # The source for this specification attr_reader :source ## # The Gem::Specification for this Resolver::Specification. # # Implementers, note that #install updates @spec, so be sure to cache the # Gem::Specification in @spec when overriding. attr_reader :spec ## # The version of the gem for this specification. attr_reader :version ## # The required_ruby_version constraint for this specification. attr_reader :required_ruby_version ## # The required_ruby_version constraint for this specification. attr_reader :required_rubygems_version ## # Sets default instance variables for the specification. def initialize @dependencies = nil @name = nil @platform = nil @set = nil @source = nil @version = nil @required_ruby_version = Gem::Requirement.default @required_rubygems_version = Gem::Requirement.default end ## # Fetches development dependencies if the source does not provide them by # default (see APISpecification). def fetch_development_dependencies # :nodoc: end ## # The name and version of the specification. # # Unlike Gem::Specification#full_name, the platform is not included. def full_name "#{@name}-#{@version}" end ## # Installs this specification using the Gem::Installer +options+. The # install method yields a Gem::Installer instance, which indicates the # gem will be installed, or +nil+, which indicates the gem is already # installed. # # After installation #spec is updated to point to the just-installed # specification. def install(options = {}) require_relative "../installer" gem = download options installer = Gem::Installer.at gem, options yield installer if block_given? @spec = installer.install end def download(options) dir = options[:install_dir] || Gem.dir Gem.ensure_gem_subdirectories dir source.download spec, dir end ## # Returns true if this specification is installable on this platform. def installable_platform? Gem::Platform.match_spec? spec end def local? # :nodoc: false end end PK!ehresolver/spec_specification.rbnu[# frozen_string_literal: true ## # The Resolver::SpecSpecification contains common functionality for # Resolver specifications that are backed by a Gem::Specification. class Gem::Resolver::SpecSpecification < Gem::Resolver::Specification ## # A SpecSpecification is created for a +set+ for a Gem::Specification in # +spec+. The +source+ is either where the +spec+ came from, or should be # loaded from. def initialize(set, spec, source = nil) @set = set @source = source @spec = spec end ## # The dependencies of the gem for this specification def dependencies spec.dependencies end ## # The required_ruby_version constraint for this specification def required_ruby_version spec.required_ruby_version end ## # The required_rubygems_version constraint for this specification def required_rubygems_version spec.required_rubygems_version end ## # The name and version of the specification. # # Unlike Gem::Specification#full_name, the platform is not included. def full_name "#{spec.name}-#{spec.version}" end ## # The name of the gem for this specification def name spec.name end ## # The platform this gem works on. def platform spec.platform end ## # The version of the gem for this specification. def version spec.version end ## # The hash value for this specification. def hash spec.hash end end PK!Qh9#resolver/installed_specification.rbnu[# frozen_string_literal: true ## # An InstalledSpecification represents a gem that is already installed # locally. class Gem::Resolver::InstalledSpecification < Gem::Resolver::SpecSpecification def ==(other) # :nodoc: self.class === other && @set == other.set && @spec == other.spec end ## # This is a null install as this specification is already installed. # +options+ are ignored. def install(options = {}) yield nil end ## # Returns +true+ if this gem is installable for the current platform. def installable_platform? # BACKCOMPAT If the file is coming out of a specified file, then we # ignore the platform. This code can be removed in RG 3.0. return true if @source.is_a? Gem::Source::SpecificFile super end def pretty_print(q) # :nodoc: q.group 2, "[InstalledSpecification", "]" do q.breakable q.text "name: #{name}" q.breakable q.text "version: #{version}" q.breakable q.text "platform: #{platform}" q.breakable q.text "dependencies:" q.breakable q.pp spec.dependencies end end ## # The source for this specification def source @source ||= Gem::Source::Installed.new end end PK!]Z^^resolver/installer_set.rbnu[# frozen_string_literal: true ## # A set of gems for installation sourced from remote sources and local .gem # files class Gem::Resolver::InstallerSet < Gem::Resolver::Set ## # List of Gem::Specification objects that must always be installed. attr_reader :always_install # :nodoc: ## # Only install gems in the always_install list attr_accessor :ignore_dependencies # :nodoc: ## # Do not look in the installed set when finding specifications. This is # used by the --install-dir option to `gem install` attr_accessor :ignore_installed # :nodoc: ## # The remote_set looks up remote gems for installation. attr_reader :remote_set # :nodoc: ## # Ignore ruby & rubygems specification constraints. # attr_accessor :force # :nodoc: ## # Creates a new InstallerSet that will look for gems in +domain+. def initialize(domain) super() @domain = domain @f = Gem::SpecFetcher.fetcher @always_install = [] @ignore_dependencies = false @ignore_installed = false @local = {} @local_source = Gem::Source::Local.new @remote_set = Gem::Resolver::BestSet.new @force = false @specs = {} end ## # Looks up the latest specification for +dependency+ and adds it to the # always_install list. def add_always_install(dependency) request = Gem::Resolver::DependencyRequest.new dependency, nil found = find_all request found.delete_if do |s| s.version.prerelease? && !s.local? end unless dependency.prerelease? found = found.select do |s| Gem::Source::SpecificFile === s.source || Gem::Platform.match_spec?(s) end found = found.sort_by do |s| [s.version, Gem::Platform.sort_priority(s.platform)] end newest = found.last unless newest exc = Gem::UnsatisfiableDependencyError.new request exc.errors = errors raise exc end unless @force found_matching_metadata = found.reverse.find do |spec| metadata_satisfied?(spec) end if found_matching_metadata.nil? ensure_required_ruby_version_met(newest.spec) ensure_required_rubygems_version_met(newest.spec) else newest = found_matching_metadata end end @always_install << newest.spec end ## # Adds a local gem requested using +dep_name+ with the given +spec+ that can # be loaded and installed using the +source+. def add_local(dep_name, spec, source) @local[dep_name] = [spec, source] end ## # Should local gems should be considered? def consider_local? # :nodoc: @domain == :both || @domain == :local end ## # Should remote gems should be considered? def consider_remote? # :nodoc: @domain == :both || @domain == :remote end ## # Errors encountered while resolving gems def errors @errors + @remote_set.errors end ## # Returns an array of IndexSpecification objects matching DependencyRequest # +req+. def find_all(req) res = [] dep = req.dependency return res if @ignore_dependencies && @always_install.none? {|spec| dep.match? spec } name = dep.name dep.matching_specs.each do |gemspec| next if @always_install.any? {|spec| spec.name == gemspec.name } res << Gem::Resolver::InstalledSpecification.new(self, gemspec) end unless @ignore_installed matching_local = [] if consider_local? matching_local = @local.values.select do |spec, _| req.match? spec end.map do |spec, source| Gem::Resolver::LocalSpecification.new self, spec, source end res.concat matching_local begin if local_spec = @local_source.find_gem(name, dep.requirement) res << Gem::Resolver::IndexSpecification.new( self, local_spec.name, local_spec.version, @local_source, local_spec.platform ) end rescue Gem::Package::FormatError # ignore end end res.concat @remote_set.find_all req if consider_remote? && matching_local.empty? res end def prefetch(reqs) @remote_set.prefetch(reqs) if consider_remote? end def prerelease=(allow_prerelease) super @remote_set.prerelease = allow_prerelease end def inspect # :nodoc: always_install = @always_install.map(&:full_name) format("#<%s domain: %s specs: %p always install: %p>", self.class, @domain, @specs.keys, always_install) end ## # Called from IndexSpecification to get a true Specification # object. def load_spec(name, ver, platform, source) # :nodoc: key = "#{name}-#{ver}-#{platform}" @specs.fetch key do tuple = Gem::NameTuple.new name, ver, platform @specs[key] = source.fetch_spec tuple end end ## # Has a local gem for +dep_name+ been added to this set? def local?(dep_name) # :nodoc: spec, _ = @local[dep_name] spec end def pretty_print(q) # :nodoc: q.group 2, "[InstallerSet", "]" do q.breakable q.text "domain: #{@domain}" q.breakable q.text "specs: " q.pp @specs.keys q.breakable q.text "always install: " q.pp @always_install end end def remote=(remote) # :nodoc: case @domain when :local then @domain = :both if remote when :remote then @domain = nil unless remote when :both then @domain = :local unless remote end end private def metadata_satisfied?(spec) spec.required_ruby_version.satisfied_by?(Gem.ruby_version) && spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version) end def ensure_required_ruby_version_met(spec) # :nodoc: if rrv = spec.required_ruby_version ruby_version = Gem.ruby_version unless rrv.satisfied_by? ruby_version raise Gem::RuntimeRequirementNotMetError, "#{spec.full_name} requires Ruby version #{rrv}. The current ruby version is #{ruby_version}." end end end def ensure_required_rubygems_version_met(spec) # :nodoc: if rrgv = spec.required_rubygems_version unless rrgv.satisfied_by? Gem.rubygems_version rg_version = Gem::VERSION raise Gem::RuntimeRequirementNotMetError, "#{spec.full_name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " \ "Try 'gem update --system' to update RubyGems itself." end end end end PK!" end ## # Return the 2 dependency objects that conflicted def conflicting_dependencies [@failed_dep.dependency, @activated.request.dependency] end ## # Explanation of the conflict used by exceptions to print useful messages def explanation activated = @activated.spec.full_name dependency = @failed_dep.dependency requirement = dependency.requirement alternates = dependency.matching_specs.map(&:full_name) unless alternates.empty? matching = <<-MATCHING.chomp Gems matching %s: %s MATCHING matching = format(matching, dependency, alternates.join(", ")) end explanation = <<-EXPLANATION Activated %s which does not match conflicting dependency (%s) Conflicting dependency chains: %s versus: %s %s EXPLANATION format(explanation, activated, requirement, request_path(@activated).reverse.join(", depends on\n "), request_path(@failed_dep).reverse.join(", depends on\n "), matching) end ## # Returns true if the conflicting dependency's name matches +spec+. def for_spec?(spec) @dependency.name == spec.name end def pretty_print(q) # :nodoc: q.group 2, "[Dependency conflict: ", "]" do q.breakable q.text "activated " q.pp @activated q.breakable q.text " dependency " q.pp @dependency q.breakable if @dependency == @failed_dep q.text " failed" else q.text " failed dependency " q.pp @failed_dep end end end ## # Path of activations from the +current+ list. def request_path(current) path = [] while current do case current when Gem::Resolver::ActivationRequest then path << "#{current.request.dependency}, #{current.spec.version} activated" current = current.parent when Gem::Resolver::DependencyRequest then path << current.dependency.to_s current = current.requester else raise Gem::Exception, "[BUG] unknown request class #{current.class}" end end path = ["user request (gem command or Gemfile)"] if path.empty? path end ## # Return the Specification that listed the dependency def requester @failed_dep.requester end end PK!F0Tresolver/current_set.rbnu[# frozen_string_literal: true ## # A set which represents the installed gems. Respects # all the normal settings that control where to look # for installed gems. class Gem::Resolver::CurrentSet < Gem::Resolver::Set def find_all(req) req.dependency.matching_specs end end PK!p)defaults/operating_system.rbnu[module Gem class << self ## # Returns full path of previous but one directory of dir in path # E.g. for '/usr/share/ruby', 'ruby', it returns '/usr' def previous_but_one_dir_to(path, dir) return unless path split_path = path.split(File::SEPARATOR) File.join(split_path.take_while { |one_dir| one_dir !~ /^#{dir}$/ }[0..-2]) end private :previous_but_one_dir_to ## # Detects --install-dir option specified on command line. def opt_install_dir? @opt_install_dir ||= ARGV.include?('--install-dir') || ARGV.include?('-i') end private :opt_install_dir? ## # Detects --build-root option specified on command line. def opt_build_root? @opt_build_root ||= ARGV.include?('--build-root') end private :opt_build_root? ## # Tries to detect, if arguments and environment variables suggest that # 'gem install' is executed from rpmbuild. def rpmbuild? @rpmbuild ||= ENV['RPM_PACKAGE_NAME'] && (opt_install_dir? || opt_build_root?) end private :rpmbuild? ## # Default gems locations allowed on FHS system (/usr, /usr/share). # The locations are derived from directories specified during build # configuration. def default_locations @default_locations ||= { :system => previous_but_one_dir_to(RbConfig::CONFIG['vendordir'], RbConfig::CONFIG['RUBY_INSTALL_NAME']), :local => previous_but_one_dir_to(RbConfig::CONFIG['sitedir'], RbConfig::CONFIG['RUBY_INSTALL_NAME']) } end ## # For each location provides set of directories for binaries (:bin_dir) # platform independent (:gem_dir) and dependent (:ext_dir) files. def default_dirs @libdir ||= case RUBY_PLATFORM when 'java' RbConfig::CONFIG['datadir'] else RbConfig::CONFIG['libdir'] end @default_dirs ||= default_locations.inject(Hash.new) do |hash, location| destination, path = location hash[destination] = if path { :bin_dir => File.join(path, RbConfig::CONFIG['bindir'].split(File::SEPARATOR).last), :gem_dir => File.join(path, RbConfig::CONFIG['datadir'].split(File::SEPARATOR).last, 'gems'), :ext_dir => File.join(path, @libdir.split(File::SEPARATOR).last, 'gems') } else { :bin_dir => '', :gem_dir => '', :ext_dir => '' } end hash end end ## # Remove methods we are going to override. This avoids "method redefined;" # warnings otherwise issued by Ruby. remove_method :operating_system_defaults if method_defined? :operating_system_defaults remove_method :default_dir if method_defined? :default_dir remove_method :default_path if method_defined? :default_path remove_method :default_ext_dir_for if method_defined? :default_ext_dir_for ## # Regular user installs into user directory, root manages /usr/local. def operating_system_defaults unless opt_build_root? options = if Process.uid == 0 "--install-dir=#{Gem.default_dirs[:local][:gem_dir]} --bindir #{Gem.default_dirs[:local][:bin_dir]}" end {"gem" => options} else {} end end ## # RubyGems default overrides. def default_dir Gem.default_dirs[:system][:gem_dir] end def default_path path = default_dirs.collect {|location, paths| paths[:gem_dir]} path.unshift Gem.user_dir if File.exist? Gem.user_home path end def default_ext_dir_for base_dir dir = if rpmbuild? build_dir = base_dir.chomp Gem.default_dirs[:system][:gem_dir] if build_dir != base_dir File.join build_dir, Gem.default_dirs[:system][:ext_dir] end else dirs = Gem.default_dirs.detect {|location, paths| paths[:gem_dir] == base_dir} dirs && dirs.last[:ext_dir] end dir && File.join(dir, RbConfig::CONFIG['RUBY_INSTALL_NAME']) end # This method should be available since RubyGems 2.2 until RubyGems 3.0. # https://github.com/rubygems/rubygems/issues/749 if method_defined? :install_extension_in_lib remove_method :install_extension_in_lib def install_extension_in_lib false end end end end PK![Wext/build_error.rbnu[# frozen_string_literal: true ## # Raised when there is an error while building extensions. require_relative "../exceptions" class Gem::Ext::BuildError < Gem::InstallError end PK!^?ext/cmake_builder.rbnu[# frozen_string_literal: true # This builder creates extensions defined using CMake. Its is invoked if a Gem's spec file # sets the `extension` property to a string that contains `CMakeLists.txt`. # # In general, CMake projects are built in two steps: # # * configure # * build # # The builder follow this convention. First it runs a configuration step and then it runs a build step. # # CMake projects can be quite configurable - it is likely you will want to specify options when # installing a gem. To pass options to CMake specify them after `--` in the gem install command. For example: # # gem install -- --preset # # Note that options are ONLY sent to the configure step - it is not currently possible to specify # options for the build step. If this becomes and issue then the CMake builder can be updated to # support build options. # # Useful options to know are: # # -G to specify a generator (-G Ninja is recommended) # -D to set a CMake variable (for example -DCMAKE_BUILD_TYPE=Release) # --preset to use a preset # # If the Gem author provides presets, via CMakePresets.json file, you will likely want to use one of them. # If not, you may wish to specify a generator. Ninja is recommended because it can build projects in parallel # and thus much faster than building them serially like Make does. class Gem::Ext::CmakeBuilder < Gem::Ext::Builder attr_accessor :runner, :profile def initialize @runner = self.class.method(:run) @profile = :release end def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd, target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring" end # Figure the build dir build_dir = File.join(cmake_dir, "build") # Check if the gem defined presets check_presets(cmake_dir, args, results) # Configure configure(cmake_dir, build_dir, dest_path, args, results) # Compile compile(cmake_dir, build_dir, args, results) results end def configure(cmake_dir, build_dir, install_dir, args, results) cmd = ["cmake", cmake_dir, "-B", build_dir, "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=#{install_dir}", # Windows "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=#{install_dir}", # Not Windows *Gem::Command.build_args, *args] runner.call(cmd, results, "cmake_configure", cmake_dir) end def compile(cmake_dir, build_dir, args, results) cmd = ["cmake", "--build", build_dir.to_s, "--config", @profile.to_s] runner.call(cmd, results, "cmake_compile", cmake_dir) end private def check_presets(cmake_dir, args, results) # Return if the user specified a preset return unless args.grep(/--preset/i).empty? cmd = ["cmake", "--list-presets"] presets = Array.new begin runner.call(cmd, presets, "cmake_presets", cmake_dir) # Remove the first two lines of the array which is the current_directory and the command # that was run presets = presets[2..].join results << <<~EOS The gem author provided a list of presets that can be used to build the gem. To use a preset specify it on the command line: gem install -- --preset #{presets} EOS rescue Gem::InstallError # Do nothing, CMakePresets.json was not included in the Gem end end end PK!5c((ext/cargo_builder.rbnu[# frozen_string_literal: true # This class is used by rubygems to build Rust extensions. It is a thin-wrapper # over the `cargo rustc` command which takes care of building Rust code in a way # that Ruby can use. class Gem::Ext::CargoBuilder < Gem::Ext::Builder attr_accessor :spec, :runner, :profile def initialize require_relative "../command" require_relative "cargo_builder/link_flag_converter" @runner = self.class.method(:run) @profile = :release end def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd, target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "tempfile" require "fileutils" if target_rbconfig.path warn "--target-rbconfig is not yet supported for Rust extensions. Ignoring" end # Where's the Cargo.toml of the crate we're building cargo_toml = File.join(cargo_dir, "Cargo.toml") # What's the crate's name crate_name = cargo_crate_name(cargo_dir, cargo_toml, results) begin # Create a tmp dir to do the build in tmp_dest = Dir.mktmpdir(".gem.", cargo_dir) # Run the build cmd = cargo_command(cargo_toml, tmp_dest, args, crate_name) runner.call(cmd, results, "cargo", cargo_dir, build_env) # Where do we expect Cargo to write the compiled library dylib_path = cargo_dylib_path(tmp_dest, crate_name) # Helpful error if we didn't find the compiled library raise DylibNotFoundError, tmp_dest unless File.exist?(dylib_path) # Cargo and Ruby differ on how the library should be named, rename from # what Cargo outputs to what Ruby expects dlext_name = "#{crate_name}.#{makefile_config("DLEXT")}" dlext_path = File.join(File.dirname(dylib_path), dlext_name) FileUtils.cp(dylib_path, dlext_path) nesting = extension_nesting(extension) if Gem.install_extension_in_lib && lib_dir nested_lib_dir = File.join(lib_dir, nesting) FileUtils.mkdir_p nested_lib_dir FileUtils.cp_r dlext_path, nested_lib_dir, remove_destination: true end # move to final destination nested_dest_path = File.join(dest_path, nesting) FileUtils.mkdir_p nested_dest_path FileUtils.cp_r dlext_path, nested_dest_path, remove_destination: true ensure # clean up intermediary build artifacts FileUtils.rm_rf tmp_dest if tmp_dest end results end def build_env build_env = rb_config_env build_env["RUBY_STATIC"] = "true" if ruby_static? && ENV.key?("RUBY_STATIC") cfg = "--cfg=rb_sys_gem --cfg=rubygems --cfg=rubygems_#{Gem::VERSION.tr(".", "_")}" build_env["RUSTFLAGS"] = [ENV["RUSTFLAGS"], cfg].compact.join(" ") build_env end def cargo_command(cargo_toml, dest_path, args = [], crate_name = nil) cmd = [] cmd += [cargo, "rustc"] cmd += ["--crate-type", "cdylib"] cmd += ["--target", ENV["CARGO_BUILD_TARGET"]] if ENV["CARGO_BUILD_TARGET"] cmd += ["--target-dir", dest_path] cmd += ["--manifest-path", cargo_toml] cmd += ["--lib"] cmd += ["--profile", profile.to_s] cmd += ["--locked"] cmd += Gem::Command.build_args cmd += args cmd += ["--"] cmd += [*cargo_rustc_args(dest_path, crate_name)] cmd end private def cargo ENV.fetch("CARGO", "cargo") end # returns the directory nesting of the extension, ignoring the first part, so # "ext/foo/bar/Cargo.toml" becomes "foo/bar" def extension_nesting(extension) parts = extension.to_s.split(Regexp.union([File::SEPARATOR, File::ALT_SEPARATOR].compact)) parts = parts.each_with_object([]) do |segment, final| next if segment == "." if segment == ".." raise Gem::InstallError, "extension outside of gem root" if final.empty? next final.pop end final << segment end File.join(parts[1...-1]) end def rb_config_env result = {} RbConfig::CONFIG.each {|k, v| result["RBCONFIG_#{k}"] = v } result end def cargo_rustc_args(dest_dir, crate_name) [ *linker_args, *mkmf_libpath, *rustc_dynamic_linker_flags(dest_dir, crate_name), *rustc_lib_flags(dest_dir), *platform_specific_rustc_args(dest_dir), ] end def platform_specific_rustc_args(dest_dir, flags = []) if mingw_target? # On mingw platforms, mkmf adds libruby to the linker flags flags += libruby_args(dest_dir) # Make sure ALSR is used on mingw # see https://github.com/rust-lang/rust/pull/75406/files flags += ["-C", "link-arg=-Wl,--dynamicbase"] flags += ["-C", "link-arg=-Wl,--disable-auto-image-base"] # If the gem is installed on a host with build tools installed, but is # run on one that isn't the missing libraries will cause the extension # to fail on start. flags += ["-C", "link-arg=-static-libgcc"] elsif darwin_target? # Ventura does not always have this flag enabled flags += ["-C", "link-arg=-Wl,-undefined,dynamic_lookup"] end flags end # We want to use the same linker that Ruby uses, so that the linker flags from # mkmf work properly. def linker_args cc_flag = self.class.shellsplit(makefile_config("CC")) # Avoid to ccache like tool from Rust build # see https://github.com/ruby/rubygems/pull/8521#issuecomment-2689854359 # ex. CC="ccache gcc" or CC="sccache clang --any --args" cc_flag.shift if cc_flag.size >= 2 && !cc_flag[1].start_with?("-") linker = cc_flag.shift link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] } return mswin_link_args if linker == "cl" ["-C", "linker=#{linker}", *link_args] end def mswin_link_args args = [] args += ["-l", makefile_config("LIBRUBYARG_SHARED").chomp(".lib")] args += split_flags("LIBS").flat_map {|lib| ["-l", lib.chomp(".lib")] } args += split_flags("LOCAL_LIBS").flat_map {|lib| ["-l", lib.chomp(".lib")] } args end def libruby_args(dest_dir) libs = makefile_config(ruby_static? ? "LIBRUBYARG_STATIC" : "LIBRUBYARG_SHARED") raw_libs = self.class.shellsplit(libs) raw_libs.flat_map {|l| ldflag_to_link_modifier(l) } end def ruby_static? return true if %w[1 true].include?(ENV["RUBY_STATIC"]) makefile_config("ENABLE_SHARED") == "no" end def cargo_dylib_path(dest_path, crate_name) so_ext = RbConfig::CONFIG["SOEXT"] prefix = so_ext == "dll" ? "" : "lib" path_parts = [dest_path] path_parts << ENV["CARGO_BUILD_TARGET"] if ENV["CARGO_BUILD_TARGET"] path_parts += ["release", "#{prefix}#{crate_name}.#{so_ext}"] File.join(*path_parts) end def cargo_crate_name(cargo_dir, manifest_path, results) require "open3" Gem.load_yaml output, status = begin Open3.capture2e(cargo, "metadata", "--no-deps", "--format-version", "1", chdir: cargo_dir) rescue StandardError => error raise Gem::InstallError, "cargo metadata failed #{error.message}" end unless status.success? if Gem.configuration.really_verbose puts output else results << output end exit_reason = if status.exited? ", exit code #{status.exitstatus}" elsif status.signaled? ", uncaught signal #{status.termsig}" end raise Gem::InstallError, "cargo metadata failed#{exit_reason}" end # cargo metadata output is specified as json require "json" metadata = JSON.parse(output) package = metadata["packages"].find {|pkg| normalize_path(pkg["manifest_path"]) == manifest_path } unless package found = metadata["packages"].map {|md| "#{md["name"]} at #{md["manifest_path"]}" } raise Gem::InstallError, <<-EOF failed to determine cargo package name looking for: #{manifest_path} found: #{found.join("\n")} EOF end package["name"].tr("-", "_") end def normalize_path(path) return path unless File::ALT_SEPARATOR path.tr(File::ALT_SEPARATOR, File::SEPARATOR) end def rustc_dynamic_linker_flags(dest_dir, crate_name) split_flags("DLDFLAGS"). filter_map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir, crate_name) }. flat_map {|arg| ldflag_to_link_modifier(arg) } end def rustc_lib_flags(dest_dir) split_flags("LIBS").flat_map {|arg| ldflag_to_link_modifier(arg) } end def split_flags(var) self.class.shellsplit(RbConfig::CONFIG.fetch(var, "")) end def ldflag_to_link_modifier(arg) LinkFlagConverter.convert(arg) end def msvc_target? makefile_config("target_os").include?("msvc") end def darwin_target? makefile_config("target_os").include?("darwin") end def mingw_target? makefile_config("target_os").include?("mingw") end def win_target? target_platform = RbConfig::CONFIG["target_os"] !!Gem::WIN_PATTERNS.find {|r| target_platform =~ r } end # Interpolate substitution vars in the arg (i.e. $(DEFFILE)) def maybe_resolve_ldflag_variable(input_arg, dest_dir, crate_name) var_matches = input_arg.match(/\$\((\w+)\)/) return input_arg unless var_matches var_name = var_matches[1] return input_arg if var_name.nil? || var_name.chomp.empty? case var_name # On windows, it is assumed that mkmf has setup an exports file for the # extension, so we have to create one ourselves. when "DEFFILE" write_deffile(dest_dir, crate_name) else RbConfig::CONFIG[var_name] end end def write_deffile(dest_dir, crate_name) deffile_path = File.join(dest_dir, "#{crate_name}-#{RbConfig::CONFIG["arch"]}.def") export_prefix = makefile_config("EXPORT_PREFIX") || "" File.open(deffile_path, "w") do |f| f.puts "EXPORTS" f.puts "#{export_prefix.strip}Init_#{crate_name}" end deffile_path end # Corresponds to $(LIBPATH) in mkmf def mkmf_libpath ["-L", "native=#{makefile_config("libdir")}"] end def makefile_config(var_name) val = RbConfig::MAKEFILE_CONFIG[var_name] return unless val RbConfig.expand(val.dup) end # Error raised when no cdylib artifact was created class DylibNotFoundError < StandardError def initialize(dir) files = Dir.glob(File.join(dir, "**", "*")).map {|f| "- #{f}" }.join "\n" super <<~MSG Dynamic library not found for Rust extension (in #{dir}) Make sure you set "crate-type" in Cargo.toml to "cdylib" Found files: #{files} MSG end end end PK!R4##ext/rake_builder.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ class Gem::Ext::RakeBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring" end if /mkrf_conf/i.match?(File.basename(extension)) run([Gem.ruby, File.basename(extension), *args], results, class_name, extension_dir) end rake = ENV["rake"] if rake rake = shellsplit(rake) else begin rake = ruby << "-rrubygems" << Gem.bin_path("rake", "rake") rescue Gem::Exception rake = [Gem.default_exec_format % "rake"] end end rake_args = ["RUBYARCHDIR=#{dest_path}", "RUBYLIBDIR=#{dest_path}", *args] run(rake + rake_args, results, class_name, extension_dir) results end end PK!>(ext/cargo_builder/link_flag_converter.rbnu[# frozen_string_literal: true class Gem::Ext::CargoBuilder < Gem::Ext::Builder # Converts Ruby link flags into something cargo understands class LinkFlagConverter FILTERED_PATTERNS = [ /compress-debug-sections/, # Not supported by all linkers, and not required for Rust ].freeze def self.convert(arg) return [] if FILTERED_PATTERNS.any? {|p| p.match?(arg) } case arg.chomp when /^-L\s*(.+)$/ ["-L", "native=#{$1}"] when /^--library=(\w+\S+)$/, /^-l\s*(\w+\S+)$/ ["-l", $1] when /^-l\s*([^:\s])+/ # -lfoo, but not -l:libfoo.a ["-l", $1] when /^-F\s*(.*)$/ ["-l", "framework=#{$1}"] else ["-C", "link-args=#{arg}"] end end end end PK!+Ѻqqext/builder.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../user_interaction" class Gem::Ext::Builder include Gem::UserInteraction class NoMakefileError < Gem::InstallError end attr_accessor :build_args # :nodoc: def self.class_name name =~ /Ext::(.*)Builder/ $1.downcase end def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"], target_rbconfig: Gem.target_rbconfig, n_jobs: nil) unless File.exist? File.join(make_dir, "Makefile") # No makefile exists, nothing to do. raise NoMakefileError, "No Makefile found in #{make_dir}" end # try to find make program from Ruby configure arguments first target_rbconfig["configure_args"] =~ /with-make-prog\=(\w+)/ make_program_name = ENV["MAKE"] || ENV["make"] || $1 make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make" make_program = shellsplit(make_program_name) is_nmake = /\bnmake/i.match?(make_program_name) # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform. destdir = !is_nmake || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" # nmake doesn't support parallel build unless is_nmake have_make_arguments = make_program.size > 1 if !have_make_arguments && !ENV["MAKEFLAGS"] && n_jobs make_program << "-j#{n_jobs}" end end env = [destdir] if sitedir env << format("sitearchdir=%s", sitedir) env << format("sitelibdir=%s", sitedir) end targets.each do |target| # Pass DESTDIR via command line to override what's in MAKEFLAGS cmd = [ *make_program, *env, target, ].reject(&:empty?) begin run(cmd, results, "make #{target}".rstrip, make_dir) rescue Gem::InstallError raise unless target == "clean" # ignore clean failure end end end def self.ruby # Gem.ruby is quoted if it contains whitespace cmd = shellsplit(Gem.ruby) # This load_path is only needed when running rubygems test without a proper installation. # Prepending it in a normal installation will cause problem with order of $LOAD_PATH. # Therefore only add load_path if it is not present in the default $LOAD_PATH. load_path = File.expand_path("../..", __dir__) case load_path when RbConfig::CONFIG["sitelibdir"], RbConfig::CONFIG["vendorlibdir"], RbConfig::CONFIG["rubylibdir"] cmd else cmd << "-I#{load_path}" end end def self.run(command, results, command_name = nil, dir = Dir.pwd, env = {}) verbose = Gem.configuration.really_verbose begin rubygems_gemdeps = ENV["RUBYGEMS_GEMDEPS"] ENV["RUBYGEMS_GEMDEPS"] = nil if verbose puts("current directory: #{dir}") p(command) end results << "current directory: #{dir}" results << shelljoin(command) require "open3" # Set $SOURCE_DATE_EPOCH for the subprocess. build_env = { "SOURCE_DATE_EPOCH" => Gem.source_date_epoch_string }.merge(env) output, status = begin Open3.popen2e(build_env, *command, chdir: dir) do |_stdin, stdouterr, wait_thread| output = String.new while line = stdouterr.gets output << line if verbose print line end end [output, wait_thread.value] end rescue StandardError => error raise Gem::InstallError, "#{command_name || class_name} failed#{error.message}" end unless verbose results << output end ensure ENV["RUBYGEMS_GEMDEPS"] = rubygems_gemdeps end unless status.success? results << "Building has failed. See above output for more information on the failure." if verbose end yield(status, results) if block_given? unless status.success? exit_reason = if status.exited? ", exit code #{status.exitstatus}" elsif status.signaled? ", uncaught signal #{status.termsig}" end raise Gem::InstallError, "#{command_name || class_name} failed#{exit_reason}" end end def self.shellsplit(command) require "shellwords" Shellwords.split(command) end def self.shelljoin(command) require "shellwords" Shellwords.join(command) end ## # Creates a new extension builder for +spec+. If the +spec+ does not yet # have build arguments, saved, set +build_args+ which is an ARGV-style # array. def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig, build_jobs = nil) @spec = spec @build_args = build_args @gem_dir = spec.full_gem_path @target_rbconfig = target_rbconfig @build_jobs = build_jobs end ## # Chooses the extension builder class for +extension+ def builder_for(extension) # :nodoc: case extension when /extconf/ then Gem::Ext::ExtConfBuilder when /configure/ then Gem::Ext::ConfigureBuilder when /rakefile/i, /mkrf_conf/i then Gem::Ext::RakeBuilder when /CMakeLists.txt/ then Gem::Ext::CmakeBuilder.new when /Cargo.toml/ then Gem::Ext::CargoBuilder.new else build_error("No builder for extension '#{extension}'") end end ## # Logs the build +output+, then raises Gem::Ext::BuildError. def build_error(output, backtrace = nil) # :nodoc: gem_make_out = write_gem_make_out output message = <<-EOF ERROR: Failed to build gem native extension. #{output} Gem files will remain installed in #{@gem_dir} for inspection. Results logged to #{gem_make_out} EOF raise Gem::Ext::BuildError, message, backtrace end def build_extension(extension, dest_path) # :nodoc: results = [] builder = builder_for(extension) extension_dir = File.expand_path File.join(@gem_dir, File.dirname(extension)) lib_dir = File.join @spec.full_gem_path, @spec.raw_require_paths.first begin FileUtils.mkdir_p dest_path results = builder.build(extension, dest_path, results, @build_args, lib_dir, extension_dir, @target_rbconfig, n_jobs: @build_jobs) verbose { results.join("\n") } write_gem_make_out results.join "\n" rescue StandardError => e results << e.message build_error(results.join("\n"), $@) end end ## # Builds extensions. Valid types of extensions are extconf.rb files, # configure scripts and rakefiles or mkrf_conf files. def build_extensions return if @spec.extensions.empty? if @build_args.empty? say "Building native extensions. This could take a while..." else say "Building native extensions with: '#{@build_args.join " "}'" say "This could take a while..." end dest_path = @spec.extension_dir require "fileutils" FileUtils.rm_f @spec.gem_build_complete_path @spec.extensions.each do |extension| build_extension extension, dest_path end FileUtils.touch @spec.gem_build_complete_path end ## # Writes +output+ to gem_make.out in the extension install directory. def write_gem_make_out(output) # :nodoc: destination = File.join @spec.extension_dir, "gem_make.out" FileUtils.mkdir_p @spec.extension_dir File.open destination, "wb" do |io| io.puts output end destination end end PK!*))ext/configure_builder.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, configure_dir = Dir.pwd, target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring" end unless File.exist?(File.join(configure_dir, "Makefile")) cmd = ["sh", "./configure", "--prefix=#{dest_path}", *args] run cmd, results, class_name, configure_dir end make dest_path, results, configure_dir, target_rbconfig: target_rbconfig, n_jobs: n_jobs results end end PK!V& & ext/ext_conf_builder.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "fileutils" require "tempfile" tmp_dest = Dir.mktmpdir(".gem.", extension_dir) # Some versions of `mktmpdir` return absolute paths, which will break make # if the paths contain spaces. # # As such, we convert to a relative path. tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir) destdir = ENV["DESTDIR"] begin cmd = ruby << File.basename(extension) cmd << "--target-rbconfig=#{target_rbconfig.path}" if target_rbconfig.path cmd.push(*args) run(cmd, results, class_name, extension_dir) do |s, r| mkmf_log = File.join(extension_dir, "mkmf.log") if File.exist? mkmf_log unless s.success? r << "To see why this extension failed to compile, please check" \ " the mkmf.log which can be found here:\n" r << " " + File.join(dest_path, "mkmf.log") + "\n" end FileUtils.mv mkmf_log, dest_path end end ENV["DESTDIR"] = nil make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs full_tmp_dest = File.join(extension_dir, tmp_dest_relative) is_cross_compiling = target_rbconfig["platform"] != RbConfig::CONFIG["platform"] # Do not copy extension libraries by default when cross-compiling # not to conflict with the one already built for the host platform. if Gem.install_extension_in_lib && lib_dir && !is_cross_compiling FileUtils.mkdir_p lib_dir entries = Dir.entries(full_tmp_dest) - %w[. ..] entries = entries.map {|entry| File.join full_tmp_dest, entry } FileUtils.cp_r entries, lib_dir, remove_destination: true end FileUtils::Entry_.new(full_tmp_dest).traverse do |ent| destent = ent.class.new(dest_path, ent.rel) destent.exist? || FileUtils.mv(ent.path, destent.path) end make dest_path, results, extension_dir, tmp_dest_relative, ["clean"], target_rbconfig: target_rbconfig ensure ENV["DESTDIR"] = destdir end results rescue Gem::Ext::Builder::NoMakefileError => error results << error.message results << "Skipping make for #{extension} as no Makefile was found." # We are good, do not re-raise the error. ensure FileUtils.rm_rf tmp_dest if tmp_dest end def self.get_relative_path(path, base) path[0..base.length - 1] = "." if path.start_with?(base) path end end PK!b (gemcutter_utilities/webauthn_listener.rbnu[# frozen_string_literal: true require_relative "webauthn_listener/response" ## # The WebauthnListener class retrieves an OTP after a user successfully WebAuthns with the Gem host. # An instance opens a socket using the TCPServer instance given and listens for a request from the Gem host. # The request should be a GET request to the root path and contains the OTP code in the form # of a query parameter `code`. The listener will return the code which will be used as the OTP for # API requests. # # Types of responses sent by the listener after receiving a request: # - 200 OK: OTP code was successfully retrieved # - 204 No Content: If the request was an OPTIONS request # - 400 Bad Request: If the request did not contain a query parameter `code` # - 404 Not Found: The request was not to the root path # - 405 Method Not Allowed: OTP code was not retrieved because the request was not a GET/OPTIONS request # # Example usage: # # thread = Gem::WebauthnListener.listener_thread("https://rubygems.example", server) # thread.join # otp = thread[:otp] # error = thread[:error] # module Gem::GemcutterUtilities class WebauthnListener attr_reader :host def initialize(host) @host = host end def self.listener_thread(host, server) Thread.new do thread = Thread.current thread.abort_on_exception = true thread.report_on_exception = false thread[:otp] = new(host).wait_for_otp_code(server) rescue Gem::WebauthnVerificationError => e thread[:error] = e ensure server.close end end def wait_for_otp_code(server) loop do socket = server.accept request_line = socket.gets method, req_uri, _protocol = request_line.split(" ") req_uri = Gem::URI.parse(req_uri) responder = SocketResponder.new(socket) unless root_path?(req_uri) responder.send(NotFoundResponse.for(host)) raise Gem::WebauthnVerificationError, "Page at #{req_uri.path} not found." end case method.upcase when "OPTIONS" responder.send(NoContentResponse.for(host)) next # will be GET when "GET" if otp = parse_otp_from_uri(req_uri) responder.send(OkResponse.for(host)) return otp end responder.send(BadRequestResponse.for(host)) raise Gem::WebauthnVerificationError, "Did not receive OTP from #{host}." else responder.send(MethodNotAllowedResponse.for(host)) raise Gem::WebauthnVerificationError, "Invalid HTTP method #{method.upcase} received." end end end private def root_path?(uri) uri.path == "/" end def parse_otp_from_uri(uri) query = uri.query return unless query && !query.empty? query.split("&") do |param| key, value = param.split("=", 2) if value && Gem::URI.decode_www_form_component(key) == "code" return Gem::URI.decode_www_form_component(value) end end nil end class SocketResponder def initialize(socket) @socket = socket end def send(response) @socket.print response.to_s @socket.close end end end end PK!L] &gemcutter_utilities/webauthn_poller.rbnu[# frozen_string_literal: true ## # The WebauthnPoller class retrieves an OTP after a user successfully WebAuthns. An instance # polls the Gem host for the OTP code. The polling request (api/v1/webauthn_verification//status.json) # is sent to the Gem host every 5 seconds and will timeout after 5 minutes. If the status field in the json response # is "success", the code field will contain the OTP code. # # Example usage: # # thread = Gem::WebauthnPoller.poll_thread( # {}, # "RubyGems.org", # "https://rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY", # { email: "email@example.com", password: "password" } # ) # thread.join # otp = thread[:otp] # error = thread[:error] # module Gem::GemcutterUtilities class WebauthnPoller include Gem::GemcutterUtilities TIMEOUT_IN_SECONDS = 300 attr_reader :options, :host def initialize(options, host) @options = options @host = host end def self.poll_thread(options, host, webauthn_url, credentials) Thread.new do thread = Thread.current thread.abort_on_exception = true thread.report_on_exception = false thread[:otp] = new(options, host).poll_for_otp(webauthn_url, credentials) rescue Gem::WebauthnVerificationError, Gem::Timeout::Error => e thread[:error] = e end end def poll_for_otp(webauthn_url, credentials) Gem::Timeout.timeout(TIMEOUT_IN_SECONDS) do loop do response = webauthn_verification_poll_response(webauthn_url, credentials) raise Gem::WebauthnVerificationError, response.message unless response.is_a?(Gem::Net::HTTPSuccess) require "json" parsed_response = JSON.parse(response.body) case parsed_response["status"] when "pending" sleep 5 when "success" return parsed_response["code"] else raise Gem::WebauthnVerificationError, parsed_response.fetch("message", "Invalid response from server") end end end end private def webauthn_verification_poll_response(webauthn_url, credentials) webauthn_token = %r{(?<=\/)[^\/]+(?=$)}.match(webauthn_url)[0] rubygems_api_request(:get, "api/v1/webauthn_verification/#{webauthn_token}/status.json") do |request| if credentials.empty? request.add_field "Authorization", api_key elsif credentials[:identifier] && credentials[:password] request.basic_auth credentials[:identifier], credentials[:password] else raise Gem::WebauthnVerificationError, "Provided missing credentials" end end end end end PK!I 1gemcutter_utilities/webauthn_listener/response.rbnu[# frozen_string_literal: true ## # The WebauthnListener Response class is used by the WebauthnListener to create # responses to be sent to the Gem host. It creates a Gem::Net::HTTPResponse instance # when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`. # Gem::Net::HTTPResponse instances cannot be directly sent over a socket. # # Types of response classes: # - OkResponse # - NoContentResponse # - BadRequestResponse # - NotFoundResponse # - MethodNotAllowedResponse # # Example usage: # # server = TCPServer.new(0) # socket = server.accept # # response = OkResponse.for("https://rubygems.example") # socket.print response.to_s # socket.close # module Gem::GemcutterUtilities class WebauthnListener class Response attr_reader :http_response def self.for(host) new(host) end def initialize(host) @host = host build_http_response end def to_s status_line = "HTTP/#{@http_response.http_version} #{@http_response.code} #{@http_response.message}\r\n" headers = @http_response.to_hash.map {|header, value| "#{header}: #{value.join(", ")}\r\n" }.join + "\r\n" body = @http_response.body ? "#{@http_response.body}\n" : "" status_line + headers + body end private # Must be implemented in subclasses def code raise NotImplementedError end def reason_phrase raise NotImplementedError end def body; end def build_http_response response_class = Gem::Net::HTTPResponse::CODE_TO_OBJ[code.to_s] @http_response = response_class.new("1.1", code, reason_phrase) @http_response.instance_variable_set(:@read, true) add_connection_header add_access_control_headers add_body end def add_connection_header @http_response["connection"] = "close" end def add_access_control_headers @http_response["access-control-allow-origin"] = @host @http_response["access-control-allow-methods"] = "POST" @http_response["access-control-allow-headers"] = %w[Content-Type Authorization x-csrf-token] end def add_body return unless body @http_response["content-type"] = "text/plain; charset=utf-8" @http_response["content-length"] = body.bytesize @http_response.instance_variable_set(:@body, body) end end class OkResponse < Response private def code 200 end def reason_phrase "OK" end def body "success" end end class NoContentResponse < Response private def code 204 end def reason_phrase "No Content" end end class BadRequestResponse < Response private def code 400 end def reason_phrase "Bad Request" end def body "missing code parameter" end end class NotFoundResponse < Response private def code 404 end def reason_phrase "Not Found" end end class MethodNotAllowedResponse < Response private def code 405 end def reason_phrase "Method Not Allowed" end def add_access_control_headers super @http_response["allow"] = %w[GET OPTIONS] end end end end PK!NXcommands/unpack_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../security_option" require_relative "../remote_fetcher" require_relative "../package" # forward-declare module Gem::Security # :nodoc: class Policy # :nodoc: end end class Gem::Commands::UnpackCommand < Gem::Command include Gem::VersionOption include Gem::SecurityOption def initialize require "fileutils" super "unpack", "Unpack an installed gem to the current directory", version: Gem::Requirement.default, target: Dir.pwd add_option("--target=DIR", "target directory for unpacking") do |value, options| options[:target] = value end add_option("--spec", "unpack the gem specification") do |_value, options| options[:spec] = true end add_security_option add_version_option end def arguments # :nodoc: "GEMNAME name of gem to unpack" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}'" end def description <<-EOF The unpack command allows you to examine the contents of a gem or modify them to help diagnose a bug. You can add the contents of the unpacked gem to the load path using the RUBYLIB environment variable or -I: $ gem unpack my_gem Unpacked gem: '.../my_gem-1.0' [edit my_gem-1.0/lib/my_gem.rb] $ ruby -Imy_gem-1.0/lib -S other_program You can repackage an unpacked gem using the build command. See the build command help for an example. EOF end def usage # :nodoc: "#{program_name} GEMNAME" end #-- # TODO: allow, e.g., 'gem unpack rake-0.3.1'. Find a general solution for # this, so that it works for uninstall as well. (And check other commands # at the same time.) def execute security_policy = options[:security_policy] get_all_gem_names.each do |name| dependency = Gem::Dependency.new name, options[:version] path = get_path dependency unless path alert_error "Gem '#{name}' not installed nor fetchable." next end if @options[:spec] spec, metadata = Gem::Package.raw_spec(path, security_policy) if metadata.nil? alert_error "--spec is unsupported on '#{name}' (old format gem)" next end spec_file = File.basename spec.spec_file FileUtils.mkdir_p @options[:target] if @options[:target] destination = if @options[:target] File.join @options[:target], spec_file else spec_file end File.open destination, "w" do |io| io.write metadata end else basename = File.basename path, ".gem" target_dir = File.expand_path basename, options[:target] package = Gem::Package.new path, security_policy package.extract_files target_dir say "Unpacked gem: '#{target_dir}'" end end end ## # # Find cached filename in Gem.path. Returns nil if the file cannot be found. # #-- # TODO: see comments in get_path() about general service. def find_in_cache(filename) Gem.path.each do |path| this_path = File.join(path, "cache", filename) return this_path if File.exist? this_path end nil end ## # Return the full path to the cached gem file matching the given # name and version requirement. Returns 'nil' if no match. # # Example: # # get_path 'rake', '> 0.4' # "/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem" # get_path 'rake', '< 0.1' # nil # get_path 'rak' # nil (exact name required) #-- def get_path(dependency) return dependency.name if /\.gem$/i.match?(dependency.name) specs = dependency.matching_specs selected = specs.max_by(&:version) return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless selected return unless /^#{selected.name}$/i.match?(dependency.name) # We expect to find (basename).gem in the 'cache' directory. Furthermore, # the name match must be exact (ignoring case). path = find_in_cache File.basename selected.cache_file return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless path path end end PK!Rcommands/stale_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::StaleCommand < Gem::Command def initialize super("stale", "List gems along with access times") end def description # :nodoc: <<-EOF The stale command lists the latest access time for all the files in your installed gems. You can use this command to discover gems and gem versions you are no longer using. EOF end def usage # :nodoc: program_name.to_s end def execute gem_to_atime = {} Gem::Specification.each do |spec| name = spec.full_name Dir["#{spec.full_gem_path}/**/*.*"].each do |file| next if File.directory?(file) stat = File.stat(file) gem_to_atime[name] ||= stat.atime gem_to_atime[name] = stat.atime if gem_to_atime[name] < stat.atime end end gem_to_atime.sort_by {|_, atime| atime }.each do |name, atime| say "#{name} at #{atime.strftime "%c"}" end end end PK!X9llcommands/uninstall_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../uninstaller" require "fileutils" ## # Gem uninstaller command line tool # # See `gem help uninstall` class Gem::Commands::UninstallCommand < Gem::Command include Gem::VersionOption def initialize super "uninstall", "Uninstall gems from the local repository", version: Gem::Requirement.default, user_install: true, check_dev: false, vendor: false add_option("-a", "--[no-]all", "Uninstall all matching versions") do |value, options| options[:all] = value end add_option("-I", "--[no-]ignore-dependencies", "Ignore dependency requirements while", "uninstalling") do |value, options| options[:ignore] = value end add_option("-D", "--[no-]check-development", "Check development dependencies while uninstalling", "(default: false)") do |value, options| options[:check_dev] = value end add_option("-x", "--[no-]executables", "Uninstall applicable executables without", "confirmation") do |value, options| options[:executables] = value end add_option("-i", "--install-dir DIR", "Directory to uninstall gem from") do |value, options| options[:install_dir] = File.expand_path(value) end add_option("-n", "--bindir DIR", "Directory to remove executables from") do |value, options| options[:bin_dir] = File.expand_path(value) end add_option("--[no-]user-install", "Uninstall from user's home directory", "in addition to GEM_HOME.") do |value, options| options[:user_install] = value end add_option("--[no-]format-executable", "Assume executable names match Ruby's prefix and suffix.") do |value, options| options[:format_executable] = value end add_option("--[no-]force", "Uninstall all versions of the named gems", "ignoring dependencies") do |value, options| options[:force] = value end add_option("--[no-]abort-on-dependent", "Prevent uninstalling gems that are", "depended on by other gems.") do |value, options| options[:abort_on_dependent] = value end add_version_option add_platform_option add_option("--vendor", "Uninstall gem from the vendor directory.", "Only for use by gem repackagers.") do |_value, options| unless Gem.vendor_dir raise Gem::OptionParser::InvalidOption.new "your platform is not supported" end alert_warning "Use your OS package manager to uninstall vendor gems" options[:vendor] = true options[:install_dir] = Gem.vendor_dir end end def arguments # :nodoc: "GEMNAME name of gem to uninstall" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}' --no-force " \ "--user-install" end def description # :nodoc: <<-EOF The uninstall command removes a previously installed gem. RubyGems will ask for confirmation if you are attempting to uninstall a gem that is a dependency of an existing gem. You can use the --ignore-dependencies option to skip this check. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def check_version # :nodoc: if options[:version] != Gem::Requirement.default && get_all_gem_names.size > 1 alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" terminate_interaction 1 end end def execute check_version # Consider only gem specifications installed at `--install-dir` Gem::Specification.dirs = options[:install_dir] if options[:install_dir] if options[:all] && !options[:args].empty? uninstall_specific elsif options[:all] uninstall_all else uninstall_specific end end def uninstall_all specs = Gem::Specification.reject(&:default_gem?) specs.each do |spec| options[:version] = spec.version uninstall_gem spec.name end alert "Uninstalled all gems in #{options[:install_dir] || Gem.dir}" end def uninstall_specific deplist = Gem::DependencyList.new original_gem_version = {} get_all_gem_names_and_versions.each do |name, version| original_gem_version[name] = version || options[:version] gem_specs = Gem::Specification.find_all_by_name(name, original_gem_version[name]) if gem_specs.empty? say("Gem '#{name}' is not installed") else gem_specs.reject!(&:default_gem?) if gem_specs.size > 1 gem_specs.each do |spec| deplist.add spec end end end deps = deplist.strongly_connected_components.flatten.reverse gems_to_uninstall = {} deps.each do |dep| if original_gem_version[dep.name] == Gem::Requirement.default next if gems_to_uninstall[dep.name] gems_to_uninstall[dep.name] = true else options[:version] = dep.version end uninstall_gem(dep.name) end end def uninstall_gem(gem_name) uninstall(gem_name) rescue Gem::GemNotInHomeException => e spec = e.spec alert("In order to remove #{spec.name}, please execute:\n" \ "\tgem uninstall #{spec.name} --install-dir=#{spec.base_dir}") rescue Gem::UninstallError => e spec = e.spec alert_error("Error: unable to successfully uninstall '#{spec.name}' which is " \ "located at '#{spec.full_gem_path}'. This is most likely because" \ "the current user does not have the appropriate permissions") terminate_interaction 1 end def uninstall(gem_name) Gem::Uninstaller.new(gem_name, options).uninstall end end PK!ߘ|commands/rdoc_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../rdoc" require "fileutils" class Gem::Commands::RdocCommand < Gem::Command include Gem::VersionOption def initialize super "rdoc", "Generates RDoc for pre-installed gems", version: Gem::Requirement.default, include_rdoc: false, include_ri: true, overwrite: false add_option("--all", "Generate RDoc/RI documentation for all", "installed gems") do |value, options| options[:all] = value end add_option("--[no-]rdoc", "Generate RDoc HTML") do |value, options| options[:include_rdoc] = value end add_option("--[no-]ri", "Generate RI data") do |value, options| options[:include_ri] = value end add_option("--[no-]overwrite", "Overwrite installed documents") do |value, options| options[:overwrite] = value end add_version_option end def arguments # :nodoc: "GEMNAME gem to generate documentation for (unless --all)" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}' --ri --no-overwrite" end def description # :nodoc: <<-DESC The rdoc command builds documentation for installed gems. By default only documentation is built using rdoc, but additional types of documentation may be built through rubygems plugins and the Gem.post_installs hook. Use --overwrite to force rebuilding of documentation. DESC end def usage # :nodoc: "#{program_name} [args]" end def execute specs = if options[:all] Gem::Specification.to_a else get_all_gem_names.flat_map do |name| Gem::Specification.find_by_name name, options[:version] end.uniq end if specs.empty? alert_error "No matching gems found" terminate_interaction 1 end specs.each do |spec| doc = Gem::RDoc.new spec, options[:include_rdoc], options[:include_ri] doc.force = options[:overwrite] if options[:overwrite] FileUtils.rm_rf File.join(spec.doc_dir, "ri") FileUtils.rm_rf File.join(spec.doc_dir, "rdoc") end doc.generate end end end PK!̅NV V commands/build_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../gemspec_helpers" require_relative "../package" require_relative "../version_option" class Gem::Commands::BuildCommand < Gem::Command include Gem::VersionOption include Gem::GemspecHelpers def initialize super "build", "Build a gem from a gemspec" add_platform_option add_option "--force", "skip validation of the spec" do |_value, options| options[:force] = true end add_option "--strict", "consider warnings as errors when validating the spec" do |_value, options| options[:strict] = true end add_option "-o", "--output FILE", "output gem with the given filename" do |value, options| options[:output] = value end end def arguments # :nodoc: "GEMSPEC_FILE gemspec file name to build a gem for" end def description # :nodoc: <<-EOF The build command allows you to create a gem from a ruby gemspec. The best way to build a gem is to use a Rakefile and the Gem::PackageTask which ships with RubyGems. The gemspec can either be created by hand or extracted from an existing gem with gem spec: $ gem unpack my_gem-1.0.gem Unpacked gem: '.../my_gem-1.0' $ gem spec my_gem-1.0.gem --ruby > my_gem-1.0/my_gem-1.0.gemspec $ cd my_gem-1.0 [edit gem contents] $ gem build my_gem-1.0.gemspec Gems can be saved to a specified filename with the output option: $ gem build my_gem-1.0.gemspec --output=release.gem EOF end def usage # :nodoc: "#{program_name} GEMSPEC_FILE" end def execute if build_path = options[:build_path] Dir.chdir(build_path) { build_gem } return end build_gem end private def build_gem gemspec = resolve_gem_name if gemspec build_package(gemspec) else alert_error error_message terminate_interaction(1) end end def build_package(gemspec) spec = Gem::Specification.load(gemspec) if spec Gem::Package.build( spec, options[:force], options[:strict], options[:output] ) else alert_error "Error loading gemspec. Aborting." terminate_interaction 1 end end def resolve_gem_name return find_gemspec unless gem_name if File.exist?(gem_name) gem_name else find_gemspec("#{gem_name}.gemspec") || find_gemspec(gem_name) end end def error_message if gem_name "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}" else "Couldn't find a gemspec file in #{Dir.pwd}" end end def gem_name get_one_optional_argument end end PK!D^dl commands/fetch_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" class Gem::Commands::FetchCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize defaults = { suggest_alternate: true, version: Gem::Requirement.default, } super "fetch", "Download a gem and place it in the current directory", defaults add_bulk_threshold_option add_proxy_option add_source_option add_clear_sources_option add_version_option add_platform_option add_prerelease_option add_option "--[no-]suggestions", "Suggest alternates when gems are not found" do |value, options| options[:suggest_alternate] = value end end def arguments # :nodoc: "GEMNAME name of gem to download" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}'" end def description # :nodoc: <<-EOF The fetch command fetches gem files that can be stored for later use or unpacked to examine their contents. See the build command help for an example of unpacking a gem, modifying it, then repackaging it. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def check_version # :nodoc: if options[:version] != Gem::Requirement.default && get_all_gem_names.size > 1 alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" terminate_interaction 1 end end def execute check_version exit_code = fetch_gems terminate_interaction exit_code end private def fetch_gems exit_code = 0 version = options[:version] platform = Gem.platforms.last gem_names = get_all_gem_names_and_versions gem_names.each do |gem_name, gem_version| gem_version ||= version dep = Gem::Dependency.new gem_name, gem_version dep.prerelease = options[:prerelease] suppress_suggestions = !options[:suggest_alternate] specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep if platform filtered = specs_and_sources.select {|s,| s.platform == platform } specs_and_sources = filtered unless filtered.empty? end spec, source = specs_and_sources.max_by {|s,| s } if spec.nil? show_lookup_failure gem_name, gem_version, errors, suppress_suggestions, options[:domain] exit_code |= 2 next end source.download spec say "Downloaded #{spec.full_name}" end exit_code end end PK!~commands/install_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../install_update_options" require_relative "../dependency_installer" require_relative "../local_remote_options" require_relative "../validator" require_relative "../version_option" require_relative "../update_suggestion" ## # Gem installer command line tool # # See `gem help install` class Gem::Commands::InstallCommand < Gem::Command attr_reader :installed_specs # :nodoc: include Gem::VersionOption include Gem::LocalRemoteOptions include Gem::InstallUpdateOptions include Gem::UpdateSuggestion def initialize defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ format_executable: false, lock: true, suggest_alternate: true, version: Gem::Requirement.default, without_groups: [], }) defaults.merge!(install_update_options) super "install", "Install a gem into the local repository", defaults add_install_update_options add_local_remote_options add_platform_option add_version_option add_prerelease_option "to be installed. (Only for listed gems)" @installed_specs = [] end def arguments # :nodoc: "GEMNAME name of gem to install" end def defaults_str # :nodoc: "--both --version '#{Gem::Requirement.default}' --no-force\n" \ "--install-dir #{Gem.dir} --lock\n" + install_update_defaults_str end def description # :nodoc: <<-EOF The install command installs local or remote gem into a gem repository. For gems with executables ruby installs a wrapper file into the executable directory by default. This can be overridden with the --no-wrappers option. The wrapper allows you to choose among alternate gem versions using _version_. For example `rake _0.7.3_ --version` will run rake version 0.7.3 if a newer version is also installed. Gem Dependency Files ==================== RubyGems can install a consistent set of gems across multiple environments using `gem install -g` when a gem dependencies file (gem.deps.rb, Gemfile or Isolate) is present. If no explicit file is given RubyGems attempts to find one in the current directory. When the RUBYGEMS_GEMDEPS environment variable is set to a gem dependencies file the gems from that file will be activated at startup time. Set it to a specific filename or to "-" to have RubyGems automatically discover the gem dependencies file by walking up from the current directory. NOTE: Enabling automatic discovery on multiuser systems can lead to execution of arbitrary code when used from directories outside your control. Extension Install Failures ========================== If an extension fails to compile during gem installation the gem specification is not written out, but the gem remains unpacked in the repository. You may need to specify the path to the library's headers and libraries to continue. You can do this by adding a -- between RubyGems' options and the extension's build options: $ gem install some_extension_gem [build fails] Gem files will remain installed in \\ /path/to/gems/some_extension_gem-1.0 for inspection. Results logged to /path/to/gems/some_extension_gem-1.0/gem_make.out $ gem install some_extension_gem -- --with-extension-lib=/path/to/lib [build succeeds] $ gem list some_extension_gem *** LOCAL GEMS *** some_extension_gem (1.0) $ If you correct the compilation errors by editing the gem files you will need to write the specification by hand. For example: $ gem install some_extension_gem [build fails] Gem files will remain installed in \\ /path/to/gems/some_extension_gem-1.0 for inspection. Results logged to /path/to/gems/some_extension_gem-1.0/gem_make.out $ [cd /path/to/gems/some_extension_gem-1.0] $ [edit files or what-have-you and run make] $ gem spec ../../cache/some_extension_gem-1.0.gem --ruby > \\ ../../specifications/some_extension_gem-1.0.gemspec $ gem list some_extension_gem *** LOCAL GEMS *** some_extension_gem (1.0) $ Command Alias ========================== You can use `i` command instead of `install`. $ gem i GEMNAME EOF end def usage # :nodoc: "#{program_name} [options] GEMNAME [GEMNAME ...] -- --build-flags" end def check_version # :nodoc: if options[:version] != Gem::Requirement.default && get_all_gem_names.size > 1 alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \ " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`" terminate_interaction 1 end end def execute if options.include? :gemdeps install_from_gemdeps return # not reached end @installed_specs = [] ENV.delete "GEM_PATH" if options[:install_dir].nil? check_version load_hooks exit_code = install_gems show_installed say update_suggestion if eligible_for_update? terminate_interaction exit_code end def install_from_gemdeps # :nodoc: require_relative "../request_set" rs = Gem::RequestSet.new specs = rs.install_from_gemdeps options do |req, inst| s = req.full_spec if inst say "Installing #{s.name} (#{s.version})" else say "Using #{s.name} (#{s.version})" end end @installed_specs = specs terminate_interaction end def install_gem(name, version) # :nodoc: return if options[:conservative] && !Gem::Dependency.new(name, version).matching_specs.empty? req = Gem::Requirement.create(version) dinst = Gem::DependencyInstaller.new options request_set = dinst.resolve_dependencies name, req if options[:explain] say "Gems to install:" request_set.sorted_requests.each do |activation_request| say " #{activation_request.full_name}" end else @installed_specs.concat request_set.install options end show_install_errors dinst.errors end def install_gems # :nodoc: exit_code = 0 get_all_gem_names_and_versions.each do |gem_name, gem_version| gem_version ||= options[:version] domain = options[:domain] domain = :local unless options[:suggest_alternate] suppress_suggestions = (domain == :local) begin install_gem gem_name, gem_version rescue Gem::InstallError => e alert_error "Error installing #{gem_name}:\n\t#{e.message}" exit_code |= 1 rescue Gem::UnsatisfiableDependencyError => e show_lookup_failure e.name, e.version, e.errors, suppress_suggestions, "'#{gem_name}' (#{gem_version})" exit_code |= 2 end end exit_code end ## # Loads post-install hooks def load_hooks # :nodoc: require_relative "../install_message" require_relative "../rdoc" end def show_install_errors(errors) # :nodoc: return unless errors errors.each do |x| next unless Gem::SourceFetchProblem === x require_relative "../uri" msg = "Unable to pull data from '#{Gem::Uri.redact(x.source.uri)}': #{x.error.message}" alert_warning msg end end def show_installed # :nodoc: return if @installed_specs.empty? gems = @installed_specs.length == 1 ? "gem" : "gems" say "#{@installed_specs.length} #{gems} installed" end end PK!K=@BBcommands/contents_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" class Gem::Commands::ContentsCommand < Gem::Command include Gem::VersionOption def initialize super "contents", "Display the contents of the installed gems", specdirs: [], lib_only: false, prefix: true, show_install_dir: false add_version_option add_option("--all", "Contents for all gems") do |all, options| options[:all] = all end add_option("-s", "--spec-dir a,b,c", Array, "Search for gems under specific paths") do |spec_dirs, options| options[:specdirs] = spec_dirs end add_option("-l", "--[no-]lib-only", "Only return files in the Gem's lib_dirs") do |lib_only, options| options[:lib_only] = lib_only end add_option("--[no-]prefix", "Don't include installed path prefix") do |prefix, options| options[:prefix] = prefix end add_option("--[no-]show-install-dir", "Show only the gem install dir") do |show, options| options[:show_install_dir] = show end @path_kind = nil @spec_dirs = nil @version = nil end def arguments # :nodoc: "GEMNAME name of gem to list contents for" end def defaults_str # :nodoc: "--no-lib-only --prefix" end def description # :nodoc: <<-EOF The contents command lists the files in an installed gem. The listing can be given as full file names, file names without the installed directory prefix or only the files that are requireable. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def execute @version = options[:version] || Gem::Requirement.default @spec_dirs = specification_directories @path_kind = path_description @spec_dirs names = gem_names names.each do |name| found = if options[:show_install_dir] gem_install_dir name else gem_contents name end terminate_interaction 1 unless found || names.length > 1 end end def files_in(spec) if spec.default_gem? files_in_default_gem spec else files_in_gem spec end end def files_in_gem(spec) gem_path = spec.full_gem_path extra = "/{#{spec.require_paths.join ","}}" if options[:lib_only] glob = "#{gem_path}#{extra}/**/*" prefix_re = %r{#{Regexp.escape(gem_path)}/} Dir[glob].map do |file| [gem_path, file.sub(prefix_re, "")] end end def files_in_default_gem(spec) spec.files.filter_map do |file| if file.start_with?("#{spec.bindir}/") [RbConfig::CONFIG["bindir"], file.delete_prefix("#{spec.bindir}/")] else gem spec.name, spec.version require_path = spec.require_paths.find do |path| file.start_with?("#{path}/") end requirable_part = file.delete_prefix("#{require_path}/") resolve = $LOAD_PATH.resolve_feature_path(requirable_part)&.last next unless resolve [resolve.delete_suffix(requirable_part), requirable_part] end end end def gem_contents(name) spec = spec_for name return false unless spec files = files_in spec show_files files true end def gem_install_dir(name) spec = spec_for name return false unless spec say spec.gem_dir true end def gem_names # :nodoc: if options[:all] Gem::Specification.map(&:name) else get_all_gem_names end end def path_description(spec_dirs) # :nodoc: if spec_dirs.empty? "default gem paths" else "specified path" end end def show_files(files) files.sort.each do |prefix, basename| absolute_path = File.join(prefix, basename) next if File.directory? absolute_path if options[:prefix] say absolute_path else say basename end end end def spec_for(name) spec = Gem::Specification.find_all_by_name(name, @version).first return spec if spec say "Unable to find gem '#{name}' in #{@path_kind}" if Gem.configuration.verbose say "\nDirectories searched:" @spec_dirs.sort.each {|dir| say dir } end nil end def specification_directories # :nodoc: options[:specdirs].flat_map do |i| [i, File.join(i, "specifications")] end end end PK!Xi^commands/cleanup_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../dependency_list" require_relative "../uninstaller" class Gem::Commands::CleanupCommand < Gem::Command def initialize super "cleanup", "Clean up old versions of installed gems", force: false, install_dir: Gem.dir, check_dev: true add_option("-n", "-d", "--dry-run", "Do not uninstall gems") do |_value, options| options[:dryrun] = true end add_option(:Deprecated, "--dryrun", "Do not uninstall gems") do |_value, options| options[:dryrun] = true end deprecate_option("--dryrun", extra_msg: "Use --dry-run instead") add_option("-D", "--[no-]check-development", "Check development dependencies while uninstalling", "(default: true)") do |value, options| options[:check_dev] = value end add_option("--[no-]user-install", "Cleanup in user's home directory instead", "of GEM_HOME.") do |value, options| options[:user_install] = value end @candidate_gems = nil @default_gems = [] @full = nil @gems_to_cleanup = nil @primary_gems = nil end def arguments # :nodoc: "GEMNAME name of gem to cleanup" end def defaults_str # :nodoc: "--no-dry-run" end def description # :nodoc: <<-EOF The cleanup command removes old versions of gems from GEM_HOME that are not required to meet a dependency. If a gem is installed elsewhere in GEM_PATH the cleanup command won't delete it. If no gems are named all gems in GEM_HOME are cleaned. EOF end def usage # :nodoc: "#{program_name} [GEMNAME ...]" end def execute say "Cleaning up installed gems..." if options[:args].empty? done = false last_set = nil until done do clean_gems this_set = @gems_to_cleanup.map(&:full_name).sort done = this_set.empty? || last_set == this_set last_set = this_set end else clean_gems end say "Clean up complete" verbose do skipped = @default_gems.map(&:full_name) "Skipped default gems: #{skipped.join ", "}" end end def clean_gems get_primary_gems get_candidate_gems get_gems_to_cleanup @full = Gem::DependencyList.from_specs deplist = Gem::DependencyList.new @gems_to_cleanup.each {|spec| deplist.add spec } deps = deplist.strongly_connected_components.flatten deps.reverse_each do |spec| uninstall_dep spec end end def get_candidate_gems @candidate_gems = if options[:args].empty? Gem::Specification.to_a else options[:args].flat_map do |gem_name| Gem::Specification.find_all_by_name gem_name end end end def get_gems_to_cleanup gems_to_cleanup = @candidate_gems.select do |spec| @primary_gems[spec.name].version != spec.version end default_gems, gems_to_cleanup = gems_to_cleanup.partition(&:default_gem?) uninstall_from = options[:user_install] ? Gem.user_dir : Gem.dir gems_to_cleanup = gems_to_cleanup.select do |spec| spec.base_dir == uninstall_from end @default_gems += default_gems @default_gems.uniq! @gems_to_cleanup = gems_to_cleanup.uniq end def get_primary_gems @primary_gems = {} Gem::Specification.each do |spec| if @primary_gems[spec.name].nil? || @primary_gems[spec.name].version < spec.version @primary_gems[spec.name] = spec end end end def uninstall_dep(spec) return unless @full.ok_to_remove?(spec.full_name, options[:check_dev]) if options[:dryrun] say "Dry Run Mode: Would uninstall #{spec.full_name}" return end say "Attempting to uninstall #{spec.full_name}" uninstall_options = { executables: false, version: "= #{spec.version}", } uninstall_options[:user_install] = Gem.user_dir == spec.base_dir uninstaller = Gem::Uninstaller.new spec.name, uninstall_options begin uninstaller.uninstall rescue Gem::DependencyRemovalException, Gem::InstallError, Gem::GemNotInHomeException, Gem::FilePermissionError => e say "Unable to uninstall #{spec.full_name}:" say "\t#{e.class}: #{e.message}" end end end PK!|TL$$commands/cert_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../security" class Gem::Commands::CertCommand < Gem::Command def initialize super "cert", "Manage RubyGems certificates and signing settings", add: [], remove: [], list: [], build: [], sign: [] add_option("-a", "--add CERT", "Add a trusted certificate.") do |cert_file, options| options[:add] << open_cert(cert_file) end add_option("-l", "--list [FILTER]", "List trusted certificates where the", "subject contains FILTER") do |filter, options| filter ||= "" options[:list] << filter end add_option("-r", "--remove FILTER", "Remove trusted certificates where the", "subject contains FILTER") do |filter, options| options[:remove] << filter end add_option("-b", "--build EMAIL_ADDR", "Build private key and self-signed", "certificate for EMAIL_ADDR") do |email_address, options| options[:build] << email_address end add_option("-C", "--certificate CERT", "Signing certificate for --sign") do |cert_file, options| options[:issuer_cert] = open_cert(cert_file) options[:issuer_cert_file] = cert_file end add_option("-K", "--private-key KEY", "Key for --sign or --build") do |key_file, options| options[:key] = open_private_key(key_file) end add_option("-A", "--key-algorithm ALGORITHM", "Select which key algorithm to use for --build") do |algorithm, options| options[:key_algorithm] = algorithm end add_option("-s", "--sign CERT", "Signs CERT with the key from -K", "and the certificate from -C") do |cert_file, options| raise Gem::OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless File.file? cert_file options[:sign] << cert_file end add_option("-d", "--days NUMBER_OF_DAYS", "Days before the certificate expires") do |days, options| options[:expiration_length_days] = days.to_i end add_option("-R", "--re-sign", "Re-signs the certificate from -C with the key from -K") do |resign, options| options[:resign] = resign end end def add_certificate(certificate) # :nodoc: Gem::Security.trust_dir.trust_cert certificate say "Added '#{certificate.subject}'" end def check_openssl return if Gem::HAVE_OPENSSL alert_error "OpenSSL library is required for the cert command" terminate_interaction 1 end def open_cert(certificate_file) check_openssl OpenSSL::X509::Certificate.new File.read certificate_file rescue Errno::ENOENT raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: does not exist" rescue OpenSSL::X509::CertificateError raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: invalid X509 certificate" end def open_private_key(key_file) check_openssl passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"] key = OpenSSL::PKey.read File.read(key_file), passphrase raise Gem::OptionParser::InvalidArgument, "#{key_file}: private key not found" unless key.private? key rescue Errno::ENOENT raise Gem::OptionParser::InvalidArgument, "#{key_file}: does not exist" rescue OpenSSL::PKey::PKeyError, ArgumentError raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key" end def execute check_openssl options[:add].each do |certificate| add_certificate certificate end options[:remove].each do |filter| remove_certificates_matching filter end options[:list].each do |filter| list_certificates_matching filter end options[:build].each do |email| build email end if options[:resign] re_sign_cert( options[:issuer_cert], options[:issuer_cert_file], options[:key] ) end sign_certificates unless options[:sign].empty? end def build(email) unless valid_email?(email) raise Gem::CommandLineError, "Invalid email address #{email}" end key, key_path = build_key cert_path = build_cert email, key say "Certificate: #{cert_path}" if key_path say "Private Key: #{key_path}" say "Don't forget to move the key file to somewhere private!" end end def build_cert(email, key) # :nodoc: expiration_length_days = options[:expiration_length_days] || Gem.configuration.cert_expiration_length_days cert = Gem::Security.create_cert_email( email, key, Gem::Security::ONE_DAY * expiration_length_days ) Gem::Security.write cert, "gem-public_cert.pem" end def build_key # :nodoc: return options[:key] if options[:key] passphrase = ask_for_password "Passphrase for your Private Key:" say "\n" passphrase_confirmation = ask_for_password "Please repeat the passphrase for your Private Key:" say "\n" raise Gem::CommandLineError, "Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM key = Gem::Security.create_key(algorithm) key_path = Gem::Security.write key, "gem-private_key.pem", 0o600, passphrase [key, key_path] end def certificates_matching(filter) return enum_for __method__, filter unless block_given? Gem::Security.trusted_certificates.select do |certificate, _| subject = certificate.subject.to_s subject.downcase.index filter end.sort_by do |certificate, _| certificate.subject.to_a.map {|name, data,| [name, data] } end.each do |certificate, path| yield certificate, path end end def description # :nodoc: <<-EOF The cert command manages signing keys and certificates for creating signed gems. Your signing certificate and private key are typically stored in ~/.gem/gem-public_cert.pem and ~/.gem/gem-private_key.pem respectively. To build a certificate for signing gems: gem cert --build you@example If you already have an RSA key, or are creating a new certificate for an existing key: gem cert --build you@example --private-key /path/to/key.pem If you wish to trust a certificate you can add it to the trust list with: gem cert --add /path/to/cert.pem You can list trusted certificates with: gem cert --list or: gem cert --list cert_subject_substring If you wish to remove a previously trusted certificate: gem cert --remove cert_subject_substring To sign another gem author's certificate: gem cert --sign /path/to/other_cert.pem For further reading on signing gems see `ri Gem::Security`. EOF end def list_certificates_matching(filter) # :nodoc: certificates_matching filter do |certificate, _| # this could probably be formatted more gracefully say certificate.subject.to_s end end def load_default_cert cert_file = File.join Gem.default_cert_path cert = File.read cert_file options[:issuer_cert] = OpenSSL::X509::Certificate.new cert rescue Errno::ENOENT alert_error \ "--certificate not specified and ~/.gem/gem-public_cert.pem does not exist" terminate_interaction 1 rescue OpenSSL::X509::CertificateError alert_error \ "--certificate not specified and ~/.gem/gem-public_cert.pem is not valid" terminate_interaction 1 end def load_default_key key_file = File.join Gem.default_key_path key = File.read key_file passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"] options[:key] = OpenSSL::PKey.read key, passphrase rescue Errno::ENOENT alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem does not exist" terminate_interaction 1 rescue OpenSSL::PKey::PKeyError alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem is not valid" terminate_interaction 1 end def load_defaults # :nodoc: load_default_cert unless options[:issuer_cert] load_default_key unless options[:key] end def remove_certificates_matching(filter) # :nodoc: certificates_matching filter do |certificate, path| FileUtils.rm path say "Removed '#{certificate.subject}'" end end def sign(cert_file) cert = File.read cert_file cert = OpenSSL::X509::Certificate.new cert permissions = File.stat(cert_file).mode & 0o777 issuer_cert = options[:issuer_cert] issuer_key = options[:key] cert = Gem::Security.sign cert, issuer_key, issuer_cert Gem::Security.write cert, cert_file, permissions end def sign_certificates # :nodoc: load_defaults unless options[:sign].empty? options[:sign].each do |cert_file| sign cert_file end end def re_sign_cert(cert, cert_path, private_key) Gem::Security::Signer.re_sign_cert(cert, cert_path, private_key) do |expired_cert_path, new_expired_cert_path| alert("Your certificate #{expired_cert_path} has been re-signed") alert("Your expired certificate will be located at: #{new_expired_cert_path}") end end private def valid_email?(email) # It's simple, but is all we need email =~ /\A.+@.+\z/ end end PK!v((commands/help_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::HelpCommand < Gem::Command # :stopdoc: EXAMPLES = <<-EOF Some examples of 'gem' usage. * Install 'rake', either from local directory or remote server: gem install rake * Install 'rake', only from remote server: gem install rake --remote * Install 'rake', but only version 0.3.1, even if dependencies are not met, and into a user-specific directory: gem install rake --version 0.3.1 --force --user-install * List local gems whose name begins with 'D': gem list D * List local and remote gems whose name contains 'log': gem search log --both * List only remote gems whose name contains 'log': gem search log --remote * Uninstall 'rake': gem uninstall rake * Create a gem: See https://guides.rubygems.org/make-your-own-gem/ * See information about RubyGems: gem environment * Update all gems on your system: gem update * Update your local version of RubyGems gem update --system EOF GEM_DEPENDENCIES = <<-EOF A gem dependencies file allows installation of a consistent set of gems across multiple environments. The RubyGems implementation is designed to be compatible with Bundler's Gemfile format. You can see additional documentation on the format at: https://bundler.io RubyGems automatically looks for these gem dependencies files: * gem.deps.rb * Gemfile * Isolate These files are looked up automatically using `gem install -g`, or you can specify a custom file. When the RUBYGEMS_GEMDEPS environment variable is set to a gem dependencies file the gems from that file will be activated at startup time. Set it to a specific filename or to "-" to have RubyGems automatically discover the gem dependencies file by walking up from the current directory. You can also activate gem dependencies at program startup using Gem.use_gemdeps. NOTE: Enabling automatic discovery on multiuser systems can lead to execution of arbitrary code when used from directories outside your control. Gem Dependencies ================ Use #gem to declare which gems you directly depend upon: gem 'rake' To depend on a specific set of versions: gem 'rake', '~> 10.3', '>= 10.3.2' RubyGems will require the gem name when activating the gem using the RUBYGEMS_GEMDEPS environment variable or Gem::use_gemdeps. Use the require: option to override this behavior if the gem does not have a file of that name or you don't want to require those files: gem 'my_gem', require: 'other_file' To prevent RubyGems from requiring any files use: gem 'my_gem', require: false To load dependencies from a .gemspec file: gemspec RubyGems looks for the first .gemspec file in the current directory. To override this use the name: option: gemspec name: 'specific_gem' To look in a different directory use the path: option: gemspec name: 'specific_gem', path: 'gemspecs' To depend on a gem unpacked into a local directory: gem 'modified_gem', path: 'vendor/modified_gem' To depend on a gem from git: gem 'private_gem', git: 'git@my.company.example:private_gem.git' To depend on a gem from github: gem 'private_gem', github: 'my_company/private_gem' To depend on a gem from a github gist: gem 'bang', gist: '1232884' Git, github and gist support the ref:, branch: and tag: options to specify a commit reference or hash, branch or tag respectively to use for the gem. Setting the submodules: option to true for git, github and gist dependencies causes fetching of submodules when fetching the repository. You can depend on multiple gems from a single repository with the git method: git 'https://github.com/rails/rails.git' do gem 'activesupport' gem 'activerecord' end Gem Sources =========== RubyGems uses the default sources for regular `gem install` for gem dependencies files. Unlike bundler, you do need to specify a source. You can override the sources used for downloading gems with: source 'https://gem_server.example' You may specify multiple sources. Unlike bundler the prepend: option is not supported. Sources are used in-order, to prepend a source place it at the front of the list. Gem Platform ============ You can restrict gem dependencies to specific platforms with the #platform and #platforms methods: platform :ruby_21 do gem 'debugger' end See the bundler Gemfile manual page for a list of platforms supported in a gem dependencies file.: https://bundler.io/v2.5/man/gemfile.5.html Ruby Version and Engine Dependency ================================== You can specify the version, engine and engine version of ruby to use with your gem dependencies file. If you are not running the specified version RubyGems will raise an exception. To depend on a specific version of ruby: ruby '2.1.2' To depend on a specific ruby engine: ruby '1.9.3', engine: 'jruby' To depend on a specific ruby engine version: ruby '1.9.3', engine: 'jruby', engine_version: '1.7.11' Grouping Dependencies ===================== Gem dependencies may be placed in groups that can be excluded from install. Dependencies required for development or testing of your code may be excluded when installed in a production environment. A #gem dependency may be placed in a group using the group: option: gem 'minitest', group: :test To install dependencies from a gemfile without specific groups use the `--without` option for `gem install -g`: $ gem install -g --without test The group: option also accepts multiple groups if the gem fits in multiple categories. Multiple groups may be excluded during install by comma-separating the groups for `--without` or by specifying `--without` multiple times. The #group method can also be used to place gems in groups: group :test do gem 'minitest' gem 'minitest-emoji' end The #group method allows multiple groups. The #gemspec development dependencies are placed in the :development group by default. This may be overridden with the :development_group option: gemspec development_group: :other EOF PLATFORMS = <<-'EOF' RubyGems platforms are composed of three parts, a CPU, an OS, and a version. These values are taken from values in rbconfig.rb. You can view your current platform by running `gem environment`. RubyGems matches platforms as follows: * The CPU must match exactly unless one of the platforms has "universal" as the CPU or the local CPU starts with "arm" and the gem's CPU is exactly "arm" (for gems that support generic ARM architecture). * The OS must match exactly. * The versions must match exactly unless one of the versions is nil. For commands that install, uninstall and list gems, you can override what RubyGems thinks your platform is with the --platform option. The platform you pass must match "#{cpu}-#{os}" or "#{cpu}-#{os}-#{version}". On mswin platforms, the version is the compiler version, not the OS version. (Ruby compiled with VC6 uses "60" as the compiler version, VC8 uses "80".) For the ARM architecture, gems with a platform of "arm-linux" should run on a reasonable set of ARM CPUs and not depend on instructions present on a limited subset of the architecture. For example, the binary should run on platforms armv5, armv6hf, armv6l, armv7, etc. If you use the "arm-linux" platform please test your gem on a variety of ARM hardware before release to ensure it functions correctly. Example platforms: x86-freebsd # Any FreeBSD version on an x86 CPU universal-darwin-8 # Darwin 8 only gems that run on any CPU x86-mswin32-80 # Windows gems compiled with VC8 armv7-linux # Gem complied for an ARMv7 CPU running linux arm-linux # Gem compiled for any ARM CPU running linux When building platform gems, set the platform in the gem specification to Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's platform. EOF # NOTE: when updating also update Gem::Command::HELP SUBCOMMANDS = [ ["commands", :show_commands], ["options", Gem::Command::HELP], ["examples", EXAMPLES], ["gem_dependencies", GEM_DEPENDENCIES], ["platforms", PLATFORMS], ].freeze # :startdoc: def initialize super "help", "Provide help on the 'gem' command" @command_manager = Gem::CommandManager.instance end def usage # :nodoc: "#{program_name} ARGUMENT" end def execute arg = options[:args][0] _, help = SUBCOMMANDS.find do |command,| begins? command, arg end if help if Symbol === help send help else say help end return end if options[:help] show_help elsif arg show_command_help arg else say Gem::Command::HELP end end def show_commands # :nodoc: out = [] out << "GEM commands are:" out << nil margin_width = 4 desc_width = @command_manager.command_names.map(&:size).max + 4 summary_width = 80 - margin_width - desc_width wrap_indent = " " * (margin_width + desc_width) format = "#{" " * margin_width}%-#{desc_width}s%s" @command_manager.command_names.each do |cmd_name| command = @command_manager[cmd_name] next if command&.deprecated? summary = if command command.summary else "[No command found for #{cmd_name}]" end summary = wrap(summary, summary_width).split "\n" out << format(format, cmd_name, summary.shift) until summary.empty? do out << "#{wrap_indent}#{summary.shift}" end end out << nil out << "For help on a particular command, use 'gem help COMMAND'." out << nil out << "Commands may be abbreviated, so long as they are unambiguous." out << "e.g. 'gem i rake' is short for 'gem install rake'." say out.join("\n") end def show_command_help(command_name) # :nodoc: command_name = command_name.downcase possibilities = @command_manager.find_command_possibilities command_name if possibilities.size == 1 command = @command_manager[possibilities.first] command.invoke("--help") elsif possibilities.size > 1 alert_warning "Ambiguous command #{command_name} (#{possibilities.join(", ")})" else alert_warning "Unknown command #{command_name}. Try: gem help commands" end end end PK! 6}VVcommands/dependency_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" class Gem::Commands::DependencyCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize super "dependency", "Show the dependencies of an installed gem", version: Gem::Requirement.default, domain: :local add_version_option add_platform_option add_prerelease_option add_option("-R", "--[no-]reverse-dependencies", "Include reverse dependencies in the output") do |value, options| options[:reverse_dependencies] = value end add_option("-p", "--pipe", "Pipe Format (name --version ver)") do |value, options| options[:pipe_format] = value end add_local_remote_options end def arguments # :nodoc: "REGEXP show dependencies for gems whose names start with REGEXP" end def defaults_str # :nodoc: "--local --version '#{Gem::Requirement.default}' --no-reverse-dependencies" end def description # :nodoc: <<-EOF The dependency commands lists which other gems a given gem depends on. For local gems only the reverse dependencies can be shown (which gems depend on the named gem). The dependency list can be displayed in a format suitable for piping for use with other commands. EOF end def usage # :nodoc: "#{program_name} REGEXP" end def fetch_remote_specs(name, requirement, prerelease) # :nodoc: fetcher = Gem::SpecFetcher.fetcher specs_type = prerelease ? :complete : :released ss = if name.nil? fetcher.detect(specs_type) { true } else fetcher.detect(specs_type) do |name_tuple| name === name_tuple.name && requirement.satisfied_by?(name_tuple.version) end end ss.map {|tuple, source| source.fetch_spec(tuple) } end def fetch_specs(name_pattern, requirement, prerelease) # :nodoc: specs = [] if local? specs.concat Gem::Specification.stubs.find_all {|spec| name_matches = name_pattern ? name_pattern =~ spec.name : true version_matches = requirement.satisfied_by?(spec.version) name_matches && version_matches }.map(&:to_spec) end specs.concat fetch_remote_specs name_pattern, requirement, prerelease if remote? ensure_specs specs specs.uniq.sort end def display_pipe(specs) # :nodoc: specs.each do |spec| next if spec.dependencies.empty? spec.dependencies.sort_by(&:name).each do |dep| say "#{dep.name} --version '#{dep.requirement}'" end end end def display_readable(specs, reverse) # :nodoc: response = String.new specs.each do |spec| response << print_dependencies(spec) unless reverse[spec.full_name].empty? response << " Used by\n" reverse[spec.full_name].each do |sp, dep| response << " #{sp} (#{dep})\n" end end response << "\n" end say response end def execute ensure_local_only_reverse_dependencies pattern = name_pattern options[:args] requirement = Gem::Requirement.new options[:version] specs = fetch_specs pattern, requirement, options[:prerelease] reverse = reverse_dependencies specs if options[:pipe_format] display_pipe specs else display_readable specs, reverse end end def ensure_local_only_reverse_dependencies # :nodoc: if options[:reverse_dependencies] && remote? && !local? alert_error "Only reverse dependencies for local gems are supported." terminate_interaction 1 end end def ensure_specs(specs) # :nodoc: return unless specs.empty? patterns = options[:args].join "," say "No gems found matching #{patterns} (#{options[:version]})" if Gem.configuration.verbose terminate_interaction 1 end def print_dependencies(spec, level = 0) # :nodoc: response = String.new response << " " * level + "Gem #{spec.full_name}\n" unless spec.dependencies.empty? spec.dependencies.sort_by(&:name).each do |dep| response << " " * level + " #{dep}\n" end end response end def reverse_dependencies(specs) # :nodoc: reverse = Hash.new {|h, k| h[k] = [] } return reverse unless options[:reverse_dependencies] specs.each do |spec| reverse[spec.full_name] = find_reverse_dependencies spec end reverse end ## # Returns an Array of [specification, dep] that are satisfied by +spec+. def find_reverse_dependencies(spec) # :nodoc: result = [] Gem::Specification.each do |sp| sp.dependencies.each do |dep| dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep if spec.name == dep.name && dep.requirement.satisfied_by?(spec.version) result << [sp.full_name, dep] end end end result end private def name_pattern(args) return if args.empty? if args.length == 1 && args.first =~ /\A(.*)(i)?\z/m flags = $2 ? Regexp::IGNORECASE : nil Regexp.new $1, flags else /\A#{Regexp.union(*args)}/ end end end PK!scommands/pristine_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../package" require_relative "../installer" require_relative "../version_option" class Gem::Commands::PristineCommand < Gem::Command include Gem::VersionOption def initialize super "pristine", "Restores installed gems to pristine condition from files located in the gem cache", version: Gem::Requirement.default, extensions: true, extensions_set: false, all: false add_option("--all", "Restore all installed gems to pristine", "condition") do |value, options| options[:all] = value end add_option("--skip=gem_name", "used on --all, skip if name == gem_name") do |value, options| options[:skip] ||= [] options[:skip] << value end add_option("--[no-]extensions", "Restore gems with extensions", "in addition to regular gems") do |value, options| options[:extensions_set] = true options[:extensions] = value end add_option("--only-missing-extensions", "Only restore gems with missing extensions") do |value, options| options[:only_missing_extensions] = value end add_option("--only-executables", "Only restore executables") do |value, options| options[:only_executables] = value end add_option("--only-plugins", "Only restore plugins") do |value, options| options[:only_plugins] = value end add_option("-E", "--[no-]env-shebang", "Rewrite executables with a shebang", "of /usr/bin/env") do |value, options| options[:env_shebang] = value end add_option("-i", "--install-dir DIR", "Gem repository to get gems restored") do |value, options| options[:install_dir] = File.expand_path(value) end add_option("-n", "--bindir DIR", "Directory where executables are", "located") do |value, options| options[:bin_dir] = File.expand_path(value) end add_version_option("restore to", "pristine condition") end def arguments # :nodoc: "GEMNAME gem to restore to pristine condition (unless --all)" end def defaults_str # :nodoc: "--extensions" end def description # :nodoc: <<-EOF The pristine command compares an installed gem with the contents of its cached .gem file and restores any files that don't match the cached .gem's copy. If you have made modifications to an installed gem, the pristine command will revert them. All extensions are rebuilt and all bin stubs for the gem are regenerated after checking for modifications. Rebuilding extensions also refreshes C-extension gems against updated system libraries (for example after OS or package upgrades) to avoid mismatches like outdated library version warnings. If the cached gem cannot be found it will be downloaded. If --no-extensions is provided pristine will not attempt to restore a gem with an extension. If --extensions is given (but not --all or gem names) only gems with extensions will be restored. EOF end def usage # :nodoc: "#{program_name} [GEMNAME ...]" end def execute install_dir = options[:install_dir] specification_record = install_dir ? Gem::SpecificationRecord.from_path(install_dir) : Gem::Specification.specification_record specs = if options[:all] specification_record.map # `--extensions` must be explicitly given to pristine only gems # with extensions. elsif options[:extensions_set] && options[:extensions] && options[:args].empty? specification_record.select do |spec| spec.extensions && !spec.extensions.empty? end elsif options[:only_missing_extensions] specification_record.select(&:missing_extensions?) else get_all_gem_names.sort.flat_map do |gem_name| specification_record.find_all_by_name(gem_name, options[:version]).reverse end end specs = specs.select {|spec| spec.platform == RUBY_ENGINE || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY } if specs.to_a.empty? if options[:only_missing_extensions] say "No gems with missing extensions to restore" return end raise Gem::Exception, "Failed to find gems #{options[:args]} #{options[:version]}" end say "Restoring gems to pristine condition..." specs.group_by(&:full_name_with_location).values.each do |grouped_specs| spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first only_executables = options[:only_executables] only_plugins = options[:only_plugins] unless only_executables || only_plugins # Default gemspecs include changes provided by ruby-core installer that # can't currently be pristined (inclusion of compiled extension targets in # the file list). So stick to resetting executables if it's a default gem. only_executables = true if spec.default_gem? end if options.key? :skip if options[:skip].include? spec.name say "Skipped #{spec.full_name}, it was given through options" next end end unless spec.extensions.empty? || options[:extensions] || only_executables || only_plugins say "Skipped #{spec.full_name_with_location}, it needs to compile an extension" next end gem = spec.cache_file unless File.exist?(gem) || only_executables || only_plugins require_relative "../remote_fetcher" say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..." dep = Gem::Dependency.new spec.name, spec.version found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep if found.empty? say "Skipped #{spec.full_name}, it was not found from cache and remote sources" next end spec_candidate, source = found.first Gem::RemoteFetcher.fetcher.download spec_candidate, source.uri.to_s, spec.base_dir end env_shebang = if options.include? :env_shebang options[:env_shebang] else install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS["install"] install_defaults.to_s["--env-shebang"] end bin_dir = options[:bin_dir] if options[:bin_dir] installer_options = { wrappers: true, force: true, install_dir: install_dir || spec.base_dir, env_shebang: env_shebang, build_args: spec.build_args, bin_dir: bin_dir, } if only_executables installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin elsif only_plugins installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_plugins else installer = Gem::Installer.at(gem, installer_options) installer.install end say "Restored #{spec.full_name_with_location}" end end end PK!U+commands/environment_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::EnvironmentCommand < Gem::Command def initialize super "environment", "Display information about the RubyGems environment" end def arguments # :nodoc: args = <<-EOF home display the path where gems are installed. Aliases: gemhome, gemdir, GEM_HOME path display path used to search for gems. Aliases: gempath, GEM_PATH user_gemhome display the path where gems are installed when `--user-install` is given. Aliases: user_gemdir version display the gem format version remotesources display the remote gem servers platform display the supported gem platforms credentials display the path where credentials are stored display everything EOF args.gsub(/^\s+/, "") end def description # :nodoc: <<-EOF The environment command lets you query rubygems for its configuration for use in shell scripts or as a debugging aid. The RubyGems environment can be controlled through command line arguments, gemrc files, environment variables and built-in defaults. Command line argument defaults and some RubyGems defaults can be set in a ~/.gemrc file for individual users and a gemrc in the SYSTEM CONFIGURATION DIRECTORY for all users. These files are YAML files with the following YAML keys: :sources: A YAML array of remote gem repositories to install gems from :verbose: Verbosity of the gem command. false, true, and :really are the levels :update_sources: Enable/disable automatic updating of repository metadata :backtrace: Print backtrace when RubyGems encounters an error :gempath: The paths in which to look for gems :disable_default_gem_server: Force specification of gem server host on push : A string containing arguments for the specified gem command Example: :verbose: false install: --no-wrappers update: --no-wrappers :disable_default_gem_server: true RubyGems' default local repository can be overridden with the GEM_PATH and GEM_HOME environment variables. GEM_HOME sets the default repository to install into. GEM_PATH allows multiple local repositories to be searched for gems. If you are behind a proxy server, RubyGems uses the HTTP_PROXY, HTTP_PROXY_USER and HTTP_PROXY_PASS environment variables to discover the proxy server. If you would like to push gems to a private gem server the RUBYGEMS_HOST environment variable can be set to the URI for that server. If you are packaging RubyGems all of RubyGems' defaults are in lib/rubygems/defaults.rb. You may override these in lib/rubygems/defaults/operating_system.rb EOF end def usage # :nodoc: "#{program_name} [arg]" end def execute out = String.new arg = options[:args][0] out << case arg when /^version/ then Gem::VERSION when /^gemdir/, /^gemhome/, /^home/, /^GEM_HOME/ then Gem.dir when /^gempath/, /^path/, /^GEM_PATH/ then Gem.path.join(File::PATH_SEPARATOR) when /^user_gemdir/, /^user_gemhome/ then Gem.user_dir when /^remotesources/ then Gem.sources.to_a.join("\n") when /^platform/ then Gem.platforms.join(File::PATH_SEPARATOR) when /^credentials/, /^creds/ then Gem.configuration.credentials_path when nil then show_environment else raise Gem::CommandLineError, "Unknown environment option [#{arg}]" end say out true end def add_path(out, path) path.each do |component| out << " - #{component}\n" end end def show_environment # :nodoc: out = "RubyGems Environment:\n".dup out << " - RUBYGEMS VERSION: #{Gem::VERSION}\n" out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]\n" out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n" out << " - USER INSTALLATION DIRECTORY: #{Gem.user_dir}\n" out << " - CREDENTIALS FILE: #{Gem.configuration.credentials_path}\n" out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil? out << " - RUBY EXECUTABLE: #{Gem.ruby}\n" out << " - GIT EXECUTABLE: #{git_path}\n" out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n" out << " - SPEC CACHE DIRECTORY: #{Gem.spec_cache_dir}\n" out << " - SYSTEM CONFIGURATION DIRECTORY: #{Gem::ConfigFile::SYSTEM_CONFIG_PATH}\n" out << " - RUBYGEMS PLATFORMS:\n" Gem.platforms.each do |platform| out << " - #{platform}\n" end out << " - GEM PATHS:\n" out << " - #{Gem.dir}\n" gem_path = Gem.path.dup gem_path.delete Gem.dir add_path out, gem_path out << " - GEM CONFIGURATION:\n" Gem.configuration.each do |name, value| value = value.gsub(/./, "*") if name == "gemcutter_key" out << " - #{name.inspect} => #{value.inspect}\n" end out << " - REMOTE SOURCES:\n" Gem.sources.each do |s| out << " - #{s}\n" end out << " - SHELL PATH:\n" shell_path = ENV["PATH"].split(File::PATH_SEPARATOR) add_path out, shell_path out end private ## # Git binary path def git_path exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""] ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| exts.each do |ext| exe = File.join(path, "git#{ext}") return exe if File.executable?(exe) && !File.directory?(exe) end end nil end end PK!LTbcommands/signin_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../gemcutter_utilities" class Gem::Commands::SigninCommand < Gem::Command include Gem::GemcutterUtilities def initialize super "signin", "Sign in to any gemcutter-compatible host. "\ "It defaults to https://rubygems.org" add_option("--host HOST", "Push to another gemcutter-compatible host") do |value, options| options[:host] = value end add_otp_option end def description # :nodoc: "The signin command executes host sign in for a push server (the default is"\ " https://rubygems.org). The host can be provided with the host flag or can"\ " be inferred from the provided gem. Host resolution matches the resolution"\ " strategy for the push command." end def usage # :nodoc: program_name end def execute sign_in options[:host] end end PK!##commands/query_command.rbnu[require 'rubygems/command' require 'rubygems/local_remote_options' require 'rubygems/spec_fetcher' require 'rubygems/version_option' require 'rubygems/text' class Gem::Commands::QueryCommand < Gem::Command include Gem::Text include Gem::LocalRemoteOptions include Gem::VersionOption def initialize(name = 'query', summary = 'Query gem information in local or remote repositories') super name, summary, :name => //, :domain => :local, :details => false, :versions => true, :installed => nil, :version => Gem::Requirement.default add_option('-i', '--[no-]installed', 'Check for installed gem') do |value, options| options[:installed] = value end add_option('-I', 'Equivalent to --no-installed') do |value, options| options[:installed] = false end add_version_option command, "for use with --installed" add_option('-n', '--name-matches REGEXP', 'Name of gem(s) to query on matches the', 'provided REGEXP') do |value, options| options[:name] = /#{value}/i end add_option('-d', '--[no-]details', 'Display detailed information of gem(s)') do |value, options| options[:details] = value end add_option( '--[no-]versions', 'Display only gem names') do |value, options| options[:versions] = value options[:details] = false unless value end add_option('-a', '--all', 'Display all gem versions') do |value, options| options[:all] = value end add_option( '--[no-]prerelease', 'Display prerelease versions') do |value, options| options[:prerelease] = value end add_local_remote_options end def defaults_str # :nodoc: "--local --name-matches // --no-details --versions --no-installed" end def description # :nodoc: <<-EOF The query command is the basis for the list and search commands. You should really use the list and search commands instead. This command is too hard to use. EOF end def execute exit_code = 0 if options[:args].to_a.empty? and options[:name].source.empty? name = options[:name] no_name = true elsif !options[:name].source.empty? name = Array(options[:name]) else name = options[:args].to_a.map{|arg| /#{arg}/i } end prerelease = options[:prerelease] unless options[:installed].nil? then if no_name then alert_error "You must specify a gem name" exit_code |= 4 elsif name.count > 1 alert_error "You must specify only ONE gem!" exit_code |= 4 else installed = installed? name.first, options[:version] installed = !installed unless options[:installed] if installed then say "true" else say "false" exit_code |= 1 end end terminate_interaction exit_code end names = Array(name) names.each { |n| show_gems n, prerelease } end private def display_header type if (ui.outs.tty? and Gem.configuration.verbose) or both? then say say "*** #{type} GEMS ***" say end end #Guts of original execute def show_gems name, prerelease req = Gem::Requirement.default # TODO: deprecate for real dep = Gem::Deprecate.skip_during { Gem::Dependency.new name, req } dep.prerelease = prerelease if local? then if prerelease and not both? then alert_warning "prereleases are always shown locally" end display_header 'LOCAL' specs = Gem::Specification.find_all { |s| s.name =~ name and req =~ s.version } spec_tuples = specs.map do |spec| [spec.name_tuple, spec] end output_query_results spec_tuples end if remote? then display_header 'REMOTE' fetcher = Gem::SpecFetcher.fetcher type = if options[:all] if options[:prerelease] :complete else :released end elsif options[:prerelease] :prerelease else :latest end if name.source.empty? spec_tuples = fetcher.detect(type) { true } else spec_tuples = fetcher.detect(type) do |name_tuple| name === name_tuple.name end end output_query_results spec_tuples end end ## # Check if gem +name+ version +version+ is installed. def installed?(name, req = Gem::Requirement.default) Gem::Specification.any? { |s| s.name =~ name and req =~ s.version } end def output_query_results(spec_tuples) output = [] versions = Hash.new { |h,name| h[name] = [] } spec_tuples.each do |spec_tuple, source| versions[spec_tuple.name] << [spec_tuple, source] end versions = versions.sort_by do |(n,_),_| n.downcase end output_versions output, versions say output.join(options[:details] ? "\n\n" : "\n") end def output_versions output, versions versions.each do |gem_name, matching_tuples| matching_tuples = matching_tuples.sort_by { |n,_| n.version }.reverse platforms = Hash.new { |h,version| h[version] = [] } matching_tuples.each do |n, _| platforms[n.version] << n.platform if n.platform end seen = {} matching_tuples.delete_if do |n,_| if seen[n.version] then true else seen[n.version] = true false end end output << clean_text(make_entry(matching_tuples, platforms)) end end def entry_details entry, detail_tuple, specs, platforms return unless options[:details] name_tuple, spec = detail_tuple spec = spec.fetch_spec name_tuple unless Gem::Specification === spec entry << "\n" spec_platforms entry, platforms spec_authors entry, spec spec_homepage entry, spec spec_license entry, spec spec_loaded_from entry, spec, specs spec_summary entry, spec end def entry_versions entry, name_tuples, platforms return unless options[:versions] list = if platforms.empty? or options[:details] then name_tuples.map { |n| n.version }.uniq else platforms.sort.reverse.map do |version, pls| if pls == [Gem::Platform::RUBY] then version else ruby = pls.delete Gem::Platform::RUBY platform_list = [ruby, *pls.sort].compact "#{version} #{platform_list.join ' '}" end end end entry << " (#{list.join ', '})" end def make_entry entry_tuples, platforms detail_tuple = entry_tuples.first name_tuples, specs = entry_tuples.flatten.partition do |item| Gem::NameTuple === item end entry = [name_tuples.first.name] entry_versions entry, name_tuples, platforms entry_details entry, detail_tuple, specs, platforms entry.join end def spec_authors entry, spec authors = "Author#{spec.authors.length > 1 ? 's' : ''}: " authors << spec.authors.join(', ') entry << format_text(authors, 68, 4) end def spec_homepage entry, spec return if spec.homepage.nil? or spec.homepage.empty? entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4) end def spec_license entry, spec return if spec.license.nil? or spec.license.empty? licenses = "License#{spec.licenses.length > 1 ? 's' : ''}: " licenses << spec.licenses.join(', ') entry << "\n" << format_text(licenses, 68, 4) end def spec_loaded_from entry, spec, specs return unless spec.loaded_from if specs.length == 1 then default = spec.default_gem? ? ' (default)' : nil entry << "\n" << " Installed at#{default}: #{spec.base_dir}" else label = 'Installed at' specs.each do |s| version = s.version.to_s version << ', default' if s.default_gem? entry << "\n" << " #{label} (#{version}): #{s.base_dir}" label = ' ' * label.length end end end def spec_platforms entry, platforms non_ruby = platforms.any? do |_, pls| pls.any? { |pl| pl != Gem::Platform::RUBY } end return unless non_ruby if platforms.length == 1 then title = platforms.values.length == 1 ? 'Platform' : 'Platforms' entry << " #{title}: #{platforms.values.sort.join ', '}\n" else entry << " Platforms:\n" platforms.sort_by do |version,| version end.each do |version, pls| label = " #{version}: " data = format_text pls.sort.join(', '), 68, label.length data[0, label.length] = label entry << data << "\n" end end end def spec_summary entry, spec summary = truncate_text(spec.summary, "the summary for #{spec.full_name}") entry << "\n\n" << format_text(summary, 68, 4) end end PK!gpY !commands/specification_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" require_relative "../package" class Gem::Commands::SpecificationCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize Gem.load_yaml super "specification", "Display gem specification (in yaml)", domain: :local, version: Gem::Requirement.default, format: :yaml add_version_option("examine") add_platform_option add_prerelease_option add_option("--all", "Output specifications for all versions of", "the gem") do |_value, options| options[:all] = true end add_option("--ruby", "Output ruby format") do |_value, options| options[:format] = :ruby end add_option("--yaml", "Output YAML format") do |_value, options| options[:format] = :yaml end add_option("--marshal", "Output Marshal format") do |_value, options| options[:format] = :marshal end add_local_remote_options end def arguments # :nodoc: <<-ARGS GEMFILE name of gem to show the gemspec for FIELD name of gemspec field to show ARGS end def defaults_str # :nodoc: "--local --version '#{Gem::Requirement.default}' --yaml" end def description # :nodoc: <<-EOF The specification command allows you to extract the specification from a gem for examination. The specification can be output in YAML, ruby or Marshal formats. Specific fields in the specification can be extracted in YAML format: $ gem spec rake summary --- Ruby based make-like utility. ... EOF end def usage # :nodoc: "#{program_name} [GEMFILE] [FIELD]" end def execute specs = [] gem = options[:args].shift unless gem raise Gem::CommandLineError, "Please specify a gem name or file on the command line" end case v = options[:version] when String req = Gem::Requirement.create v when Gem::Requirement req = v else raise Gem::CommandLineError, "Unsupported version type: '#{v}'" end if !req.none? && options[:all] alert_error "Specify --all or -v, not both" terminate_interaction 1 end if options[:all] dep = Gem::Dependency.new gem else dep = Gem::Dependency.new gem, req end field = get_one_optional_argument raise Gem::CommandLineError, "--ruby and FIELD are mutually exclusive" if field && options[:format] == :ruby if local? if File.exist? gem begin specs << Gem::Package.new(gem).spec rescue StandardError nil end end if specs.empty? specs.push(*dep.matching_specs) end end if remote? dep.prerelease = options[:prerelease] found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep specs.push(*found.map {|spec,| spec }) end if specs.empty? alert_error "No gem matching '#{dep}' found" terminate_interaction 1 end platform = get_platform_from_requirements(options) if platform specs = specs.select {|s| s.platform.to_s == platform } end unless options[:all] specs = [specs.max_by(&:version)] end specs.each do |s| s = s.send field if field say case options[:format] when :ruby then s.to_ruby when :marshal then Marshal.dump s else s.to_yaml end say "\n" end end end PK!]g commands/owner_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" require_relative "../text" class Gem::Commands::OwnerCommand < Gem::Command include Gem::Text include Gem::LocalRemoteOptions include Gem::GemcutterUtilities def description # :nodoc: <<-EOF The owner command lets you add and remove owners of a gem on a push server (the default is https://rubygems.org). Multiple owners can be added or removed at the same time, if the flag is given multiple times. The supported user identifiers are dependent on the push server. For rubygems.org, both e-mail and handle are supported, even though the user identifier field is called "email". The owner of a gem has the permission to push new versions, yank existing versions or edit the HTML page of the gem. Be careful of who you give push permission to. EOF end def arguments # :nodoc: "GEM gem to manage owners for" end def usage # :nodoc: "#{program_name} GEM" end def initialize super "owner", "Manage gem owners of a gem on the push server" add_proxy_option add_key_option add_otp_option defaults.merge! add: [], remove: [] add_option "-a", "--add NEW_OWNER", "Add an owner by user identifier" do |value, options| options[:add] << value end add_option "-r", "--remove OLD_OWNER", "Remove an owner by user identifier" do |value, options| options[:remove] << value end add_option "-h", "--host HOST", "Use another gemcutter-compatible host", " (e.g. https://rubygems.org)" do |value, options| options[:host] = value end end def execute @host = options[:host] sign_in(scope: get_owner_scope) name = get_one_gem_name add_owners name, options[:add] remove_owners name, options[:remove] show_owners name end def show_owners(name) Gem.load_yaml response = rubygems_api_request :get, "api/v1/gems/#{name}/owners.yaml" do |request| request.add_field "Authorization", api_key end with_response response do |resp| owners = Gem::SafeYAML.load clean_text(resp.body) say "Owners for gem: #{name}" owners.each do |owner| identifier = owner["email"] || owner["handle"] || owner["id"] say "- #{identifier} (#{owner["role"]})" end end end def add_owners(name, owners) manage_owners :post, name, owners end def remove_owners(name, owners) manage_owners :delete, name, owners end def manage_owners(method, name, owners) owners.each do |owner| response = send_owner_request(method, name, owner) action = method == :delete ? "Removing" : "Adding" with_response response, "#{action} #{owner}" rescue Gem::WebauthnVerificationError => e raise e rescue StandardError # ignore early exits to allow for completing the iteration of all owners end end private def send_owner_request(method, name, owner) rubygems_api_request method, "api/v1/gems/#{name}/owners", scope: get_owner_scope(method: method) do |request| request.set_form_data "email" => owner request.add_field "Authorization", api_key end end def get_owner_scope(method: nil) if method == :post || options.any? && options[:add].any? :add_owner elsif method == :delete || options.any? && options[:remove].any? :remove_owner end end end PK!ZAQ commands/search_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../query_utils" class Gem::Commands::SearchCommand < Gem::Command include Gem::QueryUtils def initialize super "search", "Display remote gems whose name matches REGEXP", domain: :remote, details: false, versions: true, installed: nil, version: Gem::Requirement.default add_query_options end def arguments # :nodoc: "REGEXP regexp to search for in gem name" end def defaults_str # :nodoc: "--remote --no-details" end def description # :nodoc: <<-EOF The search command displays remote gems whose name matches the given regexp. The --details option displays additional details from the gem but will take a little longer to complete as it must download the information individually from the index. To list local gems use the list command. EOF end def usage # :nodoc: "#{program_name} [REGEXP]" end end PK!])[[commands/which_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::WhichCommand < Gem::Command def initialize super "which", "Find the location of a library file you can require", search_gems_first: false, show_all: false add_option "-a", "--[no-]all", "show all matching files" do |show_all, options| options[:show_all] = show_all end add_option "-g", "--[no-]gems-first", "search gems before non-gems" do |gems_first, options| options[:search_gems_first] = gems_first end end def arguments # :nodoc: "FILE name of file to find" end def defaults_str # :nodoc: "--no-gems-first --no-all" end def description # :nodoc: <<-EOF The which command is like the shell which command and shows you where the file you wish to require lives. You can use the which command to help determine why you are requiring a version you did not expect or to look at the content of a file you are requiring to see why it does not behave as you expect. EOF end def execute found = true options[:args].each do |arg| arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, "") dirs = $LOAD_PATH spec = Gem::Specification.find_by_path arg if spec if options[:search_gems_first] dirs = spec.full_require_paths + $LOAD_PATH else dirs = $LOAD_PATH + spec.full_require_paths end end paths = find_paths arg, dirs if paths.empty? alert_error "Can't find Ruby library file or shared library #{arg}" found = false else say paths end end terminate_interaction 1 unless found end def find_paths(package_name, dirs) result = [] dirs.each do |dir| Gem.suffixes.each do |ext| full_path = File.join dir, "#{package_name}#{ext}" if File.exist?(full_path) && !File.directory?(full_path) result << full_path return result unless options[:show_all] end end end result end def usage # :nodoc: "#{program_name} FILE [FILE ...]" end end PK!ؿd d commands/yank_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" require_relative "../gemcutter_utilities" class Gem::Commands::YankCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption include Gem::GemcutterUtilities def description # :nodoc: <<-EOF The yank command permanently removes a gem you pushed to a server. Once you have pushed a gem several downloads will happen automatically via the webhooks. If you accidentally pushed passwords or other sensitive data you will need to change them immediately and yank your gem. EOF end def arguments # :nodoc: "GEM name of gem" end def usage # :nodoc: "#{program_name} -v VERSION [-p PLATFORM] [--key KEY_NAME] [--host HOST] GEM" end def initialize super "yank", "Remove a pushed gem from the index" add_version_option("remove") add_platform_option("remove") add_otp_option add_option("--host HOST", "Yank from another gemcutter-compatible host", " (e.g. https://rubygems.org)") do |value, options| options[:host] = value end add_key_option @host = nil end def execute @host = options[:host] sign_in @host, scope: get_yank_scope version = get_version_from_requirements(options[:version]) platform = get_platform_from_requirements(options) if version yank_gem(version, platform) else say "A version argument is required: #{usage}" terminate_interaction end end def yank_gem(version, platform) say "Yanking gem from #{host}..." args = [:delete, version, platform, "api/v1/gems/yank"] response = yank_api_request(*args) say response.body end private def yank_api_request(method, version, platform, api) name = get_one_gem_name response = rubygems_api_request(method, api, host, scope: get_yank_scope) do |request| request.add_field("Authorization", api_key) data = { "gem_name" => name, "version" => version, } data["platform"] = platform if platform request.set_form_data data end response end def get_version_from_requirements(requirements) requirements.requirements.first[1].version rescue StandardError nil end def get_yank_scope :yank_rubygem end end PK!Q BBcommands/info_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../query_utils" class Gem::Commands::InfoCommand < Gem::Command include Gem::QueryUtils def initialize super "info", "Show information for the given gem", name: //, domain: :local, details: false, versions: true, installed: nil, version: Gem::Requirement.default add_query_options remove_option("-d") defaults[:details] = true defaults[:exact] = true end def description # :nodoc: "Info prints information about the gem such as name,"\ " description, website, license and installed paths" end def usage # :nodoc: "#{program_name} GEMNAME" end def arguments # :nodoc: "GEMNAME name of the gem to print information about" end def defaults_str "--local" end end PK!A commands/lock_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::LockCommand < Gem::Command def initialize super "lock", "Generate a lockdown list of gems", strict: false add_option "-s", "--[no-]strict", "fail if unable to satisfy a dependency" do |strict, options| options[:strict] = strict end end def arguments # :nodoc: "GEMNAME name of gem to lock\nVERSION version of gem to lock" end def defaults_str # :nodoc: "--no-strict" end def description # :nodoc: <<-EOF The lock command will generate a list of +gem+ statements that will lock down the versions for the gem given in the command line. It will specify exact versions in the requirements list to ensure that the gems loaded will always be consistent. A full recursive search of all effected gems will be generated. Example: gem lock rails-1.0.0 > lockdown.rb will produce in lockdown.rb: require "rubygems" gem 'rails', '= 1.0.0' gem 'rake', '= 0.7.0.1' gem 'activesupport', '= 1.2.5' gem 'activerecord', '= 1.13.2' gem 'actionpack', '= 1.11.2' gem 'actionmailer', '= 1.1.5' gem 'actionwebservice', '= 1.0.0' Just load lockdown.rb from your application to ensure that the current versions are loaded. Make sure that lockdown.rb is loaded *before* any other require statements. Notice that rails 1.0.0 only requires that rake 0.6.2 or better be used. Rake-0.7.0.1 is the most recent version installed that satisfies that, so we lock it down to the exact version. EOF end def usage # :nodoc: "#{program_name} GEMNAME-VERSION [GEMNAME-VERSION ...]" end def complain(message) if options[:strict] raise Gem::Exception, message else say "# #{message}" end end def execute say "require 'rubygems'" locked = {} pending = options[:args] until pending.empty? do full_name = pending.shift spec = Gem::Specification.load spec_path(full_name) if spec.nil? complain "Could not find gem #{full_name}, try using the full name" next end say "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name] locked[spec.name] = true spec.runtime_dependencies.each do |dep| next if locked[dep.name] candidates = dep.matching_specs if candidates.empty? complain "Unable to satisfy '#{dep}' from currently installed gems" else pending << candidates.last.full_name end end end end def spec_path(gem_full_name) gemspecs = Gem.path.map do |path| File.join path, "specifications", "#{gem_full_name}.gemspec" end gemspecs.find {|path| File.exist? path } end end PK! U1commands/signout_command.rbnu[# frozen_string_literal: true require_relative "../command" class Gem::Commands::SignoutCommand < Gem::Command def initialize super "signout", "Sign out from all the current sessions." end def description # :nodoc: "The `signout` command is used to sign out from all current sessions,"\ " allowing you to sign in using a different set of credentials." end def usage # :nodoc: program_name end def execute credentials_path = Gem.configuration.credentials_path if !File.exist?(credentials_path) alert_error "You are not currently signed in." elsif !File.writable?(credentials_path) alert_error "File '#{Gem.configuration.credentials_path}' is read-only."\ " Please make sure it is writable." else Gem.configuration.unset_api_key! say "You have successfully signed out from all sessions." end end end PK!Lcommands/list_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../query_utils" ## # Searches for gems starting with the supplied argument. class Gem::Commands::ListCommand < Gem::Command include Gem::QueryUtils def initialize super "list", "Display local gems whose name matches REGEXP", domain: :local, details: false, versions: true, installed: nil, version: Gem::Requirement.default add_query_options end def arguments # :nodoc: "REGEXP regexp to look for in gem name" end def defaults_str # :nodoc: "--local --no-details" end def description # :nodoc: <<-EOF The list command is used to view the gems you have installed locally. The --details option displays additional details including the summary, the homepage, the author, the locations of different versions of the gem. To search for remote gems use the search command. EOF end def usage # :nodoc: "#{program_name} [REGEXP ...]" end end PK!d"commands/generate_index_command.rbnu[# frozen_string_literal: true require_relative "../command" unless defined? Gem::Commands::GenerateIndexCommand class Gem::Commands::GenerateIndexCommand < Gem::Command module RubygemsTrampoline def description # :nodoc: <<~EOF The generate_index command has been moved to the rubygems-generate_index gem. EOF end def execute alert_error "Install the rubygems-generate_index gem for the generate_index command" end def invoke_with_build_args(args, build_args) name = "rubygems-generate_index" spec = begin Gem::Specification.find_by_name(name) rescue Gem::LoadError require "rubygems/dependency_installer" Gem.install(name, Gem::Requirement.default, Gem::DependencyInstaller::DEFAULT_OPTIONS).find {|s| s.name == name } end # remove the methods defined in this file so that the methods defined in the gem are used instead, # and without a method redefinition warning %w[description execute invoke_with_build_args].each do |method| RubygemsTrampoline.remove_method(method) end self.class.singleton_class.remove_method(:new) spec.activate Gem.load_plugin_files spec.matches_for_glob("rubygems_plugin#{Gem.suffix_pattern}") self.class.new.invoke_with_build_args(args, build_args) end end private_constant :RubygemsTrampoline # remove_method(:initialize) warns, but removing new does not warn def self.new command = allocate command.send(:initialize, "generate_index", "Generates the index files for a gem server directory (requires rubygems-generate_index)") command end prepend(RubygemsTrampoline) end end PK!sscommands/mirror_command.rbnu[# frozen_string_literal: true require_relative "../command" unless defined? Gem::Commands::MirrorCommand class Gem::Commands::MirrorCommand < Gem::Command def initialize super("mirror", "Mirror all gem files (requires rubygems-mirror)") begin Gem::Specification.find_by_name("rubygems-mirror").activate rescue Gem::LoadError # no-op end end def description # :nodoc: <<-EOF The mirror command has been moved to the rubygems-mirror gem. EOF end def execute alert_error "Install the rubygems-mirror gem for the mirror command" end end end PK!V- - commands/check_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" require_relative "../validator" require_relative "../doctor" class Gem::Commands::CheckCommand < Gem::Command include Gem::VersionOption def initialize super "check", "Check a gem repository for added or missing files", alien: true, doctor: false, dry_run: false, gems: true add_option("-a", "--[no-]alien", 'Report "unmanaged" or rogue files in the', "gem repository") do |value, options| options[:alien] = value end add_option("--[no-]doctor", "Clean up uninstalled gems and broken", "specifications") do |value, options| options[:doctor] = value end add_option("--[no-]dry-run", "Do not remove files, only report what", "would be removed") do |value, options| options[:dry_run] = value end add_option("--[no-]gems", "Check installed gems for problems") do |value, options| options[:gems] = value end add_version_option "check" end def check_gems say "Checking gems..." say gems = begin get_all_gem_names rescue StandardError [] end Gem::Validator.new.alien(gems).sort.each do |key, val| if val.empty? say "#{key} is error-free" if Gem.configuration.verbose else say "#{key} has #{val.size} problems" val.each do |error_entry| say " #{error_entry.path}:" say " #{error_entry.problem}" end end say end end def doctor say "Checking for files from uninstalled gems..." say Gem.path.each do |gem_repo| doctor = Gem::Doctor.new gem_repo, options[:dry_run] doctor.doctor end end def execute check_gems if options[:gems] doctor if options[:doctor] end def arguments # :nodoc: "GEMNAME name of gem to check" end def defaults_str # :nodoc: "--gems --alien" end def description # :nodoc: <<-EOF The check command can list and repair problems with installed gems and specifications and will clean up gems that have been partially uninstalled. EOF end def usage # :nodoc: "#{program_name} [OPTIONS] [GEMNAME ...]" end end PK!4``commands/outdated_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../spec_fetcher" require_relative "../version_option" class Gem::Commands::OutdatedCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize super "outdated", "Display all gems that need updates" add_local_remote_options add_platform_option end def description # :nodoc: <<-EOF The outdated command lists gems you may wish to upgrade to a newer version. You can check for dependency mismatches using the dependency command and update the gems with the update or install commands. EOF end def execute Gem::Specification.outdated_and_latest_version.each do |spec, remote_version| say "#{spec.name} (#{spec.version} < #{remote_version})" end end end PK! commands/rebuild_command.rbnu[# frozen_string_literal: true require "digest" require "fileutils" require "tmpdir" require_relative "../gemspec_helpers" require_relative "../package" class Gem::Commands::RebuildCommand < Gem::Command include Gem::GemspecHelpers def initialize super "rebuild", "Attempt to reproduce a build of a gem." add_option "--diff", "If the files don't match, compare them using diffoscope." do |_value, options| options[:diff] = true end add_option "--force", "Skip validation of the spec." do |_value, options| options[:force] = true end add_option "--strict", "Consider warnings as errors when validating the spec." do |_value, options| options[:strict] = true end add_option "--source GEM_SOURCE", "Specify the source to download the gem from." do |value, options| options[:source] = value end add_option "--original GEM_FILE", "Specify a local file to compare against (instead of downloading it)." do |value, options| options[:original_gem_file] = value end add_option "--gemspec GEMSPEC_FILE", "Specify the name of the gemspec file." do |value, options| options[:gemspec_file] = value end add_option "-C PATH", "Run as if gem build was started in instead of the current working directory." do |value, options| options[:build_path] = value end end def arguments # :nodoc: "GEM_NAME gem name on gem server\n" \ "GEM_VERSION gem version you are attempting to rebuild" end def description # :nodoc: <<-EOF The rebuild command allows you to (attempt to) reproduce a build of a gem from a ruby gemspec. This command assumes the gemspec can be built with the `gem build` command. If you use any of `gem build`, `rake build`, or`rake release` in the build/release process for a gem, it is a potential candidate. You will need to match the RubyGems version used, since this is included in the Gem metadata. If the gem includes lockfiles (e.g. Gemfile.lock) and similar, it will require more effort to reproduce a build. For example, it might require more precisely matched versions of Ruby and/or Bundler to be used. EOF end def usage # :nodoc: "#{program_name} GEM_NAME GEM_VERSION" end def execute gem_name, gem_version = get_gem_name_and_version old_dir, new_dir = prep_dirs gem_filename = "#{gem_name}-#{gem_version}.gem" old_file = File.join(old_dir, gem_filename) new_file = File.join(new_dir, gem_filename) if options[:original_gem_file] FileUtils.copy_file(options[:original_gem_file], old_file) else download_gem(gem_name, gem_version, old_file) end rg_version = rubygems_version(old_file) unless rg_version == Gem::VERSION alert_error <<-EOF You need to use the same RubyGems version #{gem_name} v#{gem_version} was built with. #{gem_name} v#{gem_version} was built using RubyGems v#{rg_version}. Gem files include the version of RubyGems used to build them. This means in order to reproduce #{gem_filename}, you must also use RubyGems v#{rg_version}. You're using RubyGems v#{Gem::VERSION}. Please install RubyGems v#{rg_version} and try again. EOF terminate_interaction 1 end source_date_epoch = get_timestamp(old_file).to_s if build_path = options[:build_path] Dir.chdir(build_path) { build_gem(gem_name, source_date_epoch, new_file) } else build_gem(gem_name, source_date_epoch, new_file) end compare(source_date_epoch, old_file, new_file) end private def sha256(file) Digest::SHA256.hexdigest(Gem.read_binary(file)) end def get_timestamp(file) mtime = nil File.open(file, Gem.binary_mode) do |f| Gem::Package::TarReader.new(f) do |tar| mtime = tar.seek("metadata.gz") {|tf| tf.header.mtime } end end mtime end def compare(source_date_epoch, old_file, new_file) date = Time.at(source_date_epoch.to_i).strftime("%F %T %Z") old_hash = sha256(old_file) new_hash = sha256(new_file) say say "Built at: #{date} (#{source_date_epoch})" say "Original build saved to: #{old_file}" say "Reproduced build saved to: #{new_file}" say "Working directory: #{options[:build_path] || Dir.pwd}" say say "Hash comparison:" say " #{old_hash}\t#{old_file}" say " #{new_hash}\t#{new_file}" say if old_hash == new_hash say "SUCCESS - original and rebuild hashes matched" else say "FAILURE - original and rebuild hashes did not match" say if options[:diff] if system("diffoscope", old_file, new_file).nil? alert_error "error: could not find `diffoscope` executable" end else say "Pass --diff for more details (requires diffoscope to be installed)." end terminate_interaction 1 end end def prep_dirs rebuild_dir = Dir.mktmpdir("gem_rebuild") old_dir = File.join(rebuild_dir, "old") new_dir = File.join(rebuild_dir, "new") FileUtils.mkdir_p(old_dir) FileUtils.mkdir_p(new_dir) [old_dir, new_dir] end def get_gem_name_and_version args = options[:args] || [] if args.length == 2 gem_name, gem_version = args elsif args.length > 2 raise Gem::CommandLineError, "Too many arguments" else raise Gem::CommandLineError, "Expected GEM_NAME and GEM_VERSION arguments (gem rebuild GEM_NAME GEM_VERSION)" end [gem_name, gem_version] end def build_gem(gem_name, source_date_epoch, output_file) gemspec = options[:gemspec_file] || find_gemspec("#{gem_name}.gemspec") if gemspec build_package(gemspec, source_date_epoch, output_file) else alert_error error_message(gem_name) terminate_interaction(1) end end def build_package(gemspec, source_date_epoch, output_file) with_source_date_epoch(source_date_epoch) do spec = Gem::Specification.load(gemspec) if spec Gem::Package.build( spec, options[:force], options[:strict], output_file ) else alert_error "Error loading gemspec. Aborting." terminate_interaction 1 end end end def with_source_date_epoch(source_date_epoch) old_sde = ENV["SOURCE_DATE_EPOCH"] ENV["SOURCE_DATE_EPOCH"] = source_date_epoch.to_s yield ensure ENV["SOURCE_DATE_EPOCH"] = old_sde end def error_message(gem_name) if gem_name "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}" else "Couldn't find a gemspec file in #{Dir.pwd}" end end def download_gem(gem_name, gem_version, old_file) # This code was based loosely off the `gem fetch` command. version = "= #{gem_version}" dep = Gem::Dependency.new gem_name, version specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep # There should never be more than one item in specs_and_sources, # since we search for an exact version. spec, source = specs_and_sources[0] if spec.nil? show_lookup_failure gem_name, version, errors, options[:domain] terminate_interaction 1 end download_path = source.download spec FileUtils.move(download_path, old_file) say "Downloaded #{gem_name} version #{gem_version} as #{old_file}." end def rubygems_version(gem_file) Gem::Package.new(gem_file).spec.rubygems_version end end PK! commands/exec_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../dependency_installer" require_relative "../gem_runner" require_relative "../package" require_relative "../version_option" class Gem::Commands::ExecCommand < Gem::Command include Gem::VersionOption def initialize super "exec", "Run a command from a gem", { version: Gem::Requirement.default, } add_version_option add_prerelease_option "to be installed" add_option "-g", "--gem GEM", "run the executable from the given gem" do |value, options| options[:gem_name] = value end add_option(:"Install/Update", "--conservative", "Prefer the most recent installed version, ", "rather than the latest version overall") do |_value, options| options[:conservative] = true end end def arguments # :nodoc: "COMMAND the executable command to run" end def defaults_str # :nodoc: "--version '#{Gem::Requirement.default}'" end def description # :nodoc: <<-EOF The exec command handles installing (if necessary) and running an executable from a gem, regardless of whether that gem is currently installed. The exec command can be thought of as a shortcut to running `gem install` and then the executable from the installed gem. For example, `gem exec rails new .` will run `rails new .` in the current directory, without having to manually run `gem install rails`. Additionally, the exec command ensures the most recent version of the gem is used (unless run with `--conservative`), and that the gem is not installed to the same gem path as user-installed gems. EOF end def usage # :nodoc: "#{program_name} [options --] COMMAND [args]" end def execute check_executable print_command if options[:gem_name] == "gem" && options[:executable] == "gem" set_gem_exec_install_paths Gem::GemRunner.new.run options[:args] return elsif options[:conservative] install_if_needed else install activate! end load! end private def handle_options(args) args = add_extra_args(args) check_deprecated_options(args) @options = Marshal.load Marshal.dump @defaults # deep copy parser.order!(args) do |v| # put the non-option back at the front of the list of arguments args.unshift(v) # stop parsing once we hit the first non-option, # so you can call `gem exec rails --version` and it prints the rails # version rather than rubygem's break end @options[:args] = args options[:executable], gem_version = extract_gem_name_and_version(options[:args].shift) options[:gem_name] ||= options[:executable] if gem_version if options[:version].none? options[:version] = Gem::Requirement.new(gem_version) else options[:version].concat [gem_version] end end if options[:prerelease] && !options[:version].prerelease? if options[:version].none? options[:version] = Gem::Requirement.default_prerelease else options[:version].concat [Gem::Requirement.default_prerelease] end end end def check_executable if options[:executable].nil? raise Gem::CommandLineError, "Please specify an executable to run (e.g. #{program_name} COMMAND)" end end def print_command verbose "running #{program_name} with:\n" opts = options.reject {|_, v| v.nil? || Array(v).empty? } max_length = opts.map {|k, _| k.size }.max opts.each do |k, v| next if v.nil? verbose "\t#{k.to_s.rjust(max_length)}: #{v}" end verbose "" end def install_if_needed activate! rescue Gem::MissingSpecError verbose "#{Gem::Dependency.new(options[:gem_name], options[:version])} not available locally, installing from remote" install activate! end def set_gem_exec_install_paths home = Gem.dir ENV["GEM_PATH"] = ([home] + Gem.path).join(File::PATH_SEPARATOR) ENV["GEM_HOME"] = home Gem.clear_paths end def install set_gem_exec_install_paths gem_name = options[:gem_name] gem_version = options[:version] install_options = options.merge( minimal_deps: false, wrappers: true ) suppress_always_install do dep_installer = Gem::DependencyInstaller.new install_options request_set = dep_installer.resolve_dependencies gem_name, gem_version verbose "Gems to install:" request_set.sorted_requests.each do |activation_request| verbose "\t#{activation_request.full_name}" end request_set.install install_options end Gem::Specification.reset rescue Gem::InstallError => e alert_error "Error installing #{gem_name}:\n\t#{e.message}" terminate_interaction 1 rescue Gem::GemNotFoundException => e show_lookup_failure e.name, e.version, e.errors, false terminate_interaction 2 rescue Gem::UnsatisfiableDependencyError => e show_lookup_failure e.name, e.version, e.errors, false, "'#{gem_name}' (#{gem_version})" terminate_interaction 2 end def activate! gem(options[:gem_name], options[:version]) Gem.finish_resolve verbose "activated #{options[:gem_name]} (#{Gem.loaded_specs[options[:gem_name]].version})" end def load! argv = ARGV.clone ARGV.replace options[:args] executable = options[:executable] contains_executable = Gem.loaded_specs.values.select do |spec| spec.executables.include?(executable) end if contains_executable.any? {|s| s.name == executable } contains_executable.select! {|s| s.name == executable } end if contains_executable.empty? spec = Gem.loaded_specs[executable] if spec.nil? || spec.executables.empty? alert_error "Failed to load executable `#{executable}`," \ " are you sure the gem `#{options[:gem_name]}` contains it?" terminate_interaction 1 end if spec.executables.size > 1 alert_error "Ambiguous which executable from gem `#{executable}` should be run: " \ "the options are #{spec.executables.sort}, specify one via COMMAND, and use `-g` and `-v` to specify gem and version" terminate_interaction 1 end contains_executable << spec executable = spec.executable end if contains_executable.size > 1 alert_error "Ambiguous which gem `#{executable}` should come from: " \ "the options are #{contains_executable.map(&:name)}, " \ "specify one via `-g`" terminate_interaction 1 end old_exe = $0 $0 = executable load Gem.activate_bin_path(contains_executable.first.name, executable, ">= 0.a") ensure $0 = old_exe if old_exe ARGV.replace argv end def suppress_always_install name = :always_install cls = ::Gem::Resolver::InstallerSet method = cls.instance_method(name) cls.remove_method(name) cls.define_method(name) { [] } begin yield ensure cls.remove_method(name) cls.define_method(name, method) end end end PK!GGcommands/setup_command.rbnu[# frozen_string_literal: true require_relative "../command" ## # Installs RubyGems itself. This command is ordinarily only available from a # RubyGems checkout or tarball. class Gem::Commands::SetupCommand < Gem::Command HISTORY_HEADER = %r{^##\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} VERSION_MATCHER = %r{^##\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} ENV_PATHS = %w[/usr/bin/env /bin/env].freeze def initialize super "setup", "Install RubyGems", format_executable: false, document: %w[ri], force: true, site_or_vendor: "sitelibdir", destdir: "", prefix: "", previous_version: "", regenerate_binstubs: true, regenerate_plugins: true add_option "--previous-version=VERSION", "Previous version of RubyGems", "Used for changelog processing" do |version, options| options[:previous_version] = version end add_option "--prefix=PREFIX", "Prefix path for installing RubyGems", "Will not affect gem repository location" do |prefix, options| options[:prefix] = File.expand_path prefix end add_option "--destdir=DESTDIR", "Root directory to install RubyGems into", "Mainly used for packaging RubyGems" do |destdir, options| options[:destdir] = File.expand_path destdir end add_option "--[no-]vendor", "Install into vendorlibdir not sitelibdir" do |vendor, options| options[:site_or_vendor] = vendor ? "vendorlibdir" : "sitelibdir" end add_option "--[no-]format-executable", "Makes `gem` match ruby", "If Ruby is ruby18, gem will be gem18" do |value, options| options[:format_executable] = value end add_option "--[no-]document [TYPES]", Array, "Generate documentation for RubyGems", "List the documentation types you wish to", "generate. For example: rdoc,ri" do |value, options| options[:document] = case value when nil then %w[rdoc ri] when false then [] else value end end add_option "--[no-]rdoc", "Generate RDoc documentation for RubyGems" do |value, options| if value options[:document] << "rdoc" else options[:document].delete "rdoc" end options[:document].uniq! end add_option "--[no-]ri", "Generate RI documentation for RubyGems" do |value, options| if value options[:document] << "ri" else options[:document].delete "ri" end options[:document].uniq! end add_option "--[no-]regenerate-binstubs", "Regenerate gem binstubs" do |value, options| options[:regenerate_binstubs] = value end add_option "--[no-]regenerate-plugins", "Regenerate gem plugins" do |value, options| options[:regenerate_plugins] = value end add_option "-f", "--[no-]force", "Forcefully overwrite binstubs" do |value, options| options[:force] = value end add_option("-E", "--[no-]env-shebang", "Rewrite executables with a shebang", "of /usr/bin/env") do |value, options| options[:env_shebang] = value end @verbose = nil end def defaults_str # :nodoc: "--format-executable --document ri --regenerate-binstubs" end def description # :nodoc: <<-EOF Installs RubyGems itself. RubyGems installs RDoc for itself in GEM_HOME. By default this is: #{Gem.dir} If you prefer a different directory, set the GEM_HOME environment variable. RubyGems will install the gem command with a name matching ruby's prefix and suffix. If ruby was installed as `ruby18`, gem will be installed as `gem18`. By default, this RubyGems will install gem as: #{Gem.default_exec_format % "gem"} EOF end module MakeDirs def mkdir_p(path, **opts) super (@mkdirs ||= []) << path end end def execute @verbose = Gem.configuration.really_verbose require "fileutils" if Gem.configuration.really_verbose extend FileUtils::Verbose else extend FileUtils end extend MakeDirs lib_dir, bin_dir = make_destination_dirs man_dir = generate_default_man_dir install_lib lib_dir install_executables bin_dir remove_old_bin_files bin_dir remove_old_lib_files lib_dir # Can be removed one we drop support for bundler 2.2.3 (the last version installing man files to man_dir) remove_old_man_files man_dir if man_dir && File.exist?(man_dir) install_default_bundler_gem bin_dir if mode = options[:dir_mode] @mkdirs.uniq! File.chmod(mode, @mkdirs) end say "RubyGems #{Gem::VERSION} installed" regenerate_binstubs(bin_dir) if options[:regenerate_binstubs] regenerate_plugins(bin_dir) if options[:regenerate_plugins] uninstall_old_gemcutter documentation_success = install_rdoc say if @verbose say "-" * 78 say end if options[:previous_version].empty? options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, "0") end options[:previous_version] = Gem::Version.new(options[:previous_version]) show_release_notes say say "-" * 78 say say "RubyGems installed the following executables:" say bin_file_names.map {|name| "\t#{name}\n" } say unless bin_file_names.grep(/#{File::SEPARATOR}gem$/) say "If `gem` was installed by a previous RubyGems installation, you may need" say "to remove it by hand." say end if documentation_success if options[:document].include? "rdoc" say "Rdoc documentation was installed. You may now invoke:" say " gem server" say "and then peruse beautifully formatted documentation for your gems" say "with your web browser." say "If you do not wish to install this documentation in the future, use the" say "--no-document flag, or set it as the default in your ~/.gemrc file. See" say "'gem help env' for details." say end if options[:document].include? "ri" say "Ruby Interactive (ri) documentation was installed. ri is kind of like man " say "pages for Ruby libraries. You may access it like this:" say " ri Classname" say " ri Classname.class_method" say " ri Classname#instance_method" say "If you do not wish to install this documentation in the future, use the" say "--no-document flag, or set it as the default in your ~/.gemrc file. See" say "'gem help env' for details." say end end end def install_executables(bin_dir) prog_mode = options[:prog_mode] || 0o755 executables = { "gem" => "exe" } executables.each do |tool, path| say "Installing #{tool} executable" if @verbose Dir.chdir path do bin_file = "gem" require "tmpdir" dest_file = target_bin_path(bin_dir, bin_file) bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" begin bin = File.readlines bin_file bin[0] = shebang File.open bin_tmp_file, "w" do |fp| fp.puts bin.join end install bin_tmp_file, dest_file, mode: prog_mode bin_file_names << dest_file ensure rm bin_tmp_file end next unless Gem.win_platform? begin bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat" File.open bin_cmd_file, "w" do |file| file.puts <<-TEXT @ECHO OFF @"%~dp0#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %* TEXT end install bin_cmd_file, "#{dest_file}.bat", mode: prog_mode ensure rm bin_cmd_file end end end end def shebang if options[:env_shebang] ruby_name = RbConfig::CONFIG["ruby_install_name"] @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } "#!#{@env_path} #{ruby_name}\n" else "#!#{Gem.ruby}\n" end end def install_lib(lib_dir) libs = { "RubyGems" => "lib" } libs["Bundler"] = "bundler/lib" libs.each do |tool, path| say "Installing #{tool}" if @verbose lib_files = files_in path Dir.chdir path do install_file_list(lib_files, lib_dir) end end end def install_rdoc gem_doc_dir = File.join Gem.dir, "doc" rubygems_name = "rubygems-#{Gem::VERSION}" rubygems_doc_dir = File.join gem_doc_dir, rubygems_name begin Gem.ensure_gem_subdirectories Gem.dir rescue SystemCallError # ignore end if File.writable?(gem_doc_dir) && (!File.exist?(rubygems_doc_dir) || File.writable?(rubygems_doc_dir)) say "Removing old RubyGems RDoc and ri" if @verbose Dir[File.join(Gem.dir, "doc", "rubygems-[0-9]*")].each do |dir| rm_rf dir end require_relative "../rdoc" return false unless defined?(Gem::RDoc) fake_spec = Gem::Specification.new "rubygems", Gem::VERSION def fake_spec.full_gem_path File.expand_path "../../..", __dir__ end generate_ri = options[:document].include? "ri" generate_rdoc = options[:document].include? "rdoc" rdoc = Gem::RDoc.new fake_spec, generate_rdoc, generate_ri rdoc.generate return true elsif @verbose say "Skipping RDoc generation, #{gem_doc_dir} not writable" say "Set the GEM_HOME environment variable if you want RDoc generated" end false end def install_default_bundler_gem(bin_dir) current_default_spec = Gem::Specification.default_stubs.find {|s| s.name == "bundler" } specs_dir = if current_default_spec && default_dir == Gem.default_dir all_specs_current_version = Gem::Specification.stubs.select {|s| s.full_name == current_default_spec.full_name } Gem::Specification.remove_spec current_default_spec loaded_from = current_default_spec.loaded_from File.delete(loaded_from) # Remove previous default gem executables if they were not shadowed by a regular gem FileUtils.rm_rf current_default_spec.full_gem_path if all_specs_current_version.size == 1 File.dirname(loaded_from) else target_specs_dir = File.join(default_dir, "specifications", "default") mkdir_p target_specs_dir, mode: 0o755 target_specs_dir end new_bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") } full_name = new_bundler_spec.full_name gemspec_path = "#{full_name}.gemspec" default_spec_path = File.join(specs_dir, gemspec_path) Gem.write_binary(default_spec_path, new_bundler_spec.to_ruby) bundler_spec = Gem::Specification.load(default_spec_path) # Remove gemspec that was same version of vendored bundler. normal_gemspec = File.join(default_dir, "specifications", gemspec_path) if File.file? normal_gemspec File.delete normal_gemspec end # Remove gem files that were same version of vendored bundler. if File.directory? bundler_spec.gems_dir Dir.entries(bundler_spec.gems_dir). select {|default_gem| File.basename(default_gem) == full_name }. each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) } end require_relative "../installer" Dir.chdir("bundler") do built_gem = Gem::Package.build(new_bundler_spec) begin installer = Gem::Installer.at( built_gem, env_shebang: options[:env_shebang], format_executable: options[:format_executable], force: options[:force], bin_dir: bin_dir, install_dir: default_dir, wrappers: true ) # We need to install only executable and default spec files. # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. installer.extract_bin installer.generate_bin installer.write_default_spec ensure FileUtils.rm_f built_gem end end new_bundler_spec.executables.each {|executable| bin_file_names << target_bin_path(bin_dir, executable) } say "Bundler #{new_bundler_spec.version} installed" end def make_destination_dirs lib_dir, bin_dir = Gem.default_rubygems_dirs unless lib_dir lib_dir, bin_dir = generate_default_dirs end mkdir_p lib_dir, mode: 0o755 mkdir_p bin_dir, mode: 0o755 [lib_dir, bin_dir] end def generate_default_man_dir prefix = options[:prefix] if prefix.empty? man_dir = RbConfig::CONFIG["mandir"] return unless man_dir else man_dir = File.join prefix, "man" end prepend_destdir_if_present(man_dir) end def generate_default_dirs prefix = options[:prefix] site_or_vendor = options[:site_or_vendor] if prefix.empty? lib_dir = RbConfig::CONFIG[site_or_vendor] bin_dir = RbConfig::CONFIG["bindir"] else lib_dir = File.join prefix, "lib" bin_dir = File.join prefix, "bin" end [prepend_destdir_if_present(lib_dir), prepend_destdir_if_present(bin_dir)] end def files_in(dir) Dir.chdir dir do Dir.glob(File.join("**", "*"), File::FNM_DOTMATCH). select {|f| !File.directory?(f) } end end def remove_old_bin_files(bin_dir) old_bin_files = { "gem_mirror" => "gem mirror", "gem_server" => "gem server", "gemlock" => "gem lock", "gemri" => "ri", "gemwhich" => "gem which", "index_gem_repository.rb" => "gem generate_index", } old_bin_files.each do |old_bin_file, new_name| old_bin_path = File.join bin_dir, old_bin_file next unless File.exist? old_bin_path deprecation_message = "`#{old_bin_file}` has been deprecated. Use `#{new_name}` instead." File.open old_bin_path, "w" do |fp| fp.write <<-EOF #!#{Gem.ruby} abort "#{deprecation_message}" EOF end next unless Gem.win_platform? File.open "#{old_bin_path}.bat", "w" do |fp| fp.puts %(@ECHO.#{deprecation_message}) end end end def remove_old_lib_files(lib_dir) lib_dirs = { File.join(lib_dir, "rubygems") => "lib/rubygems" } lib_dirs[File.join(lib_dir, "bundler")] = "bundler/lib/bundler" lib_dirs.each do |old_lib_dir, new_lib_dir| lib_files = files_in(new_lib_dir) old_lib_files = files_in(old_lib_dir) to_remove = old_lib_files - lib_files gauntlet_rubygems = File.join(lib_dir, "gauntlet_rubygems.rb") to_remove << gauntlet_rubygems if File.exist? gauntlet_rubygems to_remove.delete_if do |file| file.start_with? "defaults" end remove_file_list(to_remove, old_lib_dir) end end def remove_old_man_files(old_man_dir) old_man1_dir = "#{old_man_dir}/man1" if File.exist?(old_man1_dir) man1_to_remove = Dir.chdir(old_man1_dir) { Dir["bundle*.1{,.txt,.ronn}"] } remove_file_list(man1_to_remove, old_man1_dir) end old_man5_dir = "#{old_man_dir}/man5" if File.exist?(old_man5_dir) man5_to_remove = Dir.chdir(old_man5_dir) { Dir["gemfile.5{,.txt,.ronn}"] } remove_file_list(man5_to_remove, old_man5_dir) end end def show_release_notes release_notes = File.join Dir.pwd, "CHANGELOG.md" release_notes = if File.exist? release_notes history = File.read release_notes history.force_encoding Encoding::UTF_8 text = history.split(HISTORY_HEADER) text.shift # correct an off-by-one generated by split version_lines = history.scan(HISTORY_HEADER) versions = history.scan(VERSION_MATCHER).flatten.map do |x| Gem::Version.new(x) end history_string = "" until versions.length == 0 || versions.shift <= options[:previous_version] do history_string += version_lines.shift + text.shift end history_string else "Oh-no! Unable to find release notes!" end say release_notes end def uninstall_old_gemcutter require_relative "../uninstaller" ui = Gem::Uninstaller.new("gemcutter", all: true, ignore: true, version: "< 0.4") ui.uninstall rescue Gem::InstallError end def regenerate_binstubs(bindir) require_relative "pristine_command" say "Regenerating binstubs" args = %w[--all --only-executables --silent] args << "--bindir=#{bindir}" args << "--install-dir=#{default_dir}" if options[:env_shebang] args << "--env-shebang" end command = Gem::Commands::PristineCommand.new command.invoke(*args) end def regenerate_plugins(bindir) require_relative "pristine_command" say "Regenerating plugins" args = %w[--all --only-plugins --silent] args << "--bindir=#{bindir}" args << "--install-dir=#{default_dir}" command = Gem::Commands::PristineCommand.new command.invoke(*args) end private def default_dir prefix = options[:prefix] if prefix.empty? dir = Gem.default_dir else dir = prefix end prepend_destdir_if_present(dir) end def prepend_destdir_if_present(path) destdir = options[:destdir] return path if destdir.empty? File.join(options[:destdir], path.gsub(/^[a-zA-Z]:/, "")) end def install_file_list(files, dest_dir) files.each do |file| install_file file, dest_dir end end def install_file(file, dest_dir) dest_file = File.join dest_dir, file dest_dir = File.dirname dest_file unless File.directory? dest_dir mkdir_p dest_dir, mode: 0o755 end install file, dest_file, mode: options[:data_mode] || 0o644 end def remove_file_list(files, dir) Dir.chdir dir do files.each do |file| FileUtils.rm_f file warn "unable to remove old file #{file} please remove it by hand" if File.exist? file end end end def target_bin_path(bin_dir, bin_file) bin_file_formatted = if options[:format_executable] Gem.default_exec_format % bin_file else bin_file end File.join bin_dir, bin_file_formatted end def bin_file_names @bin_file_names ||= [] end end PK!commands/server_command.rbnu[# frozen_string_literal: true require_relative "../command" unless defined? Gem::Commands::ServerCommand class Gem::Commands::ServerCommand < Gem::Command def initialize super("server", "Starts up a web server that hosts the RDoc (requires rubygems-server)") begin Gem::Specification.find_by_name("rubygems-server").activate rescue Gem::LoadError # no-op end end def description # :nodoc: <<-EOF The server command has been moved to the rubygems-server gem. EOF end def execute alert_error "Install the rubygems-server gem for the server command" end end end PK!r2!2!commands/update_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../command_manager" require_relative "../dependency_installer" require_relative "../install_update_options" require_relative "../local_remote_options" require_relative "../spec_fetcher" require_relative "../version_option" require_relative "../install_message" # must come before rdoc for messaging require_relative "../rdoc" class Gem::Commands::UpdateCommand < Gem::Command include Gem::InstallUpdateOptions include Gem::LocalRemoteOptions include Gem::VersionOption attr_reader :installer # :nodoc: attr_reader :updated # :nodoc: def initialize options = { force: false, } options.merge!(install_update_options) super "update", "Update installed gems to the latest version", options add_install_update_options Gem::OptionParser.accept Gem::Version do |value| Gem::Version.new value value end add_option("--system [VERSION]", Gem::Version, "Update the RubyGems system software") do |value, opts| value ||= true opts[:system] = value end add_local_remote_options add_platform_option add_prerelease_option "as update targets" @updated = [] @installer = nil end def arguments # :nodoc: "GEMNAME name of gem to update" end def defaults_str # :nodoc: "--no-force --install-dir #{Gem.dir}\n" + install_update_defaults_str end def description # :nodoc: <<-EOF The update command will update your gems to the latest version. The update command does not remove the previous version. Use the cleanup command to remove old versions. EOF end def usage # :nodoc: "#{program_name} GEMNAME [GEMNAME ...]" end def check_latest_rubygems(version) # :nodoc: if Gem.rubygems_version == version say "Latest version already installed. Done." terminate_interaction end end def check_oldest_rubygems(version) # :nodoc: if oldest_supported_version > version alert_error "rubygems #{version} is not supported on #{RUBY_VERSION}. The oldest version supported by this ruby is #{oldest_supported_version}" terminate_interaction 1 end end def check_update_arguments # :nodoc: unless options[:args].empty? alert_error "Gem names are not allowed with the --system option" terminate_interaction 1 end end def execute if options[:system] update_rubygems return end gems_to_update = which_to_update( highest_installed_gems, options[:args].uniq ) if options[:explain] say "Gems to update:" gems_to_update.each do |name_tuple| say " #{name_tuple.full_name}" end return end say "Updating installed gems" updated = update_gems gems_to_update installed_names = highest_installed_gems.keys updated_names = updated.map(&:name) not_updated_names = options[:args].uniq - updated_names not_installed_names = not_updated_names - installed_names up_to_date_names = not_updated_names - not_installed_names if updated.empty? say "Nothing to update" else say "Gems updated: #{updated_names.join(" ")}" end say "Gems already up-to-date: #{up_to_date_names.join(" ")}" unless up_to_date_names.empty? say "Gems not currently installed: #{not_installed_names.join(" ")}" unless not_installed_names.empty? end def fetch_remote_gems(spec) # :nodoc: dependency = Gem::Dependency.new spec.name, "> #{spec.version}" dependency.prerelease = options[:prerelease] fetcher = Gem::SpecFetcher.fetcher spec_tuples, errors = fetcher.search_for_dependency dependency error = errors.find {|e| e.respond_to? :exception } raise error if error spec_tuples end def highest_installed_gems # :nodoc: hig = {} # highest installed gems # Get only gem specifications installed as --user-install Gem::Specification.dirs = Gem.user_dir if options[:user_install] Gem::Specification.each do |spec| if hig[spec.name].nil? || hig[spec.name].version < spec.version hig[spec.name] = spec end end hig end def highest_remote_name_tuple(spec) # :nodoc: spec_tuples = fetch_remote_gems spec highest_remote_gem = spec_tuples.max return unless highest_remote_gem highest_remote_gem.first end def install_rubygems(spec) # :nodoc: args = update_rubygems_arguments version = spec.version update_dir = File.join spec.base_dir, "gems", "rubygems-update-#{version}" Dir.chdir update_dir do say "Installing RubyGems #{version}" unless options[:silent] installed = preparing_gem_layout_for(version) do system Gem.ruby, "--disable-gems", "setup.rb", *args end unless options[:silent] say "RubyGems system software updated" if installed end end end def preparing_gem_layout_for(version) if Gem::Version.new(version) >= Gem::Version.new("3.2.a") yield else require "tmpdir" Dir.mktmpdir("gem_update") do |tmpdir| FileUtils.mv Gem.plugindir, tmpdir status = yield unless status FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir end status end end end def rubygems_target_version version = options[:system] update_latest = version == true unless update_latest version = Gem::Version.new version requirement = Gem::Requirement.new version return version, requirement end version = Gem::Version.new Gem::VERSION requirement = Gem::Requirement.new ">= #{Gem::VERSION}" rubygems_update = Gem::Specification.new rubygems_update.name = "rubygems-update" rubygems_update.version = version highest_remote_tup = highest_remote_name_tuple(rubygems_update) target = highest_remote_tup ? highest_remote_tup.version : version [target, requirement] end def update_gem(name, version = Gem::Requirement.default) return if @updated.any? {|spec| spec.name == name } update_options = options.dup update_options[:prerelease] = version.prerelease? @installer = Gem::DependencyInstaller.new update_options say "Updating #{name}" unless options[:system] begin @installer.install name, Gem::Requirement.new(version) rescue Gem::InstallError, Gem::DependencyError => e alert_error "Error installing #{name}:\n\t#{e.message}" end @installer.installed_gems.each do |spec| @updated << spec end end def update_gems(gems_to_update) gems_to_update.uniq.sort.each do |name_tuple| update_gem name_tuple.name, name_tuple.version end @updated end ## # Update RubyGems software to the latest version. def update_rubygems if Gem.disable_system_update_message alert_error Gem.disable_system_update_message terminate_interaction 1 end check_update_arguments version, requirement = rubygems_target_version check_latest_rubygems version check_oldest_rubygems version installed_gems = Gem::Specification.find_all_by_name "rubygems-update", requirement installed_gems = update_gem("rubygems-update", requirement) if installed_gems.empty? || installed_gems.first.version != version return if installed_gems.empty? install_rubygems installed_gems.first end def update_rubygems_arguments # :nodoc: args = [] args << "--silent" if options[:silent] args << "--prefix" << Gem.prefix if Gem.prefix args << "--no-document" unless options[:document].include?("rdoc") || options[:document].include?("ri") args << "--no-format-executable" if options[:no_format_executable] args << "--previous-version" << Gem::VERSION args end def which_to_update(highest_installed_gems, gem_names) result = [] highest_installed_gems.each do |_l_name, l_spec| next if !gem_names.empty? && gem_names.none? {|name| name == l_spec.name } highest_remote_tup = highest_remote_name_tuple l_spec next unless highest_remote_tup result << highest_remote_tup end result end private # # Oldest version we support downgrading to. This is the version that # originally ships with the oldest supported patch version of ruby. # def oldest_supported_version @oldest_supported_version ||= Gem::Version.new("3.3.3") end end PK!NBk commands/push_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" require_relative "../package" class Gem::Commands::PushCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::GemcutterUtilities def description # :nodoc: <<-EOF The push command uploads a gem to the push server (the default is https://rubygems.org) and adds it to the index. The gem can be removed from the index and deleted from the server using the yank command. For further discussion see the help for the yank command. The push command will use ~/.gem/credentials to authenticate to a server, but you can use the RubyGems environment variable GEM_HOST_API_KEY to set the api key to authenticate. EOF end def arguments # :nodoc: "GEM built gem to push up" end def usage # :nodoc: "#{program_name} GEM" end def initialize super "push", "Push a gem up to the gem server", host: host, attestations: [] @user_defined_host = false add_proxy_option add_key_option add_otp_option add_option("--host HOST", "Push to another gemcutter-compatible host", " (e.g. https://rubygems.org)") do |value, options| options[:host] = value @user_defined_host = true end add_option("--attestation FILE", "Push with sigstore attestations") do |value, options| options[:attestations] << value end @host = nil end def execute gem_name = get_one_gem_name default_gem_server, push_host = get_hosts_for(gem_name) @host = if @user_defined_host options[:host] elsif default_gem_server default_gem_server elsif push_host push_host else options[:host] end sign_in @host, scope: get_push_scope send_gem(gem_name) end def send_gem(name) args = [:post, "api/v1/gems"] _, push_host = get_hosts_for(name) @host ||= push_host # Always include @host, even if it's nil args += [@host, push_host] say "Pushing gem to #{@host || Gem.host}..." response = send_push_request(name, args) with_response response end private def send_push_request(name, args) scope = get_push_scope rubygems_api_request(*args, scope: scope) do |request| body = Gem.read_binary name if options[:attestations].any? request.set_form([ ["gem", body, { filename: name, content_type: "application/octet-stream" }], get_attestations_part, ], "multipart/form-data") else request.body = body request.add_field "Content-Type", "application/octet-stream" request.add_field "Content-Length", request.body.size end request.add_field "Authorization", api_key end end def get_hosts_for(name) gem_metadata = Gem::Package.new(name).spec.metadata [ gem_metadata["default_gem_server"], gem_metadata["allowed_push_host"], ] end def get_push_scope :push_rubygem end def get_attestations_part bundles = "[" + options[:attestations].map do |attestation| Gem.read_binary(attestation) end.join(",") + "]" [ "attestations", bundles, { content_type: "application/json" }, ] end end PK!bF葝commands/open_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../version_option" class Gem::Commands::OpenCommand < Gem::Command include Gem::VersionOption def initialize super "open", "Open gem sources in editor" add_option("-e", "--editor COMMAND", String, "Prepends COMMAND to gem path. Could be used to specify editor.") do |command, options| options[:editor] = command || get_env_editor end add_option("-v", "--version VERSION", String, "Opens specific gem version") do |version| options[:version] = version end end def arguments # :nodoc: "GEMNAME name of gem to open in editor" end def defaults_str # :nodoc: "-e #{get_env_editor}" end def description # :nodoc: <<-EOF The open command opens gem in editor and changes current path to gem's source directory. Editor command can be specified with -e option, otherwise rubygems will look for editor in $EDITOR, $VISUAL and $GEM_EDITOR variables. EOF end def usage # :nodoc: "#{program_name} [-e COMMAND] GEMNAME" end def get_env_editor ENV["GEM_EDITOR"] || ENV["VISUAL"] || ENV["EDITOR"] || "vi" end def execute @version = options[:version] || Gem::Requirement.default @editor = options[:editor] || get_env_editor found = open_gem(get_one_gem_name) terminate_interaction 1 unless found end def open_gem(name) spec = spec_for name return false unless spec if spec.default_gem? say "'#{name}' is a default gem and can't be opened." return false end open_editor(spec.full_gem_path) end def open_editor(path) system(*@editor.split(/\s+/) + [path], { chdir: path }) end def spec_for(name) spec = Gem::Specification.find_all_by_name(name, @version).first return spec if spec say "Unable to find gem '#{name}'" end end PK!W9P'P'commands/sources_command.rbnu[# frozen_string_literal: true require_relative "../command" require_relative "../remote_fetcher" require_relative "../spec_fetcher" require_relative "../local_remote_options" class Gem::Commands::SourcesCommand < Gem::Command include Gem::LocalRemoteOptions def initialize require "fileutils" super "sources", "Manage the sources and cache file RubyGems uses to search for gems" add_option "-a", "--add SOURCE_URI", "Add source" do |value, options| options[:add] = value end add_option "--append SOURCE_URI", "Append source (can be used multiple times)" do |value, options| options[:append] = value end add_option "-p", "--prepend SOURCE_URI", "Prepend source (can be used multiple times)" do |value, options| options[:prepend] = value end add_option "-l", "--list", "List sources" do |value, options| options[:list] = value end add_option "-r", "--remove SOURCE_URI", "Remove source" do |value, options| options[:remove] = value end add_option "-c", "--clear-all", "Remove all sources (clear the cache)" do |value, options| options[:clear_all] = value end add_option "-u", "--update", "Update source cache" do |value, options| options[:update] = value end add_option "-f", "--[no-]force", "Do not show any confirmation prompts and behave as if 'yes' was always answered" do |value, options| options[:force] = value end add_proxy_option end def add_source(source_uri) # :nodoc: source = build_new_source(source_uri) source_uri = source.uri.to_s begin if Gem.sources.include? source say "source #{source_uri} already present in the cache" else source.load_specs :released Gem.sources << source Gem.configuration.write say "#{source_uri} added to sources" end rescue Gem::URI::Error, ArgumentError say "#{source_uri} is not a URI" terminate_interaction 1 rescue Gem::RemoteFetcher::FetchError => e say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" terminate_interaction 1 end end def append_source(source_uri) # :nodoc: source = build_new_source(source_uri) source_uri = source.uri.to_s begin source.load_specs :released was_present = Gem.sources.include?(source) Gem.sources.append source Gem.configuration.write if was_present say "#{source_uri} moved to end of sources" else say "#{source_uri} added to sources" end rescue Gem::URI::Error, ArgumentError say "#{source_uri} is not a URI" terminate_interaction 1 rescue Gem::RemoteFetcher::FetchError => e say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" terminate_interaction 1 end end def prepend_source(source_uri) # :nodoc: source = build_new_source(source_uri) source_uri = source.uri.to_s begin source.load_specs :released was_present = Gem.sources.include?(source) Gem.sources.prepend source Gem.configuration.write if was_present say "#{source_uri} moved to top of sources" else say "#{source_uri} added to sources" end rescue Gem::URI::Error, ArgumentError say "#{source_uri} is not a URI" terminate_interaction 1 rescue Gem::RemoteFetcher::FetchError => e say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" terminate_interaction 1 end end def check_typo_squatting(source) if source.typo_squatting?("rubygems.org") question = <<-QUESTION.chomp #{source.uri} is too similar to https://rubygems.org Do you want to add this source? QUESTION terminate_interaction 1 unless options[:force] || ask_yes_no(question) end end def normalize_source_uri(source_uri) # :nodoc: # Ensure the source URI has a trailing slash for proper RFC 2396 path merging # Without a trailing slash, the last path segment is treated as a file and removed # during relative path resolution (e.g., "/blish" + "gems/foo.gem" = "/gems/foo.gem") # With a trailing slash, it's treated as a directory (e.g., "/blish/" + "gems/foo.gem" = "/blish/gems/foo.gem") uri = Gem::URI.parse(source_uri) uri.path = uri.path.gsub(%r{/+\z}, "") + "/" if uri.path && !uri.path.empty? uri.to_s rescue Gem::URI::Error # If parsing fails, return the original URI and let later validation handle it source_uri end def check_rubygems_https(source_uri) # :nodoc: uri = Gem::URI source_uri if uri.scheme && uri.scheme.casecmp("http").zero? && uri.host.casecmp("rubygems.org").zero? question = <<-QUESTION.chomp https://rubygems.org is recommended for security over #{uri} Do you want to add this insecure source? QUESTION terminate_interaction 1 unless options[:force] || ask_yes_no(question) end end def clear_all # :nodoc: path = Gem.spec_cache_dir FileUtils.rm_rf path if File.exist? path if File.writable? path say "*** Unable to remove source cache ***" else say "*** Unable to remove source cache (write protected) ***" end terminate_interaction 1 else say "*** Removed specs cache ***" end end def defaults_str # :nodoc: "--list" end def description # :nodoc: <<-EOF RubyGems fetches gems from the sources you have configured (stored in your ~/.gemrc). The default source is https://rubygems.org, but you may have other sources configured. This guide will help you update your sources or configure yourself to use your own gem server. Without any arguments the sources lists your currently configured sources: $ gem sources *** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** https://rubygems.org This may list multiple sources or non-rubygems sources. You probably configured them before or have an old `~/.gemrc`. If you have sources you do not recognize you should remove them. RubyGems has been configured to serve gems via the following URLs through its history: * http://gems.rubyforge.org (RubyGems 1.3.5 and earlier) * http://rubygems.org (RubyGems 1.3.6 through 1.8.30, and 2.0.0) * https://rubygems.org (RubyGems 2.0.1 and newer) Since all of these sources point to the same set of gems you only need one of them in your list. https://rubygems.org is recommended as it brings the protections of an SSL connection to gem downloads. To add a private gem source use the --prepend argument to insert it before the default source. This is usually the best place for private gem sources: $ gem sources --prepend https://my.private.source https://my.private.source added to sources RubyGems will check to see if gems can be installed from the source given before it is added. To add or move a source after all other sources, use --append: $ gem sources --append https://rubygems.org https://rubygems.org moved to end of sources To remove a source use the --remove argument: $ gem sources --remove https://my.private.source/ https://my.private.source/ removed from sources EOF end def list # :nodoc: if configured_sources header = "*** CURRENT SOURCES ***" list = configured_sources else header = "*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***" list = Gem.sources end say header say list.each do |src| say src end end def list? # :nodoc: !(options[:add] || options[:prepend] || options[:append] || options[:clear_all] || options[:remove] || options[:update]) end def execute clear_all if options[:clear_all] add_source options[:add] if options[:add] prepend_source options[:prepend] if options[:prepend] append_source options[:append] if options[:append] remove_source options[:remove] if options[:remove] update if options[:update] list if list? end def remove_source(source_uri) # :nodoc: source = build_source(source_uri) source_uri = source.uri.to_s if configured_sources&.include? source Gem.sources.delete source Gem.configuration.write if default_sources.include?(source) && configured_sources.one? alert_warning "Removing a default source when it is the only source has no effect. Add a different source to #{config_file_name} if you want to stop using it as a source." else say "#{source_uri} removed from sources" end elsif configured_sources say "source #{source_uri} cannot be removed because it's not present in #{config_file_name}" else say "source #{source_uri} cannot be removed because there are no configured sources in #{config_file_name}" end end def update # :nodoc: Gem.sources.each_source do |src| src.load_specs :released src.load_specs :latest end say "source cache successfully updated" end def remove_cache_file(desc, path) # :nodoc: FileUtils.rm_rf path if !File.exist?(path) say "*** Removed #{desc} source cache ***" elsif !File.writable?(path) say "*** Unable to remove #{desc} source cache (write protected) ***" else say "*** Unable to remove #{desc} source cache ***" end end private def default_sources Gem::SourceList.from(Gem.default_sources) end def configured_sources return @configured_sources if defined?(@configured_sources) configuration_sources = Gem.configuration.sources @configured_sources = Gem::SourceList.from(configuration_sources) if configuration_sources end def config_file_name Gem.configuration.config_file_name end def build_source(source_uri) source_uri = normalize_source_uri(source_uri) Gem::Source.new(source_uri) end def build_new_source(source_uri) source = build_source(source_uri) check_rubygems_https(source.uri.to_s) check_typo_squatting(source) source end end PK!JNBg uri.rbnu[# frozen_string_literal: true ## # The Uri handles rubygems source URIs. # class Gem::Uri ## # Parses and redacts uri def self.redact(uri) new(uri).redacted end ## # Parses uri, raising if it's invalid def self.parse!(uri) require_relative "vendor/uri/lib/uri" raise Gem::URI::InvalidURIError unless uri return uri unless uri.is_a?(String) # Always escape URI's to deal with potential spaces and such # It should also be considered that source_uri may already be # a valid URI with escaped characters. e.g. "{DESede}" is encoded # as "%7BDESede%7D". If this is escaped again the percentage # symbols will be escaped. begin Gem::URI.parse(uri) rescue Gem::URI::InvalidURIError Gem::URI.parse(Gem::URI::RFC2396_PARSER.escape(uri)) end end ## # Parses uri, returning the original uri if it's invalid def self.parse(uri) parse!(uri) rescue Gem::URI::InvalidURIError uri end def initialize(source_uri) @parsed_uri = parse(source_uri) end def redacted return self unless valid_uri? if token? || oauth_basic? with_redacted_user elsif password? with_redacted_password else self end end def to_s @parsed_uri.to_s end def redact_credentials_from(text) return text unless valid_uri? && password? && text.include?(to_s) text.sub(password, "REDACTED") end def method_missing(method_name, *args, &blk) if @parsed_uri.respond_to?(method_name) @parsed_uri.send(method_name, *args, &blk) else super end end def respond_to_missing?(method_name, include_private = false) @parsed_uri.respond_to?(method_name, include_private) || super end protected # Add a protected reader for the cloned instance to access the original object's parsed uri attr_reader :parsed_uri private def parse!(uri) self.class.parse!(uri) end def parse(uri) self.class.parse(uri) end def with_redacted_user clone.tap {|uri| uri.user = "REDACTED" } end def with_redacted_password clone.tap {|uri| uri.password = "REDACTED" } end def valid_uri? !@parsed_uri.is_a?(String) end def password? !!password end def oauth_basic? password == "x-oauth-basic" end def token? !user.nil? && password.nil? end def initialize_copy(original) @parsed_uri = original.parsed_uri.clone end end PK![(" doctor.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "user_interaction" ## # Cleans up after a partially-failed uninstall or for an invalid # Gem::Specification. # # If a specification was removed by hand this will remove any remaining files. # # If a corrupt specification was installed this will clean up warnings by # removing the bogus specification. class Gem::Doctor include Gem::UserInteraction ## # Maps a gem subdirectory to the files that are expected to exist in the # subdirectory. REPOSITORY_EXTENSION_MAP = [ # :nodoc: ["specifications", ".gemspec"], ["build_info", ".info"], ["cache", ".gem"], ["doc", ""], ["extensions", ""], ["gems", ""], ["plugins", ""], ].freeze missing = Gem::REPOSITORY_SUBDIRECTORIES.sort - REPOSITORY_EXTENSION_MAP.map {|(k,_)| k }.sort raise "Update REPOSITORY_EXTENSION_MAP, missing: #{missing.join ", "}" unless missing.empty? ## # Creates a new Gem::Doctor that will clean up +gem_repository+. Only one # gem repository may be cleaned at a time. # # If +dry_run+ is true no files or directories will be removed. def initialize(gem_repository, dry_run = false) @gem_repository = gem_repository @dry_run = dry_run @installed_specs = nil end ## # Specs installed in this gem repository def installed_specs # :nodoc: @installed_specs ||= Gem::Specification.map(&:full_name) end ## # Are we doctoring a gem repository? def gem_repository? !installed_specs.empty? end ## # Cleans up uninstalled files and invalid gem specifications def doctor @orig_home = Gem.dir @orig_path = Gem.path say "Checking #{@gem_repository}" Gem.use_paths @gem_repository.to_s unless gem_repository? say "This directory does not appear to be a RubyGems repository, " \ "skipping" say return end doctor_children say ensure Gem.use_paths @orig_home, *@orig_path end ## # Cleans up children of this gem repository def doctor_children # :nodoc: REPOSITORY_EXTENSION_MAP.each do |sub_directory, extension| doctor_child sub_directory, extension end end ## # Removes files in +sub_directory+ with +extension+ def doctor_child(sub_directory, extension) # :nodoc: directory = File.join(@gem_repository, sub_directory) Dir.entries(directory).sort.each do |ent| next if [".", ".."].include?(ent) child = File.join(directory, ent) next unless File.exist?(child) basename = File.basename(child, extension) next if installed_specs.include? basename next if /^rubygems-\d/.match?(basename) next if sub_directory == "specifications" && basename == "default" next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ basename type = File.directory?(child) ? "directory" : "file" action = if @dry_run "Extra" else FileUtils.rm_r(child) "Removed" end say "#{action} #{type} #{sub_directory}/#{File.basename(child)}" end rescue Errno::ENOENT # ignore end end PK!;}} openssl.rbnu[# frozen_string_literal: true autoload :OpenSSL, "openssl" module Gem HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc: end PK!9BB shellwords.rbnu[# frozen_string_literal: true autoload :Shellwords, "shellwords" PK!Fj''package_task.rbnu[# frozen_string_literal: true # Copyright (c) 2003, 2004 Jim Weirich, 2009 Eric Hodel # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require_relative "../rubygems" require_relative "package" require "rake/packagetask" ## # Create a package based upon a Gem::Specification. Gem packages, as well as # zip files and tar/gzipped packages can be produced by this task. # # In addition to the Rake targets generated by Rake::PackageTask, a # Gem::PackageTask will also generate the following tasks: # # ["package_dir/name-version.gem"] # Create a RubyGems package with the given name and version. # # Example using a Gem::Specification: # # require 'rubygems' # require 'rubygems/package_task' # # spec = Gem::Specification.new do |s| # s.summary = "Ruby based make-like utility." # s.name = 'rake' # s.version = PKG_VERSION # s.requirements << 'none' # s.files = PKG_FILES # s.description = <<-EOF # Rake is a Make-like program implemented in Ruby. Tasks # and dependencies are specified in standard Ruby syntax. # EOF # end # # Gem::PackageTask.new(spec) do |pkg| # pkg.need_zip = true # pkg.need_tar = true # end class Gem::PackageTask < Rake::PackageTask ## # Ruby Gem::Specification containing the metadata for this package. The # name, version and package_files are automatically determined from the # gemspec and don't need to be explicitly provided. attr_accessor :gem_spec ## # Create a Gem Package task library. Automatically define the gem if a # block is given. If no block is supplied, then #define needs to be called # to define the task. def initialize(gem_spec) init gem_spec yield self if block_given? define if block_given? end ## # Initialization tasks without the "yield self" or define operations. def init(gem) super gem.full_name, :noversion @gem_spec = gem @package_files += gem_spec.files if gem_spec.files @fileutils_output = $stdout end ## # Create the Rake tasks and actions specified by this Gem::PackageTask. # (+define+ is automatically called if a block is given to +new+). def define super gem_file = File.basename gem_spec.cache_file gem_path = File.join package_dir, gem_file gem_dir = File.join package_dir, gem_spec.full_name task package: [:gem] directory package_dir directory gem_dir desc "Build the gem file #{gem_file}" task gem: [gem_path] trace = Rake.application.options.trace Gem.configuration.verbose = trace file gem_path => [package_dir, gem_dir] + @gem_spec.files do chdir(gem_dir) do when_writing "Creating #{gem_spec.file_name}" do Gem::Package.build gem_spec verbose trace do mv gem_file, ".." end end end end end end PK!7XOOvendored_optparse.rbnu[# frozen_string_literal: true require_relative "vendor/optparse/lib/optparse" PK![!lkkupdate_suggestion.rbnu[# frozen_string_literal: true ## # Mixin methods for Gem::Command to promote available RubyGems update module Gem::UpdateSuggestion ONE_WEEK = 7 * 24 * 60 * 60 ## # Message to promote available RubyGems update with related gem update command. def update_suggestion <<-MESSAGE A new release of RubyGems is available: #{Gem.rubygems_version} → #{Gem.latest_rubygems_version}! Run `gem update --system #{Gem.latest_rubygems_version}` to update your installation. MESSAGE end ## # Determines if current environment is eligible for update suggestion. def eligible_for_update? # explicit opt-out return false if Gem.configuration[:prevent_update_suggestion] return false if ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] # focus only on human usage of final RubyGems releases return false unless Gem.ui.tty? return false if Gem.rubygems_version.prerelease? return false if Gem.disable_system_update_message return false if Gem::CIDetector.ci? # check makes sense only when we can store timestamp of last try # otherwise we will not be able to prevent "annoying" update message # on each command call return unless Gem.configuration.state_file_writable? # load time of last check, ensure the difference is enough to repeat the suggestion check_time = Time.now.to_i last_update_check = Gem.configuration.last_update_check return false if (check_time - last_update_check) < ONE_WEEK # compare current and latest version, this is the part where # latest rubygems spec is fetched from remote (Gem.rubygems_version < Gem.latest_rubygems_version).tap do |eligible| # store the time of last successful check into state file Gem.configuration.last_update_check = check_time return eligible end rescue StandardError # don't block install command on any problem false end end PK!<$Jinstall_update_options.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" require_relative "security_option" ## # Mixin methods for install and update options for Gem::Commands module Gem::InstallUpdateOptions include Gem::SecurityOption ## # Add the install/update options to the option parser. def add_install_update_options add_option(:"Install/Update", "-i", "--install-dir DIR", "Gem repository directory to get installed", "gems") do |value, options| options[:install_dir] = File.expand_path(value) end add_option(:"Install/Update", "-n", "--bindir DIR", "Directory where executables will be", "placed when the gem is installed") do |value, options| options[:bin_dir] = File.expand_path(value) end add_option(:"Install/Update", "-j", "--build-jobs VALUE", Integer, "Specify the number of jobs to pass to `make` when installing", "gems with native extensions.", "Defaults to the number of processors.", "This option is ignored on the mswin platform or", "if the MAKEFLAGS environment variable is set.") do |value, options| options[:build_jobs] = value end add_option(:"Install/Update", "--document [TYPES]", Array, "Generate documentation for installed gems", "List the documentation types you wish to", "generate. For example: rdoc,ri") do |value, options| options[:document] = case value when nil then %w[ri] when false then [] else value end end add_option(:"Install/Update", "--build-root DIR", "Temporary installation root. Useful for building", "packages. Do not use this when installing remote gems.") do |value, options| options[:build_root] = File.expand_path(value) end add_option(:"Install/Update", "--vendor", "Install gem into the vendor directory.", "Only for use by gem repackagers.") do |_value, options| unless Gem.vendor_dir raise Gem::OptionParser::InvalidOption.new "your platform is not supported" end options[:vendor] = true options[:install_dir] = Gem.vendor_dir end add_option(:"Install/Update", "-N", "--no-document", "Disable documentation generation") do |_value, options| options[:document] = [] end add_option(:"Install/Update", "-E", "--[no-]env-shebang", "Rewrite the shebang line on installed", "scripts to use /usr/bin/env") do |value, options| options[:env_shebang] = value end add_option(:"Install/Update", "-f", "--[no-]force", "Force gem to install, bypassing dependency", "checks") do |value, options| options[:force] = value end add_option(:"Install/Update", "-w", "--[no-]wrappers", "Use bin wrappers for executables", "Not available on dosish platforms") do |value, options| options[:wrappers] = value end add_security_option add_option(:"Install/Update", "--ignore-dependencies", "Do not install any required dependent gems") do |value, options| options[:ignore_dependencies] = value end add_option(:"Install/Update", "--[no-]format-executable", "Make installed executable names match Ruby.", "If Ruby is ruby18, foo_exec will be", "foo_exec18") do |value, options| options[:format_executable] = value end add_option(:"Install/Update", "--[no-]user-install", "Install in user's home directory instead", "of GEM_HOME.") do |value, options| options[:user_install] = value end add_option(:"Install/Update", "--development", "Install additional development", "dependencies") do |_value, options| options[:development] = true options[:dev_shallow] = true end add_option(:"Install/Update", "--development-all", "Install development dependencies for all", "gems (including dev deps themselves)") do |_value, options| options[:development] = true options[:dev_shallow] = false end add_option(:"Install/Update", "--conservative", "Don't attempt to upgrade gems already", "meeting version requirement") do |_value, options| options[:conservative] = true options[:minimal_deps] = true end add_option(:"Install/Update", "--[no-]minimal-deps", "Don't upgrade any dependencies that already", "meet version requirements") do |value, options| options[:minimal_deps] = value end add_option(:"Install/Update", "--[no-]post-install-message", "Print post install message") do |value, options| options[:post_install_message] = value end add_option(:"Install/Update", "-g", "--file [FILE]", "Read from a gem dependencies API file and", "install the listed gems") do |v,_o| v ||= Gem::GEM_DEP_FILES.find do |file| File.exist? file end unless v message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ", "})" raise Gem::OptionParser::InvalidArgument, "cannot find gem dependencies file #{message}" end options[:gemdeps] = v end add_option(:"Install/Update", "--without GROUPS", Array, "Omit the named groups (comma separated)", "when installing from a gem dependencies", "file") do |v,_o| options[:without_groups].concat v.map(&:intern) end add_option(:Deprecated, "--default", "Add the gem's full specification to", "specifications/default and extract only its bin") do |v,_o| end add_option(:"Install/Update", "--explain", "Rather than install the gems, indicate which would", "be installed") do |v,_o| options[:explain] = v end add_option(:"Install/Update", "--[no-]lock", "Create a lock file (when used with -g/--file)") do |v,_o| options[:lock] = v end add_option(:"Install/Update", "--[no-]suggestions", "Suggest alternates when gems are not found") do |v,_o| options[:suggest_alternate] = v end add_option(:"Install/Update", "--target-rbconfig [FILE]", "rbconfig.rb for the deployment target platform") do |v, _o| Gem.set_target_rbconfig(v) end end ## # Default options for the gem install and update commands. def install_update_options { document: %w[ri], } end ## # Default description for the gem install and update commands. def install_update_defaults_str "--document=ri" end end PK!p߅55util.rbnu[# frozen_string_literal: true ## # This module contains various utility methods as module methods. module Gem::Util ## # Zlib::GzipReader wrapper that unzips +data+. def self.gunzip(data) require "zlib" require "stringio" data = StringIO.new(data, "r") gzip_reader = begin Zlib::GzipReader.new(data) rescue Zlib::GzipFile::Error => e raise e.class, e.inspect, e.backtrace end unzipped = gzip_reader.read unzipped.force_encoding Encoding::BINARY unzipped end ## # Zlib::GzipWriter wrapper that zips +data+. def self.gzip(data) require "zlib" require "stringio" zipped = StringIO.new(String.new, "w") zipped.set_encoding Encoding::BINARY Zlib::GzipWriter.wrap zipped do |io| io.write data end zipped.string end ## # A Zlib::Inflate#inflate wrapper def self.inflate(data) require "zlib" Zlib::Inflate.inflate data end ## # This calls IO.popen and reads the result def self.popen(*command) IO.popen command, &:read end ## # Enumerates the parents of +directory+. def self.traverse_parents(directory, &block) return enum_for __method__, directory unless block_given? here = File.expand_path directory loop do begin Dir.chdir here, &block rescue StandardError Errno::EACCES end new_here = File.expand_path("..", here) return if new_here == here # toplevel here = new_here end end ## # Globs for files matching +pattern+ inside of +directory+, # returning absolute paths to the matching files. def self.glob_files_in_dir(glob, base_path) Dir.glob(glob, base: base_path).map! {|f| File.expand_path(f, base_path) } end ## # Corrects +path+ (usually returned by `Gem::URI.parse().path` on Windows), that # comes with a leading slash. def self.correct_for_windows_path(path) if path[0].chr == "/" && path[1].chr.match?(/[a-z]/i) && path[2].chr == ":" path[1..-1] else path end end end PK!/ basic_specification.rbnu[# frozen_string_literal: true ## # BasicSpecification is an abstract class which implements some common code # used by both Specification and StubSpecification. class Gem::BasicSpecification ## # Allows installation of extensions for git: gems. attr_writer :base_dir # :nodoc: ## # Sets the directory where extensions for this gem will be installed. attr_writer :extension_dir # :nodoc: ## # Is this specification ignored for activation purposes? attr_writer :ignored # :nodoc: ## # The path this gemspec was loaded from. This attribute is not persisted. attr_accessor :loaded_from ## # Allows correct activation of git: and path: gems. attr_writer :full_gem_path # :nodoc: def initialize internal_init end ## # The path to the gem.build_complete file within the extension install # directory. def gem_build_complete_path # :nodoc: File.join extension_dir, "gem.build_complete" end ## # True when the gem has been activated def activated? raise NotImplementedError end ## # Returns the full path to the base gem directory. # # eg: /usr/local/lib/ruby/gems/1.8 def base_dir raise NotImplementedError end ## # Return true if this spec can require +file+. def contains_requirable_file?(file) if ignored? if platform == Gem::Platform::RUBY || Gem::Platform.local === platform warn "Ignoring #{full_name} because its extensions are not built. " \ "Try: gem pristine #{name} --version #{version}" end return false end is_soext = file.end_with?(".so", ".o") if is_soext have_file? file.delete_suffix(File.extname(file)), Gem.dynamic_library_suffixes else have_file? file, Gem.suffixes end end ## # Return true if this spec should be ignored because it's missing extensions. def ignored? return @ignored unless @ignored.nil? @ignored = missing_extensions? end def default_gem? !loaded_from.nil? && File.dirname(loaded_from) == Gem.default_specifications_dir end ## # Regular gems take precedence over default gems def default_gem_priority default_gem? ? 1 : -1 end ## # Gems higher up in +gem_path+ take precedence def base_dir_priority(gem_path) gem_path.index(base_dir) || gem_path.size end ## # Returns full path to the directory where gem's extensions are installed. def extension_dir @extension_dir ||= File.expand_path(File.join(extensions_dir, full_name)) end ## # Returns path to the extensions directory. def extensions_dir Gem.default_ext_dir_for(base_dir) || File.join(base_dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version) end def find_full_gem_path # :nodoc: File.expand_path File.join(gems_dir, full_name) end private :find_full_gem_path ## # The full path to the gem (install path + full name). # # TODO: This is duplicated with #gem_dir. Eventually either of them should be deprecated. def full_gem_path @full_gem_path ||= find_full_gem_path end ## # Returns the full name (name-version) of this Gem. Platform information # is included (name-version-platform) if it is specified and not the # default Ruby platform. def full_name if platform == Gem::Platform::RUBY || platform.nil? "#{name}-#{version}" else "#{name}-#{version}-#{platform}" end end ## # Returns the full name of this Gem (see `Gem::BasicSpecification#full_name`). # Information about where the gem is installed is also included if not # installed in the default GEM_HOME. def full_name_with_location if base_dir != Gem.dir "#{full_name} in #{base_dir}" else full_name end end ## # Full paths in the gem to add to $LOAD_PATH when this gem is # activated. def full_require_paths @full_require_paths ||= begin full_paths = raw_require_paths.map do |path| File.join full_gem_path, path end full_paths << extension_dir if have_extensions? full_paths end end ## # The path to the data directory for this gem. def datadir # TODO: drop the extra ", gem_name" which is uselessly redundant File.expand_path(File.join(gems_dir, full_name, "data", name)) end extend Gem::Deprecate rubygems_deprecate :datadir, :none, "4.1" ## # Full path of the target library file. # If the file is not in this gem, return nil. def to_fullpath(path) if activated? @paths_map ||= {} Gem.suffixes.each do |suf| full_require_paths.each do |dir| fullpath = "#{dir}/#{path}#{suf}" next unless File.file?(fullpath) @paths_map[path] ||= fullpath end end @paths_map[path] end end ## # Returns the full path to this spec's gem directory. # eg: /usr/local/lib/ruby/1.8/gems/mygem-1.0 # # TODO: This is duplicated with #full_gem_path. Eventually either of them should be deprecated. def gem_dir @gem_dir ||= find_full_gem_path end ## # Returns the full path to the gems directory containing this spec's # gem directory. eg: /usr/local/lib/ruby/1.8/gems def gems_dir raise NotImplementedError end def internal_init # :nodoc: @extension_dir = nil @full_gem_path = nil @gem_dir = nil @ignored = nil end ## # Name of the gem def name raise NotImplementedError end ## # Platform of the gem def platform raise NotImplementedError end def installable_on_platform?(target_platform) # :nodoc: return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform) return true if Gem::Platform.new(platform) === target_platform false end def raw_require_paths # :nodoc: raise NotImplementedError end ## # Paths in the gem to add to $LOAD_PATH when this gem is # activated. # # See also #require_paths= # # If you have an extension you do not need to add "ext" to the # require path, the extension build process will copy the extension files # into "lib" for you. # # The default value is "lib" # # Usage: # # # If all library files are in the root directory... # spec.require_path = '.' def require_paths return raw_require_paths unless have_extensions? [extension_dir].concat raw_require_paths end ## # Returns the paths to the source files for use with analysis and # documentation tools. These paths are relative to full_gem_path. def source_paths paths = raw_require_paths.dup if have_extensions? ext_dirs = extensions.map do |extension| extension.split(File::SEPARATOR, 2).first end.uniq paths.concat ext_dirs end paths.uniq end ## # Return all files in this gem that match for +glob+. def matches_for_glob(glob) # TODO: rename? glob = File.join(lib_dirs_glob, glob) Dir[glob] end ## # Returns the list of plugins in this spec. def plugins matches_for_glob("rubygems#{Gem.plugin_suffix_pattern}") end ## # Returns a string usable in Dir.glob to match all requirable paths # for this spec. def lib_dirs_glob dirs = if raw_require_paths if raw_require_paths.size > 1 "{#{raw_require_paths.join(",")}}" else raw_require_paths.first end else "lib" # default value for require_paths for bundler/inline end "#{full_gem_path}/#{dirs}" end ## # Return a Gem::Specification from this gem def to_spec raise NotImplementedError end ## # Version of the gem def version raise NotImplementedError end ## # Whether this specification is stubbed - i.e. we have information # about the gem from a stub line, without having to evaluate the # entire gemspec file. def stubbed? raise NotImplementedError end def this self end private def have_extensions? !extensions.empty? end def have_file?(file, suffixes) return true if raw_require_paths.any? do |path| base = File.join(gems_dir, full_name, path, file) suffixes.any? {|suf| File.file? base + suf } end if have_extensions? base = File.join extension_dir, file suffixes.any? {|suf| File.file? base + suf } else false end end end PK!f util/list.rbnu[# frozen_string_literal: true module Gem # The Gem::List class is currently unused and will be removed in the next major rubygems version class List # :nodoc: include Enumerable attr_accessor :value, :tail def initialize(value = nil, tail = nil) @value = value @tail = tail end def each n = self while n yield n.value n = n.tail end end def to_a super.reverse end def prepend(value) List.new value, self end def pretty_print(q) # :nodoc: q.pp to_a end def self.prepend(list, value) return List.new(value) unless list List.new value, list end end deprecate_constant :List end PK!͵`==util/licenses.rbnu[# frozen_string_literal: true # This is generated by generate_spdx_license_list.rb, any edits to this # file will be discarded. require_relative "../text" class Gem::Licenses extend Gem::Text NONSTANDARD = "Nonstandard" LICENSE_REF = "LicenseRef-.+" # Software Package Data Exchange (SPDX) standard open-source software # license identifiers LICENSE_IDENTIFIERS = %w[ 0BSD 3D-Slicer-1.0 AAL ADSL AFL-1.1 AFL-1.2 AFL-2.0 AFL-2.1 AFL-3.0 AGPL-1.0-only AGPL-1.0-or-later AGPL-3.0-only AGPL-3.0-or-later ALGLIB-Documentation AMD-newlib AMDPLPA AML AML-glslang AMPAS ANTLR-PD ANTLR-PD-fallback APAFML APL-1.0 APSL-1.0 APSL-1.1 APSL-1.2 APSL-2.0 ASWF-Digital-Assets-1.0 ASWF-Digital-Assets-1.1 Abstyles AdaCore-doc Adobe-2006 Adobe-Display-PostScript Adobe-Glyph Adobe-Utopia Advanced-Cryptics-Dictionary Afmparse Aladdin Apache-1.0 Apache-1.1 Apache-2.0 App-s2p Arphic-1999 Artistic-1.0 Artistic-1.0-Perl Artistic-1.0-cl8 Artistic-2.0 Artistic-dist Aspell-RU BOLA-1.1 BSD-1-Clause BSD-2-Clause BSD-2-Clause-Darwin BSD-2-Clause-Patent BSD-2-Clause-Views BSD-2-Clause-first-lines BSD-2-Clause-pkgconf-disclaimer BSD-3-Clause BSD-3-Clause-Attribution BSD-3-Clause-Clear BSD-3-Clause-HP BSD-3-Clause-LBNL BSD-3-Clause-Modification BSD-3-Clause-No-Military-License BSD-3-Clause-No-Nuclear-License BSD-3-Clause-No-Nuclear-License-2014 BSD-3-Clause-No-Nuclear-Warranty BSD-3-Clause-Open-MPI BSD-3-Clause-Sun BSD-3-Clause-Tso BSD-3-Clause-acpica BSD-3-Clause-flex BSD-4-Clause BSD-4-Clause-Shortened BSD-4-Clause-UC BSD-4.3RENO BSD-4.3TAHOE BSD-Advertising-Acknowledgement BSD-Attribution-HPND-disclaimer BSD-Inferno-Nettverk BSD-Mark-Modifications BSD-Protection BSD-Source-Code BSD-Source-beginning-file BSD-Systemics BSD-Systemics-W3Works BSL-1.0 BUSL-1.1 Baekmuk Bahyph Barr Beerware BitTorrent-1.0 BitTorrent-1.1 Bitstream-Charter Bitstream-Vera BlueOak-1.0.0 Boehm-GC Boehm-GC-without-fee Borceux Brian-Gladman-2-Clause Brian-Gladman-3-Clause Buddy C-UDA-1.0 CAL-1.0 CAL-1.0-Combined-Work-Exception CAPEC-tou CATOSL-1.1 CC-BY-1.0 CC-BY-2.0 CC-BY-2.5 CC-BY-2.5-AU CC-BY-3.0 CC-BY-3.0-AT CC-BY-3.0-AU CC-BY-3.0-DE CC-BY-3.0-IGO CC-BY-3.0-NL CC-BY-3.0-US CC-BY-4.0 CC-BY-NC-1.0 CC-BY-NC-2.0 CC-BY-NC-2.5 CC-BY-NC-3.0 CC-BY-NC-3.0-DE CC-BY-NC-4.0 CC-BY-NC-ND-1.0 CC-BY-NC-ND-2.0 CC-BY-NC-ND-2.5 CC-BY-NC-ND-3.0 CC-BY-NC-ND-3.0-DE CC-BY-NC-ND-3.0-IGO CC-BY-NC-ND-4.0 CC-BY-NC-SA-1.0 CC-BY-NC-SA-2.0 CC-BY-NC-SA-2.0-DE CC-BY-NC-SA-2.0-FR CC-BY-NC-SA-2.0-UK CC-BY-NC-SA-2.5 CC-BY-NC-SA-3.0 CC-BY-NC-SA-3.0-DE CC-BY-NC-SA-3.0-IGO CC-BY-NC-SA-4.0 CC-BY-ND-1.0 CC-BY-ND-2.0 CC-BY-ND-2.5 CC-BY-ND-3.0 CC-BY-ND-3.0-DE CC-BY-ND-4.0 CC-BY-SA-1.0 CC-BY-SA-2.0 CC-BY-SA-2.0-UK CC-BY-SA-2.1-JP CC-BY-SA-2.5 CC-BY-SA-3.0 CC-BY-SA-3.0-AT CC-BY-SA-3.0-DE CC-BY-SA-3.0-IGO CC-BY-SA-4.0 CC-PDDC CC-PDM-1.0 CC-SA-1.0 CC0-1.0 CDDL-1.0 CDDL-1.1 CDL-1.0 CDLA-Permissive-1.0 CDLA-Permissive-2.0 CDLA-Sharing-1.0 CECILL-1.0 CECILL-1.1 CECILL-2.0 CECILL-2.1 CECILL-B CECILL-C CERN-OHL-1.1 CERN-OHL-1.2 CERN-OHL-P-2.0 CERN-OHL-S-2.0 CERN-OHL-W-2.0 CFITSIO CMU-Mach CMU-Mach-nodoc CNRI-Jython CNRI-Python CNRI-Python-GPL-Compatible COIL-1.0 CPAL-1.0 CPL-1.0 CPOL-1.02 CUA-OPL-1.0 Caldera Caldera-no-preamble Catharon ClArtistic Clips Community-Spec-1.0 Condor-1.1 Cornell-Lossless-JPEG Cronyx Crossword CryptoSwift CrystalStacker Cube D-FSL-1.0 DEC-3-Clause DL-DE-BY-2.0 DL-DE-ZERO-2.0 DOC DRL-1.0 DRL-1.1 DSDP DocBook-DTD DocBook-Schema DocBook-Stylesheet DocBook-XML Dotseqn ECL-1.0 ECL-2.0 EFL-1.0 EFL-2.0 EPICS EPL-1.0 EPL-2.0 ESA-PL-permissive-2.4 ESA-PL-strong-copyleft-2.4 ESA-PL-weak-copyleft-2.4 EUDatagrid EUPL-1.0 EUPL-1.1 EUPL-1.2 Elastic-2.0 Entessa ErlPL-1.1 Eurosym FBM FDK-AAC FSFAP FSFAP-no-warranty-disclaimer FSFUL FSFULLR FSFULLRSD FSFULLRWD FSL-1.1-ALv2 FSL-1.1-MIT FTL Fair Ferguson-Twofish Frameworx-1.0 FreeBSD-DOC FreeImage Furuseth GCR-docs GD GFDL-1.1-invariants-only GFDL-1.1-invariants-or-later GFDL-1.1-no-invariants-only GFDL-1.1-no-invariants-or-later GFDL-1.1-only GFDL-1.1-or-later GFDL-1.2-invariants-only GFDL-1.2-invariants-or-later GFDL-1.2-no-invariants-only GFDL-1.2-no-invariants-or-later GFDL-1.2-only GFDL-1.2-or-later GFDL-1.3-invariants-only GFDL-1.3-invariants-or-later GFDL-1.3-no-invariants-only GFDL-1.3-no-invariants-or-later GFDL-1.3-only GFDL-1.3-or-later GL2PS GLWTPL GPL-1.0-only GPL-1.0-or-later GPL-2.0-only GPL-2.0-or-later GPL-3.0-only GPL-3.0-or-later Game-Programming-Gems Giftware Glide Glulxe Graphics-Gems Gutmann HDF5 HIDAPI HP-1986 HP-1989 HPND HPND-DEC HPND-Fenneberg-Livingston HPND-INRIA-IMAG HPND-Intel HPND-Kevlin-Henney HPND-MIT-disclaimer HPND-Markus-Kuhn HPND-Netrek HPND-Pbmplus HPND-SMC HPND-UC HPND-UC-export-US HPND-doc HPND-doc-sell HPND-export-US HPND-export-US-acknowledgement HPND-export-US-modify HPND-export2-US HPND-merchantability-variant HPND-sell-MIT-disclaimer-xserver HPND-sell-regexpr HPND-sell-variant HPND-sell-variant-MIT-disclaimer HPND-sell-variant-MIT-disclaimer-rev HPND-sell-variant-critical-systems HTMLTIDY HaskellReport Hippocratic-2.1 IBM-pibs ICU IEC-Code-Components-EULA IJG IJG-short IPA IPL-1.0 ISC ISC-Veillard ISO-permission ImageMagick Imlib2 Info-ZIP Inner-Net-2.0 InnoSetup Intel Intel-ACPI Interbase-1.0 JPL-image JPNIC JSON Jam JasPer-2.0 Kastrup Kazlib Knuth-CTAN LAL-1.2 LAL-1.3 LGPL-2.0-only LGPL-2.0-or-later LGPL-2.1-only LGPL-2.1-or-later LGPL-3.0-only LGPL-3.0-or-later LGPLLR LOOP LPD-document LPL-1.0 LPL-1.02 LPPL-1.0 LPPL-1.1 LPPL-1.2 LPPL-1.3a LPPL-1.3c LZMA-SDK-9.11-to-9.20 LZMA-SDK-9.22 Latex2e Latex2e-translated-notice Leptonica LiLiQ-P-1.1 LiLiQ-R-1.1 LiLiQ-Rplus-1.1 Libpng Linux-OpenIB Linux-man-pages-1-para Linux-man-pages-copyleft Linux-man-pages-copyleft-2-para Linux-man-pages-copyleft-var Lucida-Bitmap-Fonts MIPS MIT MIT-0 MIT-CMU MIT-Click MIT-Festival MIT-Khronos-old MIT-Modern-Variant MIT-STK MIT-Wu MIT-advertising MIT-enna MIT-feh MIT-open-group MIT-testregex MITNFA MMIXware MMPL-1.0.1 MPEG-SSG MPL-1.0 MPL-1.1 MPL-2.0 MPL-2.0-no-copyleft-exception MS-LPL MS-PL MS-RL MTLL Mackerras-3-Clause Mackerras-3-Clause-acknowledgment MakeIndex Martin-Birgmeier McPhee-slideshow Minpack MirOS Motosoto MulanPSL-1.0 MulanPSL-2.0 Multics Mup NAIST-2003 NASA-1.3 NBPL-1.0 NCBI-PD NCGL-UK-2.0 NCL NCSA NGPL NICTA-1.0 NIST-PD NIST-PD-TNT NIST-PD-fallback NIST-Software NLOD-1.0 NLOD-2.0 NLPL NOSL NPL-1.0 NPL-1.1 NPOSL-3.0 NRL NTIA-PD NTP NTP-0 Naumen NetCDF Newsletr Nokia Noweb O-UDA-1.0 OAR OCCT-PL OCLC-2.0 ODC-By-1.0 ODbL-1.0 OFFIS OFL-1.0 OFL-1.0-RFN OFL-1.0-no-RFN OFL-1.1 OFL-1.1-RFN OFL-1.1-no-RFN OGC-1.0 OGDL-Taiwan-1.0 OGL-Canada-2.0 OGL-UK-1.0 OGL-UK-2.0 OGL-UK-3.0 OGTSL OLDAP-1.1 OLDAP-1.2 OLDAP-1.3 OLDAP-1.4 OLDAP-2.0 OLDAP-2.0.1 OLDAP-2.1 OLDAP-2.2 OLDAP-2.2.1 OLDAP-2.2.2 OLDAP-2.3 OLDAP-2.4 OLDAP-2.5 OLDAP-2.6 OLDAP-2.7 OLDAP-2.8 OLFL-1.3 OML OPL-1.0 OPL-UK-3.0 OPUBL-1.0 OSC-1.0 OSET-PL-2.1 OSL-1.0 OSL-1.1 OSL-2.0 OSL-2.1 OSL-3.0 OSSP OpenMDW-1.0 OpenPBS-2.3 OpenSSL OpenSSL-standalone OpenVision PADL PDDL-1.0 PHP-3.0 PHP-3.01 PPL PSF-2.0 ParaType-Free-Font-1.3 Parity-6.0.0 Parity-7.0.0 Pixar Plexus PolyForm-Noncommercial-1.0.0 PolyForm-Small-Business-1.0.0 PostgreSQL Python-2.0 Python-2.0.1 QPL-1.0 QPL-1.0-INRIA-2004 Qhull RHeCos-1.1 RPL-1.1 RPL-1.5 RPSL-1.0 RSA-MD RSCPL Rdisc Ruby Ruby-pty SAX-PD SAX-PD-2.0 SCEA SGI-B-1.0 SGI-B-1.1 SGI-B-2.0 SGI-OpenGL SGMLUG-PM SGP4 SHL-0.5 SHL-0.51 SISSL SISSL-1.2 SL SMAIL-GPL SMLNJ SMPPL SNIA SOFA SPL-1.0 SSH-OpenSSH SSH-short SSLeay-standalone SSPL-1.0 SUL-1.0 SWL Saxpath SchemeReport Sendmail Sendmail-8.23 Sendmail-Open-Source-1.1 SimPL-2.0 Sleepycat Soundex Spencer-86 Spencer-94 Spencer-99 SugarCRM-1.1.3 Sun-PPP Sun-PPP-2000 SunPro Symlinks TAPR-OHL-1.0 TCL TCP-wrappers TGPPL-1.0 TMate TORQUE-1.1 TOSL TPDL TPL-1.0 TTWL TTYP0 TU-Berlin-1.0 TU-Berlin-2.0 TekHVC TermReadKey ThirdEye TrustedQSL UCAR UCL-1.0 UMich-Merit UPL-1.0 URT-RLE Ubuntu-font-1.0 UnRAR Unicode-3.0 Unicode-DFS-2015 Unicode-DFS-2016 Unicode-TOU UnixCrypt Unlicense Unlicense-libtelnet Unlicense-libwhirlpool VOSTROM VSL-1.0 Vim Vixie-Cron W3C W3C-19980720 W3C-20150513 WTFNMFPL WTFPL Watcom-1.0 Widget-Workshop WordNet Wsuipa X11 X11-distribute-modifications-variant X11-no-permit-persons X11-swapped XFree86-1.1 XSkat Xdebug-1.03 Xerox Xfig Xnet YPL-1.0 YPL-1.1 ZPL-1.1 ZPL-2.0 ZPL-2.1 Zed Zeeff Zend-2.0 Zimbra-1.3 Zimbra-1.4 Zlib any-OSI any-OSI-perl-modules bcrypt-Solar-Designer blessing bzip2-1.0.6 check-cvs checkmk copyleft-next-0.3.0 copyleft-next-0.3.1 curl cve-tou diffmark dtoa dvipdfm eGenix etalab-2.0 fwlw gSOAP-1.3b generic-xts gnuplot gtkbook hdparm hyphen-bulgarian iMatix jove libpng-1.6.35 libpng-2.0 libselinux-1.0 libtiff libutil-David-Nugent lsof magaz mailprio man2html metamail mpi-permissive mpich2 mplus ngrep pkgconf pnmstitch psfrag psutils python-ldap radvd snprintf softSurfer ssh-keyscan swrule threeparttable ulem w3m wwl xinetd xkeyboard-config-Zinoviev xlock xpp xzoom zlib-acknowledgement ].freeze DEPRECATED_LICENSE_IDENTIFIERS = %w[ AGPL-1.0 AGPL-3.0 BSD-2-Clause-FreeBSD BSD-2-Clause-NetBSD GFDL-1.1 GFDL-1.2 GFDL-1.3 GPL-1.0 GPL-1.0+ GPL-2.0 GPL-2.0+ GPL-2.0-with-GCC-exception GPL-2.0-with-autoconf-exception GPL-2.0-with-bison-exception GPL-2.0-with-classpath-exception GPL-2.0-with-font-exception GPL-3.0 GPL-3.0+ GPL-3.0-with-GCC-exception GPL-3.0-with-autoconf-exception LGPL-2.0 LGPL-2.0+ LGPL-2.1 LGPL-2.1+ LGPL-3.0 LGPL-3.0+ Net-SNMP Nunit StandardML-NJ bzip2-1.0.5 eCos-2.0 wxWindows ].freeze # exception identifiers EXCEPTION_IDENTIFIERS = %w[ 389-exception Asterisk-exception Asterisk-linking-protocols-exception Autoconf-exception-2.0 Autoconf-exception-3.0 Autoconf-exception-generic Autoconf-exception-generic-3.0 Autoconf-exception-macro Bison-exception-1.24 Bison-exception-2.2 Bootloader-exception CGAL-linking-exception CLISP-exception-2.0 Classpath-exception-2.0 Classpath-exception-2.0-short DigiRule-FOSS-exception Digia-Qt-LGPL-exception-1.1 FLTK-exception Fawkes-Runtime-exception Font-exception-2.0 GCC-exception-2.0 GCC-exception-2.0-note GCC-exception-3.1 GNAT-exception GNOME-examples-exception GNU-compiler-exception GPL-3.0-389-ds-base-exception GPL-3.0-interface-exception GPL-3.0-linking-exception GPL-3.0-linking-source-exception GPL-CC-1.0 GStreamer-exception-2005 GStreamer-exception-2008 Gmsh-exception Independent-modules-exception KiCad-libraries-exception LGPL-3.0-linking-exception LLGPL LLVM-exception LZMA-exception Libtool-exception Linux-syscall-note OCCT-exception-1.0 OCaml-LGPL-linking-exception OpenJDK-assembly-exception-1.0 PCRE2-exception PS-or-PDF-font-exception-20170817 QPL-1.0-INRIA-2004-exception Qt-GPL-exception-1.0 Qt-LGPL-exception-1.1 Qwt-exception-1.0 RRDtool-FLOSS-exception-2.0 SANE-exception SHL-2.0 SHL-2.1 SWI-exception Simple-Library-Usage-exception Swift-exception Texinfo-exception UBDL-exception Universal-FOSS-exception-1.0 WxWindows-exception-3.1 cryptsetup-OpenSSL-exception eCos-exception-2.0 erlang-otp-linking-exception fmt-exception freertos-exception-2.0 gnu-javamail-exception harbour-exception i2p-gpl-java-exception kvirc-openssl-exception libpri-OpenH323-exception mif-exception mxml-exception openvpn-openssl-exception polyparse-exception romic-exception rsync-linking-exception sqlitestudio-OpenSSL-exception stunnel-exception u-boot-exception-2.0 vsftpd-openssl-exception x11vnc-openssl-exception ].freeze DEPRECATED_EXCEPTION_IDENTIFIERS = %w[ Nokia-Qt-exception-1.1 ].freeze VALID_REGEXP = / \A (?: #{Regexp.union(LICENSE_IDENTIFIERS)} \+? (?:\s WITH \s #{Regexp.union(EXCEPTION_IDENTIFIERS)})? | #{NONSTANDARD} | #{LICENSE_REF} ) \Z /ox DEPRECATED_LICENSE_REGEXP = / \A #{Regexp.union(DEPRECATED_LICENSE_IDENTIFIERS)} \+? (?:\s WITH \s .+?)? \Z /ox DEPRECATED_EXCEPTION_REGEXP = / \A .+? \+? (?:\s WITH \s #{Regexp.union(DEPRECATED_EXCEPTION_IDENTIFIERS)}) \Z /ox def self.match?(license) VALID_REGEXP.match?(license) end def self.deprecated_license_id?(license) DEPRECATED_LICENSE_REGEXP.match?(license) end def self.deprecated_exception_id?(license) DEPRECATED_EXCEPTION_REGEXP.match?(license) end def self.suggestions(license) by_distance = LICENSE_IDENTIFIERS.group_by do |identifier| levenshtein_distance(identifier, license) end lowest = by_distance.keys.min return unless lowest < license.size by_distance[lowest] end end PK!jK[[local_remote_options.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "vendor/uri/lib/uri" require_relative "../rubygems" ## # Mixin methods for local and remote Gem::Command options. module Gem::LocalRemoteOptions ## # Allows Gem::OptionParser to handle HTTP URIs. def accept_uri_http Gem::OptionParser.accept Gem::URI::HTTP do |value| begin uri = Gem::URI.parse value rescue Gem::URI::InvalidURIError raise Gem::OptionParser::InvalidArgument, value end valid_uri_schemes = ["http", "https", "file", "s3"] unless valid_uri_schemes.include?(uri.scheme) msg = "Invalid uri scheme for #{value}\nPreface URLs with one of #{valid_uri_schemes.map {|s| "#{s}://" }}" raise ArgumentError, msg end value end end ## # Add local/remote options to the command line parser. def add_local_remote_options add_option(:"Local/Remote", "-l", "--local", "Restrict operations to the LOCAL domain") do |_value, options| options[:domain] = :local end add_option(:"Local/Remote", "-r", "--remote", "Restrict operations to the REMOTE domain") do |_value, options| options[:domain] = :remote end add_option(:"Local/Remote", "-b", "--both", "Allow LOCAL and REMOTE operations") do |_value, options| options[:domain] = :both end add_bulk_threshold_option add_clear_sources_option add_source_option add_proxy_option add_update_sources_option end ## # Add the --bulk-threshold option def add_bulk_threshold_option add_option(:"Local/Remote", "-B", "--bulk-threshold COUNT", "Threshold for switching to bulk", "synchronization (default #{Gem.configuration.bulk_threshold})") do |value, _options| Gem.configuration.bulk_threshold = value.to_i end end ## # Add the --clear-sources option def add_clear_sources_option add_option(:"Local/Remote", "--clear-sources", "Clear the gem sources") do |_value, options| Gem.sources = nil options[:sources_cleared] = true end end ## # Add the --http-proxy option def add_proxy_option accept_uri_http add_option(:"Local/Remote", "-p", "--[no-]http-proxy [URL]", Gem::URI::HTTP, "Use HTTP proxy for remote operations") do |value, options| options[:http_proxy] = value == false ? :no_proxy : value Gem.configuration[:http_proxy] = options[:http_proxy] end end ## # Add the --source option def add_source_option accept_uri_http add_option(:"Local/Remote", "-s", "--source URL", Gem::URI::HTTP, "Append URL to list of remote gem sources") do |source, options| source << "/" unless source.end_with?("/") if options.delete :sources_cleared Gem.sources = [source] else Gem.sources << source unless Gem.sources.include?(source) end end end ## # Add the --update-sources option def add_update_sources_option add_option(:Deprecated, "-u", "--[no-]update-sources", "Update local source cache") do |value, _options| Gem.configuration.update_sources = value end end ## # Is fetching of local and remote information enabled? def both? options[:domain] == :both end ## # Is local fetching enabled? def local? [:local, :both].include?(options[:domain]) end ## # Is remote fetching enabled? def remote? [:remote, :both].include?(options[:domain]) end end PK!::specification_policy.rbnu[# frozen_string_literal: true require_relative "user_interaction" class Gem::SpecificationPolicy include Gem::UserInteraction VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc: SPECIAL_CHARACTERS = /\A[#{Regexp.escape(".-_")}]+/ # :nodoc: VALID_URI_PATTERN = %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} # :nodoc: METADATA_LINK_KEYS = %w[ homepage_uri changelog_uri source_code_uri documentation_uri wiki_uri mailing_list_uri bug_tracker_uri download_uri funding_uri ].freeze # :nodoc: def initialize(specification) @warnings = 0 @specification = specification end ## # If set to true, run packaging-specific checks, as well. attr_accessor :packaging ## # Does a sanity check on the specification. # # Raises InvalidSpecificationException if the spec does not pass the # checks. # # It also performs some validations that do not raise but print warning # messages instead. def validate(strict = false) validate_required! validate_required_metadata! validate_optional(strict) if packaging || strict true end ## # Does a sanity check on the specification. # # Raises InvalidSpecificationException if the spec does not pass the # checks. # # Only runs checks that are considered necessary for the specification to be # functional. def validate_required! validate_nil_attributes validate_rubygems_version validate_required_attributes validate_name validate_require_paths @specification.keep_only_files_and_directories validate_non_files validate_self_inclusion_in_files_list validate_specification_version validate_platform validate_array_attributes validate_authors_field validate_licenses_length validate_duplicate_dependencies end def validate_required_metadata! validate_metadata validate_lazy_metadata end def validate_optional(strict) validate_licenses validate_permissions validate_values validate_dependencies validate_required_ruby_version validate_extensions validate_removed_attributes validate_unique_links if @warnings > 0 if strict error "specification has warnings" else alert_warning help_text end end end ## # Implementation for Specification#validate_for_resolution def validate_for_resolution validate_required! end ## # Implementation for Specification#validate_metadata def validate_metadata metadata = @specification.metadata unless Hash === metadata error "metadata must be a hash" end metadata.each do |key, value| entry = "metadata['#{key}']" unless key.is_a?(String) error "metadata keys must be a String" end if key.size > 128 error "metadata key is too large (#{key.size} > 128)" end unless value.is_a?(String) error "#{entry} value must be a String" end if value.size > 1024 error "#{entry} value is too large (#{value.size} > 1024)" end next unless METADATA_LINK_KEYS.include? key unless VALID_URI_PATTERN.match?(value) error "#{entry} has invalid link: #{value.inspect}" end end end ## # Checks that no duplicate dependencies are specified. def validate_duplicate_dependencies # :nodoc: # NOTE: see REFACTOR note in Gem::Dependency about types - this might be brittle seen = Gem::Dependency::TYPES.inject({}) {|types, type| types.merge({ type => {} }) } error_messages = [] @specification.dependencies.each do |dep| if prev = seen[dep.type][dep.name] error_messages << <<-MESSAGE duplicate dependency on #{dep}, (#{prev.requirement}) use: add_#{dep.type}_dependency \"#{dep.name}\", \"#{dep.requirement}\", \"#{prev.requirement}\" MESSAGE end seen[dep.type][dep.name] = dep end if error_messages.any? error error_messages.join end end ## # Checks that the gem does not depend on itself. def validate_dependencies # :nodoc: warning_messages = [] @specification.dependencies.each do |dep| if dep.name == @specification.name # warn on self reference warning_messages << "Self referencing dependency is unnecessary and strongly discouraged." end end if warning_messages.any? warning_messages.each {|warning_message| warning warning_message } end end def validate_required_ruby_version if @specification.required_ruby_version.requirements == [Gem::Requirement::DefaultRequirement] warning "make sure you specify the oldest ruby version constraint (like \">= 3.0\") that you want your gem to support by setting the `required_ruby_version` gemspec attribute" end end ## # Issues a warning for each file to be packaged which is world-readable. # # Implementation for Specification#validate_permissions def validate_permissions return if Gem.win_platform? @specification.files.each do |file| next unless File.file?(file) next if File.stat(file).mode & 0o444 == 0o444 warning "#{file} is not world-readable" end @specification.executables.each do |name| exec = File.join @specification.bindir, name next unless File.file?(exec) next if File.stat(exec).executable? warning "#{exec} is not executable" end end private def validate_nil_attributes nil_attributes = Gem::Specification.non_nil_attributes.select do |attrname| @specification.instance_variable_get("@#{attrname}").nil? end return if nil_attributes.empty? error "#{nil_attributes.join ", "} must not be nil" end def validate_rubygems_version return unless packaging rubygems_version = @specification.rubygems_version return if rubygems_version == Gem::VERSION warning "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" @specification.rubygems_version = Gem::VERSION end def validate_required_attributes Gem::Specification.required_attributes.each do |symbol| unless @specification.send symbol error "missing value for attribute #{symbol}" end end end def validate_name name = @specification.name if !name.is_a?(String) error "invalid value for attribute name: \"#{name.inspect}\" must be a string" elsif !/[a-zA-Z]/.match?(name) error "invalid value for attribute name: #{name.dump} must include at least one letter" elsif !VALID_NAME_PATTERN.match?(name) error "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores" elsif SPECIAL_CHARACTERS.match?(name) error "invalid value for attribute name: #{name.dump} cannot begin with a period, dash, or underscore" end end def validate_require_paths return unless @specification.raw_require_paths.empty? error "specification must have at least one require_path" end def validate_non_files return unless packaging non_files = @specification.files.reject {|x| File.file?(x) || File.symlink?(x) } unless non_files.empty? error "[\"#{non_files.join "\", \""}\"] are not files" end end def validate_self_inclusion_in_files_list file_name = @specification.file_name return unless @specification.files.include?(file_name) error "#{@specification.full_name} contains itself (#{file_name}), check your files list" end def validate_specification_version return if @specification.specification_version.is_a?(Integer) error "specification_version must be an Integer (did you mean version?)" end def validate_platform platform = @specification.platform case platform when Gem::Platform, Gem::Platform::RUBY # ok else error "invalid platform #{platform.inspect}, see Gem::Platform" end end def validate_array_attributes Gem::Specification.array_attributes.each do |field| validate_array_attribute(field) end end def validate_array_attribute(field) val = @specification.send(field) klass = case field when :dependencies then Gem::Dependency else String end unless Array === val && val.all? {|x| x.is_a?(klass) || (field == :licenses && x.nil?) } error "#{field} must be an Array of #{klass}" end end def validate_authors_field return unless @specification.authors.empty? error "authors may not be empty" end def validate_licenses_length licenses = @specification.licenses licenses.each do |license| next if license.nil? if license.length > 64 error "each license must be 64 characters or less" end end end def validate_licenses licenses = @specification.licenses licenses.each do |license| next if Gem::Licenses.match?(license) || license.nil? license_id_deprecated = Gem::Licenses.deprecated_license_id?(license) exception_id_deprecated = Gem::Licenses.deprecated_exception_id?(license) suggestions = Gem::Licenses.suggestions(license) if license_id_deprecated main_message = "License identifier '#{license}' is deprecated" elsif exception_id_deprecated main_message = "Exception identifier at '#{license}' is deprecated" else main_message = "License identifier '#{license}' is invalid" end message = <<-WARNING #{main_message}. Use an identifier from https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license, or set it to nil if you don't want to specify a license. WARNING message += "Did you mean #{suggestions.map {|s| "'#{s}'" }.join(", ")}?\n" unless suggestions.nil? warning(message) end warning <<-WARNING if licenses.empty? licenses is empty, but is recommended. Use an license identifier from https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license, or set it to nil if you don't want to specify a license. WARNING end LAZY = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, "") LAZY_PATTERN = /\AFI XME|\ATO DO/x HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i def validate_lazy_metadata unless @specification.authors.grep(LAZY_PATTERN).empty? error "#{LAZY} is not an author" end unless Array(@specification.email).grep(LAZY_PATTERN).empty? error "#{LAZY} is not an email" end if LAZY_PATTERN.match?(@specification.description) error "#{LAZY} is not a description" end if LAZY_PATTERN.match?(@specification.summary) error "#{LAZY} is not a summary" end homepage = @specification.homepage # Make sure a homepage is valid HTTP/HTTPS URI if homepage && !homepage.empty? require_relative "vendor/uri/lib/uri" begin homepage_uri = Gem::URI.parse(homepage) unless [Gem::URI::HTTP, Gem::URI::HTTPS].member? homepage_uri.class error "\"#{homepage}\" is not a valid HTTP URI" end rescue Gem::URI::InvalidURIError error "\"#{homepage}\" is not a valid HTTP URI" end end end def validate_values %w[author homepage summary files].each do |attribute| validate_attribute_present(attribute) end if @specification.description == @specification.summary warning "description and summary are identical" end # TODO: raise at some given date warning "deprecated autorequire specified" if @specification.autorequire @specification.executables.each do |executable| validate_executable(executable) validate_shebang_line_in(executable) end @specification.files.select {|f| File.symlink?(f) }.each do |file| warning "#{file} is a symlink, which is not supported on all platforms" end end def validate_attribute_present(attribute) value = @specification.send attribute warning("no #{attribute} specified") if value.nil? || value.empty? end def validate_executable(executable) separators = [File::SEPARATOR, File::ALT_SEPARATOR, File::PATH_SEPARATOR].compact.map {|sep| Regexp.escape(sep) }.join return unless executable.match?(/[\s#{separators}]/) error "executable \"#{executable}\" contains invalid characters" end def validate_shebang_line_in(executable) executable_path = File.join(@specification.bindir, executable) return if File.read(executable_path, 2) == "#!" warning "#{executable_path} is missing #! line" end def validate_removed_attributes # :nodoc: @specification.removed_method_calls.each do |attr| warning("#{attr} is deprecated and ignored. Please remove this from your gemspec to ensure that your gem continues to build in the future.") end end def validate_extensions # :nodoc: require_relative "ext" builder = Gem::Ext::Builder.new(@specification) validate_rake_extensions(builder) validate_rust_extensions(builder) end def validate_rust_extensions(builder) # :nodoc: rust_extension = @specification.extensions.any? {|s| builder.builder_for(s).is_a? Gem::Ext::CargoBuilder } missing_cargo_lock = !@specification.files.any? {|f| f.end_with?("Cargo.lock") } error <<-ERROR if rust_extension && missing_cargo_lock You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec. ERROR end def validate_rake_extensions(builder) # :nodoc: rake_extension = @specification.extensions.any? {|s| builder.builder_for(s) == Gem::Ext::RakeBuilder } rake_dependency = @specification.dependencies.any? {|d| d.name == "rake" && d.type == :runtime } warning <<-WARNING if rake_extension && !rake_dependency You have specified rake based extension, but rake is not added as runtime dependency. It is recommended to add rake as a runtime dependency in gemspec since there's no guarantee rake will be already installed. WARNING end def validate_unique_links links = @specification.metadata.slice(*METADATA_LINK_KEYS) grouped = links.group_by {|_key, uri| uri } grouped.each do |uri, copies| next unless copies.length > 1 keys = copies.map(&:first).join("\n ") warning <<~WARNING You have specified the uri: #{uri} for all of the following keys: #{keys} Only the first one will be shown on rubygems.org WARNING end end def warning(statement) # :nodoc: @warnings += 1 alert_warning statement end def error(statement) # :nodoc: raise Gem::InvalidSpecificationException, statement ensure alert_warning help_text end def help_text # :nodoc: "See https://guides.rubygems.org/specification-reference/ for help" end end PK!X00 safe_marshal/visitors/visitor.rbnu[# frozen_string_literal: true module Gem::SafeMarshal::Visitors class Visitor def visit(target) send DISPATCH.fetch(target.class), target end private DISPATCH = Gem::SafeMarshal::Elements.constants.each_with_object({}) do |c, h| next if c == :Element klass = Gem::SafeMarshal::Elements.const_get(c) h[klass] = :"visit_#{klass.name.gsub("::", "_")}" h.default = :visit_unknown_element end.compare_by_identity.freeze private_constant :DISPATCH def visit_unknown_element(e) raise ArgumentError, "Attempting to visit unknown element #{e.inspect}" end def visit_Gem_SafeMarshal_Elements_Array(target) target.elements.each {|e| visit(e) } end def visit_Gem_SafeMarshal_Elements_Bignum(target); end def visit_Gem_SafeMarshal_Elements_False(target); end def visit_Gem_SafeMarshal_Elements_Float(target); end def visit_Gem_SafeMarshal_Elements_Hash(target) target.pairs.each do |k, v| visit(k) visit(v) end end def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(target) visit_Gem_SafeMarshal_Elements_Hash(target) visit(target.default) end def visit_Gem_SafeMarshal_Elements_Integer(target); end def visit_Gem_SafeMarshal_Elements_Nil(target); end def visit_Gem_SafeMarshal_Elements_Object(target) visit(target.name) end def visit_Gem_SafeMarshal_Elements_ObjectLink(target); end def visit_Gem_SafeMarshal_Elements_String(target); end def visit_Gem_SafeMarshal_Elements_Symbol(target); end def visit_Gem_SafeMarshal_Elements_SymbolLink(target); end def visit_Gem_SafeMarshal_Elements_True(target); end def visit_Gem_SafeMarshal_Elements_UserDefined(target) visit(target.name) end def visit_Gem_SafeMarshal_Elements_UserMarshal(target) visit(target.name) visit(target.data) end def visit_Gem_SafeMarshal_Elements_WithIvars(target) visit(target.object) target.ivars.each do |k, v| visit(k) visit(v) end end end end PK!Im'safe_marshal/visitors/stream_printer.rbnu[# frozen_string_literal: true require_relative "visitor" module Gem::SafeMarshal module Visitors class StreamPrinter < Visitor def initialize(io, indent: "") @io = io @indent = indent @level = 0 end def visit(target) @io.write("#{@indent * @level}#{target.class}") target.instance_variables.each do |ivar| value = target.instance_variable_get(ivar) next if Elements::Element === value || Array === value @io.write(" #{ivar}=#{value.inspect}") end @io.write("\n") begin @level += 1 super ensure @level -= 1 end end end end end PK!", self.class, @permitted_classes, @permitted_symbols, @permitted_ivars) end def visit(target) stack_idx = @stack_idx super ensure @stack_idx = stack_idx - 1 end private def push_stack(element) @stack[@stack_idx] = element @stack_idx += 1 end def visit_Gem_SafeMarshal_Elements_Array(a) array = register_object([]) elements = a.elements size = elements.size idx = 0 # not idiomatic, but there's a huge number of IMEMOs allocated here, so we avoid the block # because this is such a hot path when doing a bundle install with the full index while idx < size push_stack idx array << visit(elements[idx]) idx += 1 end array end def visit_Gem_SafeMarshal_Elements_Symbol(s) name = s.name raise UnpermittedSymbolError.new(symbol: name, stack: formatted_stack) unless @permitted_symbols.include?(name) visit_symbol_type(s) end def map_ivars(klass, ivars) stack_idx = @stack_idx ivars.map.with_index do |(k, v), i| @stack_idx = stack_idx push_stack "ivar_" push_stack i k = resolve_ivar(klass, k) @stack_idx = stack_idx push_stack k next k, visit(v) end end def visit_Gem_SafeMarshal_Elements_WithIvars(e) object_offset = @objects.size push_stack "object" object = visit(e.object) ivars = map_ivars(object.class, e.ivars) case e.object when Elements::UserDefined if object.class == ::Time internal = [] ivars.reject! do |k, v| case k when :offset, :zone, :nano_num, :nano_den, :submicro internal << [k, v] true else false end end s = e.object.binary_string # 122 is the largest integer that can be represented in marshal in a single byte raise TimeTooLargeError.new("binary string too large", stack: formatted_stack) if s.bytesize > 122 marshal_string = "\x04\bIu:\tTime".b marshal_string.concat(s.bytesize + 5) marshal_string << s # internal is limited to 5, so no overflow is possible marshal_string.concat(internal.size + 5) internal.each do |k, v| k = k.name # ivar name can't be too large because only known ivars are in the internal ivars list marshal_string.concat(":") marshal_string.concat(k.bytesize + 5) marshal_string.concat(k) dumped = Marshal.dump(v) dumped[0, 2] = "" marshal_string.concat(dumped) end object = @objects[object_offset] = Marshal.load(marshal_string) end when Elements::String enc = nil ivars.reject! do |k, v| case k when :E case v when TrueClass enc = "UTF-8" when FalseClass enc = "US-ASCII" else raise FormatError, "Unexpected value for String :E #{v.inspect}" end when :encoding enc = v else next false end true end object.force_encoding(enc) if enc end ivars.each do |k, v| object.instance_variable_set k, v end object end def visit_Gem_SafeMarshal_Elements_Hash(o) hash = register_object({}) o.pairs.each_with_index do |(k, v), i| push_stack i k = visit(k) push_stack k hash[k] = visit(v) end hash end def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(o) hash = visit_Gem_SafeMarshal_Elements_Hash(o) push_stack :default hash.default = visit(o.default) hash end def visit_Gem_SafeMarshal_Elements_Object(o) register_object(resolve_class(o.name).allocate) end def visit_Gem_SafeMarshal_Elements_ObjectLink(o) @objects.fetch(o.offset) end def visit_Gem_SafeMarshal_Elements_SymbolLink(o) @symbols.fetch(o.offset) end def visit_Gem_SafeMarshal_Elements_UserDefined(o) register_object(call_method(resolve_class(o.name), :_load, o.binary_string)) end def visit_Gem_SafeMarshal_Elements_UserMarshal(o) klass = resolve_class(o.name) compat = COMPAT_CLASSES.fetch(klass, nil) idx = @objects.size object = register_object(call_method(compat || klass, :allocate)) push_stack :data ret = call_method(object, :marshal_load, visit(o.data)) if compat object = @objects[idx] = ret end object end def visit_Gem_SafeMarshal_Elements_Integer(i) i.int end def visit_Gem_SafeMarshal_Elements_Nil(_) nil end def visit_Gem_SafeMarshal_Elements_True(_) true end def visit_Gem_SafeMarshal_Elements_False(_) false end def visit_Gem_SafeMarshal_Elements_String(s) register_object(+s.str) end def visit_Gem_SafeMarshal_Elements_Float(f) register_object( case f.string when "inf" ::Float::INFINITY when "-inf" -::Float::INFINITY when "nan" ::Float::NAN else f.string.to_f end ) end def visit_Gem_SafeMarshal_Elements_Bignum(b) result = 0 b.data.each_byte.with_index do |byte, exp| result += (byte * 2**(exp * 8)) end case b.sign when 43 # ?+ result when 45 # ?- -result else raise FormatError, "Unexpected sign for Bignum #{b.sign.chr.inspect} (#{b.sign})" end end def visit_Gem_SafeMarshal_Elements_UserClass(r) if resolve_class(r.name) == ::Hash && r.wrapped_object.is_a?(Elements::Hash) hash = register_object({}.compare_by_identity) o = r.wrapped_object o.pairs.each_with_index do |(k, v), i| push_stack i k = visit(k) push_stack k hash[k] = visit(v) end if o.is_a?(Elements::HashWithDefaultValue) push_stack :default hash.default = visit(o.default) end hash else raise UnsupportedError.new("Unsupported user class #{resolve_class(r.name)} in marshal stream", stack: formatted_stack) end end def resolve_class(n) @class_cache[n] ||= begin to_s = resolve_symbol_name(n) raise UnpermittedClassError.new(name: to_s, stack: formatted_stack) unless @permitted_classes.include?(to_s) visit_symbol_type(n) begin ::Object.const_get(to_s) rescue NameError raise ArgumentError, "Undefined class #{to_s.inspect}" end end end class RationalCompat def marshal_load(s) num, den = s raise ArgumentError, "Expected 2 ints" unless s.size == 2 && num.is_a?(Integer) && den.is_a?(Integer) Rational(num, den) end end private_constant :RationalCompat COMPAT_CLASSES = {}.tap do |h| h[Rational] = RationalCompat end.compare_by_identity.freeze private_constant :COMPAT_CLASSES def resolve_ivar(klass, name) to_s = resolve_symbol_name(name) raise UnpermittedIvarError.new(symbol: to_s, klass: klass, stack: formatted_stack) unless @permitted_ivars.fetch(klass.name, [].freeze).include?(to_s) visit_symbol_type(name) end def visit_symbol_type(element) case element when Elements::Symbol sym = element.name.to_sym @symbols << sym sym when Elements::SymbolLink visit_Gem_SafeMarshal_Elements_SymbolLink(element) end end # This is a hot method, so avoid respond_to? checks on every invocation if :read.respond_to?(:name) def resolve_symbol_name(element) case element when Elements::Symbol element.name when Elements::SymbolLink visit_Gem_SafeMarshal_Elements_SymbolLink(element).name else raise FormatError, "Expected symbol or symbol link, got #{element.inspect} @ #{formatted_stack.join(".")}" end end else def resolve_symbol_name(element) case element when Elements::Symbol element.name when Elements::SymbolLink visit_Gem_SafeMarshal_Elements_SymbolLink(element).to_s else raise FormatError, "Expected symbol or symbol link, got #{element.inspect} @ #{formatted_stack.join(".")}" end end end def register_object(o) @objects << o o end def call_method(receiver, method, *args) receiver.__send__(method, *args) rescue NoMethodError => e raise unless e.receiver == receiver raise MethodCallError, "Unable to call #{method.inspect} on #{receiver.inspect}, perhaps it is a class using marshal compat, which is not visible in ruby? #{e}" end def formatted_stack formatted = [] @stack[0, @stack_idx].each do |e| if e.is_a?(Integer) if formatted.last == "ivar_" formatted[-1] = "ivar_#{e}" else formatted << "[#{e}]" end else formatted << e end end formatted end class Error < StandardError end class TimeTooLargeError < Error def initialize(message, stack:) super "#{message} @ #{stack.join "."}" end end class UnpermittedSymbolError < Error def initialize(symbol:, stack:) @symbol = symbol @stack = stack super "Attempting to load unpermitted symbol #{symbol.inspect} @ #{stack.join "."}" end end class UnpermittedIvarError < Error def initialize(symbol:, klass:, stack:) @symbol = symbol @klass = klass @stack = stack super "Attempting to set unpermitted ivar #{symbol.inspect} on object of class #{klass} @ #{stack.join "."}" end end class UnpermittedClassError < Error def initialize(name:, stack:) @name = name @stack = stack super "Attempting to load unpermitted class #{name.inspect} @ #{stack.join "."}" end end class UnsupportedError < Error def initialize(message, stack:) super "#{message} @ #{stack.join "."}" end end class FormatError < Error end class MethodCallError < Error end end end end PK!O%O%safe_marshal/reader.rbnu[# frozen_string_literal: true require_relative "elements" module Gem module SafeMarshal class Reader class Error < StandardError end class UnsupportedVersionError < Error end class UnconsumedBytesError < Error end class NotImplementedError < Error end class EOFError < Error end class DataTooShortError < Error end class NegativeLengthError < Error end def initialize(io) @io = io end def read! read_header root = read_element raise UnconsumedBytesError, "expected EOF, got #{@io.read(10).inspect}... after top-level element #{root.class}" unless @io.eof? root end private MARSHAL_VERSION = [Marshal::MAJOR_VERSION, Marshal::MINOR_VERSION].map(&:chr).join.freeze private_constant :MARSHAL_VERSION def read_header v = @io.read(2) raise UnsupportedVersionError, "Unsupported marshal version #{v.bytes.map(&:ord).join(".")}, expected #{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" unless v == MARSHAL_VERSION end def read_bytes(n) raise NegativeLengthError if n < 0 str = @io.read(n) raise EOFError, "expected #{n} bytes, got EOF" if str.nil? raise DataTooShortError, "expected #{n} bytes, got #{str.inspect}" unless str.bytesize == n str end def read_byte @io.getbyte || raise(EOFError, "Unexpected EOF") end def read_integer b = read_byte case b when 0x00 0 when 0x01 read_byte when 0x02 read_byte | (read_byte << 8) when 0x03 read_byte | (read_byte << 8) | (read_byte << 16) when 0x04 read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24) when 0xFC read_byte | (read_byte << 8) | (read_byte << 16) | (read_byte << 24) | -0x100000000 when 0xFD read_byte | (read_byte << 8) | (read_byte << 16) | -0x1000000 when 0xFE read_byte | (read_byte << 8) | -0x10000 when 0xFF read_byte | -0x100 else signed = (b ^ 128) - 128 if b >= 128 signed + 5 else signed - 5 end end end def read_element type = read_byte case type when 34 then read_string # ?" when 48 then read_nil # ?0 when 58 then read_symbol # ?: when 59 then read_symbol_link # ?; when 64 then read_object_link # ?@ when 70 then read_false # ?F when 73 then read_object_with_ivars # ?I when 84 then read_true # ?T when 85 then read_user_marshal # ?U when 91 then read_array # ?[ when 102 then read_float # ?f when 105 then Elements::Integer.new(read_integer) # ?i when 108 then read_bignum # ?l when 111 then read_object # ?o when 117 then read_user_defined # ?u when 123 then read_hash # ?{ when 125 then read_hash_with_default_value # ?} when 101 then read_extended_object # ?e when 99 then read_class # ?c when 109 then read_module # ?m when 77 then read_class_or_module # ?M when 100 then read_data # ?d when 47 then read_regexp # ?/ when 83 then read_struct # ?S when 67 then read_user_class # ?C else raise Error, "Unknown marshal type discriminator #{type.chr.inspect} (#{type})" end end STRING_E_SYMBOL = Elements::Symbol.new("E").freeze private_constant :STRING_E_SYMBOL def read_symbol len = read_integer if len == 1 byte = read_byte if byte == 69 # ?E STRING_E_SYMBOL else Elements::Symbol.new(byte.chr) end else name = read_bytes(len) Elements::Symbol.new(name) end end EMPTY_STRING = Elements::String.new("".b.freeze).freeze private_constant :EMPTY_STRING def read_string length = read_integer return EMPTY_STRING if length == 0 str = read_bytes(length) Elements::String.new(str) end def read_true Elements::True::TRUE end def read_false Elements::False::FALSE end def read_user_defined name = read_element binary_string = read_bytes(read_integer) Elements::UserDefined.new(name, binary_string) end EMPTY_ARRAY = Elements::Array.new([].freeze).freeze private_constant :EMPTY_ARRAY def read_array length = read_integer return EMPTY_ARRAY if length == 0 raise NegativeLengthError if length < 0 elements = Array.new(length) do read_element end Elements::Array.new(elements) end def read_object_with_ivars object = read_element length = read_integer raise NegativeLengthError if length < 0 ivars = Array.new(length) do [read_element, read_element] end Elements::WithIvars.new(object, ivars) end def read_symbol_link offset = read_integer Elements::SymbolLink.new(offset) end def read_user_marshal name = read_element data = read_element Elements::UserMarshal.new(name, data) end # profiling bundle install --full-index shows that # offset 6 is by far the most common object link, # so we special case it to avoid allocating a new # object a third of the time. # the following are all the object links that # appear more than 10000 times in my profiling OBJECT_LINKS = { 6 => Elements::ObjectLink.new(6).freeze, 30 => Elements::ObjectLink.new(30).freeze, 81 => Elements::ObjectLink.new(81).freeze, 34 => Elements::ObjectLink.new(34).freeze, 38 => Elements::ObjectLink.new(38).freeze, 50 => Elements::ObjectLink.new(50).freeze, 91 => Elements::ObjectLink.new(91).freeze, 42 => Elements::ObjectLink.new(42).freeze, 46 => Elements::ObjectLink.new(46).freeze, 150 => Elements::ObjectLink.new(150).freeze, 100 => Elements::ObjectLink.new(100).freeze, 104 => Elements::ObjectLink.new(104).freeze, 108 => Elements::ObjectLink.new(108).freeze, 242 => Elements::ObjectLink.new(242).freeze, 246 => Elements::ObjectLink.new(246).freeze, 139 => Elements::ObjectLink.new(139).freeze, 143 => Elements::ObjectLink.new(143).freeze, 114 => Elements::ObjectLink.new(114).freeze, 308 => Elements::ObjectLink.new(308).freeze, 200 => Elements::ObjectLink.new(200).freeze, 54 => Elements::ObjectLink.new(54).freeze, 62 => Elements::ObjectLink.new(62).freeze, 1_286_245 => Elements::ObjectLink.new(1_286_245).freeze, }.freeze private_constant :OBJECT_LINKS def read_object_link offset = read_integer OBJECT_LINKS[offset] || Elements::ObjectLink.new(offset) end EMPTY_HASH = Elements::Hash.new([].freeze).freeze private_constant :EMPTY_HASH def read_hash length = read_integer return EMPTY_HASH if length == 0 pairs = Array.new(length) do [read_element, read_element] end Elements::Hash.new(pairs) end def read_hash_with_default_value length = read_integer raise NegativeLengthError if length < 0 pairs = Array.new(length) do [read_element, read_element] end default = read_element Elements::HashWithDefaultValue.new(pairs, default) end def read_object name = read_element object = Elements::Object.new(name) length = read_integer raise NegativeLengthError if length < 0 ivars = Array.new(length) do [read_element, read_element] end Elements::WithIvars.new(object, ivars) end def read_nil Elements::Nil::NIL end def read_float string = read_bytes(read_integer) Elements::Float.new(string) end def read_bignum sign = read_byte data = read_bytes(read_integer * 2) Elements::Bignum.new(sign, data) end def read_extended_object raise NotImplementedError, "Reading Marshal objects of type extended_object is not implemented" end def read_class raise NotImplementedError, "Reading Marshal objects of type class is not implemented" end def read_module raise NotImplementedError, "Reading Marshal objects of type module is not implemented" end def read_class_or_module raise NotImplementedError, "Reading Marshal objects of type class_or_module is not implemented" end def read_data raise NotImplementedError, "Reading Marshal objects of type data is not implemented" end def read_regexp raise NotImplementedError, "Reading Marshal objects of type regexp is not implemented" end def read_struct raise NotImplementedError, "Reading Marshal objects of type struct is not implemented" end def read_user_class name = read_element wrapped_object = read_element Elements::UserClass.new(name, wrapped_object) end end end end PK!HJ8 safe_marshal/elements.rbnu[# frozen_string_literal: true module Gem module SafeMarshal module Elements class Element end class Symbol < Element def initialize(name) @name = name end attr_reader :name end class UserDefined < Element def initialize(name, binary_string) @name = name @binary_string = binary_string end attr_reader :name, :binary_string end class UserMarshal < Element def initialize(name, data) @name = name @data = data end attr_reader :name, :data end class String < Element def initialize(str) @str = str end attr_reader :str end class Hash < Element def initialize(pairs) @pairs = pairs end attr_reader :pairs end class HashWithDefaultValue < Hash def initialize(pairs, default) super(pairs) @default = default end attr_reader :default end class Array < Element def initialize(elements) @elements = elements end attr_reader :elements end class Integer < Element def initialize(int) @int = int end attr_reader :int end class True < Element def initialize end TRUE = new.freeze end class False < Element def initialize end FALSE = new.freeze end class WithIvars < Element def initialize(object, ivars) @object = object @ivars = ivars end attr_reader :object, :ivars end class Object < Element def initialize(name) @name = name end attr_reader :name end class Nil < Element NIL = new.freeze end class ObjectLink < Element def initialize(offset) @offset = offset end attr_reader :offset end class SymbolLink < Element def initialize(offset) @offset = offset end attr_reader :offset end class Float < Element def initialize(string) @string = string end attr_reader :string end class Bignum < Element # rubocop:disable Lint/UnifiedInteger def initialize(sign, data) @sign = sign @data = data end attr_reader :sign, :data end class UserClass < Element def initialize(name, wrapped_object) @name = name @wrapped_object = wrapped_object end attr_reader :name, :wrapped_object end end end end PK!?gemspec_helpers.rbnu[# frozen_string_literal: true require_relative "../rubygems" ## # Mixin methods for commands that work with gemspecs. module Gem::GemspecHelpers def find_gemspec(glob = "*.gemspec") gemspecs = Dir.glob(glob).sort if gemspecs.size > 1 alert_error "Multiple gemspecs found: #{gemspecs}, please specify one" terminate_interaction(1) end gemspecs.first end end PK!!{L0L0uninstaller.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require "fileutils" require_relative "../rubygems" require_relative "installer_uninstaller_utils" require_relative "dependency_list" require_relative "user_interaction" ## # An Uninstaller. # # The uninstaller fires pre and post uninstall hooks. Hooks can be added # either through a rubygems_plugin.rb file in an installed gem or via a # rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb # file. See Gem.pre_uninstall and Gem.post_uninstall for details. class Gem::Uninstaller include Gem::UserInteraction include Gem::InstallerUninstallerUtils ## # The directory a gem's executables will be installed into attr_reader :bin_dir ## # The gem repository the gem will be uninstalled from attr_reader :gem_home ## # The Gem::Specification for the gem being uninstalled, only set during # #uninstall_gem attr_reader :spec ## # Constructs an uninstaller that will uninstall gem named +gem+. # +options+ is a Hash with the following keys: # # :version:: Version requirement for the gem to uninstall. If not specified, # uses Gem::Requirement.default. # :install_dir:: The directory where the gem is installed. If not specified, # uses Gem.dir. # :executables:: Whether executables should be removed without confirmation or not. If nil, asks the user explicitly. # :all:: If more than one version matches the requirement, whether to forcefully remove all matching versions or ask the user to select specific matching versions that should be removed. # :ignore:: Ignore broken dependency checks when uninstalling. # :bin_dir:: Directory containing executables to remove. If not specified, # uses Gem.bindir. # :format_executable:: In order to find executables to be removed, format executable names using Gem::Installer.exec_format. # :abort_on_dependent:: Directly abort uninstallation if dependencies would be broken, rather than asking the user for confirmation. # :check_dev:: When checking if uninstalling gem would leave broken dependencies around, also consider development dependencies. # :force:: Set both :all and :ignore to true for forced uninstallation. # :user_install:: Uninstall from user gem directory instead of system directory. def initialize(gem, options = {}) @gem = gem @version = options[:version] || Gem::Requirement.default @install_dir = options[:install_dir] @gem_home = File.realpath(@install_dir || Gem.dir) @user_dir = File.exist?(Gem.user_dir) ? File.realpath(Gem.user_dir) : Gem.user_dir @force_executables = options[:executables] @force_all = options[:all] @force_ignore = options[:ignore] @bin_dir = options[:bin_dir] @format_executable = options[:format_executable] @abort_on_dependent = options[:abort_on_dependent] @check_dev = options[:check_dev] if options[:force] @force_all = true @force_ignore = true end # only add user directory if install_dir is not set @user_install = false @user_install = options[:user_install] unless @install_dir # Optimization: populated during #uninstall @default_specs_matching_uninstall_params = [] end ## # Performs the uninstall of the gem. This removes the spec, the Gem # directory, and the cached .gem file. def uninstall dependency = Gem::Dependency.new @gem, @version list = [] specification_record.stubs.each do |spec| next unless dependency.matches_spec? spec list << spec end if list.empty? raise Gem::InstallError, "gem #{@gem.inspect} is not installed" end default_specs, list = list.partition(&:default_gem?) warn_cannot_uninstall_default_gems(default_specs - list) @default_specs_matching_uninstall_params = default_specs.map(&:to_spec) list, other_repo_specs = list.partition do |spec| @gem_home == spec.base_dir || (@user_install && spec.base_dir == @user_dir) end list.sort! if list.empty? return unless other_repo_specs.any? other_repos = other_repo_specs.map(&:base_dir).uniq message = ["#{@gem} is not installed in GEM_HOME, try:"] message.concat other_repos.map {|repo| "\tgem uninstall -i #{repo} #{@gem}" } raise Gem::InstallError, message.join("\n") elsif @force_all remove_all list elsif list.size > 1 gem_names = list.map(&:full_name_with_location) gem_names << "All versions" say _, index = choose_from_list "Select gem to uninstall:", gem_names if index == list.size remove_all list elsif index && index >= 0 && index < list.size uninstall_gem list[index] else say "Error: must enter a number [1-#{list.size + 1}]" end else uninstall_gem list.first end end ## # Uninstalls gem +spec+ def uninstall_gem(stub) spec = stub.to_spec @spec = spec unless dependencies_ok? spec if abort_on_dependent? || !ask_if_ok(spec) raise Gem::DependencyRemovalException, "Uninstallation aborted due to dependent gem(s)" end end Gem.pre_uninstall_hooks.each do |hook| hook.call self end remove_executables @spec remove_plugins @spec remove @spec specification_record.remove_spec(stub) regenerate_plugins Gem.post_uninstall_hooks.each do |hook| hook.call self end @spec = nil end ## # Removes installed executables and batch files (windows only) for +spec+. def remove_executables(spec) return if spec.executables.empty? || default_spec_matches?(spec) executables = spec.executables.clone # Leave any executables created by other installed versions # of this gem installed. list = Gem::Specification.find_all do |s| s.name == spec.name && s.version != spec.version end list.each do |s| s.executables.each do |exe_name| executables.delete exe_name end end return if executables.empty? executables = executables.map {|exec| formatted_program_filename exec } remove = if @force_executables.nil? ask_yes_no("Remove executables:\n" \ "\t#{executables.join ", "}\n\n" \ "in addition to the gem?", true) else @force_executables end if remove bin_dir = @bin_dir || Gem.bindir(spec.base_dir) raise Gem::FilePermissionError, bin_dir unless File.writable? bin_dir executables.each do |exe_name| say "Removing #{exe_name}" exe_file = File.join bin_dir, exe_name safe_delete { FileUtils.rm exe_file } safe_delete { FileUtils.rm "#{exe_file}.bat" } end else say "Executables and scripts will remain installed." end end ## # Removes all gems in +list+. # # NOTE: removes uninstalled gems from +list+. def remove_all(list) list.each {|spec| uninstall_gem spec } end ## # spec:: the spec of the gem to be uninstalled def remove(spec) unless path_ok?(@gem_home, spec) || (@user_install && path_ok?(@user_dir, spec)) e = Gem::GemNotInHomeException.new \ "Gem '#{spec.full_name}' is not installed in directory #{@gem_home}" e.spec = spec raise e end raise Gem::FilePermissionError, spec.base_dir unless File.writable?(spec.base_dir) full_gem_path = spec.full_gem_path exclusions = [] if default_spec_matches?(spec) && spec.executables.any? exclusions = spec.executables.map {|exe| File.join(spec.bin_dir, exe) } exclusions << File.dirname(exclusions.last) until exclusions.last == full_gem_path end safe_delete { rm_r full_gem_path, exclusions: exclusions } safe_delete { FileUtils.rm_r spec.extension_dir } old_platform_name = spec.original_name gem = spec.cache_file gem = File.join(spec.cache_dir, "#{old_platform_name}.gem") unless File.exist? gem safe_delete { FileUtils.rm_r gem } begin Gem::RDoc.new(spec).remove rescue NameError end gemspec = spec.spec_file unless File.exist? gemspec gemspec = File.join(File.dirname(gemspec), "#{old_platform_name}.gemspec") end safe_delete { FileUtils.rm_r gemspec } announce_deletion_of(spec) end ## # Remove any plugin wrappers for +spec+. def remove_plugins(spec) # :nodoc: return if spec.plugins.empty? remove_plugins_for(spec, plugin_dir_for(spec)) end ## # Regenerates plugin wrappers after removal. def regenerate_plugins latest = specification_record.latest_spec_for(@spec.name) return if latest.nil? regenerate_plugins_for(latest, plugin_dir_for(@spec)) end ## # Is +spec+ in +gem_dir+? def path_ok?(gem_dir, spec) full_path = File.join gem_dir, "gems", spec.full_name original_path = File.join gem_dir, "gems", spec.original_name full_path == spec.full_gem_path || original_path == spec.full_gem_path end ## # Returns true if it is OK to remove +spec+ or this is a forced # uninstallation. def dependencies_ok?(spec) # :nodoc: return true if @force_ignore deplist = Gem::DependencyList.from_specs deplist.ok_to_remove?(spec.full_name, @check_dev) end ## # Should the uninstallation abort if a dependency will go unsatisfied? # # See ::new. def abort_on_dependent? # :nodoc: @abort_on_dependent end ## # Asks if it is OK to remove +spec+. Returns true if it is OK. def ask_if_ok(spec) # :nodoc: msg = [""] msg << "You have requested to uninstall the gem:" msg << "\t#{spec.full_name}" msg << "" siblings = Gem::Specification.select do |s| s.name == spec.name && s.full_name != spec.full_name end spec.dependent_gems(@check_dev).each do |dep_spec, dep, _satlist| unless siblings.any? {|s| s.satisfies_requirement? dep } msg << "#{dep_spec.name}-#{dep_spec.version} depends on #{dep}" end end msg << "If you remove this gem, these dependencies will not be met." msg << "Continue with Uninstall?" ask_yes_no(msg.join("\n"), false) end ## # Returns the formatted version of the executable +filename+ def formatted_program_filename(filename) # :nodoc: # TODO perhaps the installer should leave a small manifest # of what it did for us to find rather than trying to recreate # it again. if @format_executable require_relative "installer" Gem::Installer.exec_format % File.basename(filename) else filename end end def safe_delete(&block) block.call rescue Errno::ENOENT nil rescue Errno::EPERM e = Gem::UninstallError.new e.spec = @spec raise e end private def rm_r(path, exclusions:) FileUtils::Entry_.new(path).postorder_traverse do |ent| ent.remove unless exclusions.include?(ent.path) end end def specification_record @specification_record ||= @install_dir ? Gem::SpecificationRecord.from_path(@install_dir) : Gem::Specification.specification_record end def announce_deletion_of(spec) name = spec.full_name say "Successfully uninstalled #{name}" if default_spec_matches?(spec) say( "There was both a regular copy and a default copy of #{name}. The " \ "regular copy was successfully uninstalled, but the default copy " \ "was left around because default gems can't be removed." ) end end # @return true if the specs of any default gems are `==` to the given `spec`. def default_spec_matches?(spec) !default_specs_that_match(spec).empty? end # @return [Array] specs of default gems that are `==` to the given `spec`. def default_specs_that_match(spec) @default_specs_matching_uninstall_params.select {|default_spec| spec == default_spec } end def warn_cannot_uninstall_default_gems(specs) specs.each do |spec| say "Gem #{spec.full_name} cannot be uninstalled because it is a default gem" end end def plugin_dir_for(spec) Gem.plugindir(spec.base_dir) end end PK!dONNuri_formatter.rbnu[# frozen_string_literal: true ## # The UriFormatter handles URIs from user-input and escaping. # # uf = Gem::UriFormatter.new 'example.com' # # p uf.normalize #=> 'http://example.com' class Gem::UriFormatter ## # The URI to be formatted. attr_reader :uri ## # Creates a new URI formatter for +uri+. def initialize(uri) require "cgi/escape" require "cgi/util" unless defined?(CGI::EscapeExt) @uri = uri end ## # Escapes the #uri for use as a CGI parameter def escape return unless @uri CGI.escape @uri end ## # Normalize the URI by adding "http://" if it is missing. def normalize /^(https?|ftp|file):/i.match?(@uri) ? @uri : "http://#{@uri}" end ## # Unescapes the #uri which came from a CGI parameter def unescape return unless @uri CGI.unescape @uri end end PK!k{yypackage/tar_reader/entry.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## # Class for reading entries out of a tar file class Gem::Package::TarReader::Entry ## # Creates a new tar entry for +header+ that will be read from +io+ # If a block is given, the entry is yielded and then closed. def self.open(header, io, &block) entry = new header, io return entry unless block_given? begin yield entry ensure entry.close end end ## # Header for this tar entry attr_reader :header ## # Creates a new tar entry for +header+ that will be read from +io+ def initialize(header, io) @closed = false @header = header @io = io @orig_pos = @io.pos @end_pos = @orig_pos + @header.size @read = 0 end def check_closed # :nodoc: raise IOError, "closed #{self.class}" if closed? end ## # Number of bytes read out of the tar entry def bytes_read @read end ## # Closes the tar entry def close return if closed? # Seek to the end of the entry if it wasn't fully read seek(0, IO::SEEK_END) # discard trailing zeros skip = (512 - (@header.size % 512)) % 512 @io.read(skip) @closed = true nil end ## # Is the tar entry closed? def closed? @closed end ## # Are we at the end of the tar entry? def eof? check_closed @read >= @header.size end ## # Full name of the tar entry def full_name @header.full_name.force_encoding(Encoding::UTF_8) rescue ArgumentError => e raise unless e.message == "string contains null byte" raise Gem::Package::TarInvalidError, "tar is corrupt, name contains null byte" end ## # Read one byte from the tar entry def getc return nil if eof? ret = @io.getc @read += 1 if ret ret end ## # Is this tar entry a directory? def directory? @header.typeflag == "5" end ## # Is this tar entry a file? def file? @header.typeflag == "0" end ## # Is this tar entry a symlink? def symlink? @header.typeflag == "2" end ## # The position in the tar entry def pos check_closed bytes_read end ## # Seek to the position in the tar entry def pos=(new_pos) seek(new_pos, IO::SEEK_SET) end def size @header.size end alias_method :length, :size ## # Reads +maxlen+ bytes from the tar file entry, or the rest of the entry if nil def read(maxlen = nil) if eof? return maxlen.to_i.zero? ? "" : nil end max_read = [maxlen, @header.size - @read].compact.min ret = @io.read max_read if ret.nil? return maxlen ? nil : "" # IO.read returns nil on EOF with len argument end @read += ret.size ret end def readpartial(maxlen, outbuf = "".b) if eof? && maxlen > 0 raise EOFError, "end of file reached" end max_read = [maxlen, @header.size - @read].min @io.readpartial(max_read, outbuf) @read += outbuf.size outbuf end ## # Seeks to +offset+ bytes into the tar file entry # +whence+ can be IO::SEEK_SET, IO::SEEK_CUR, or IO::SEEK_END def seek(offset, whence = IO::SEEK_SET) check_closed new_pos = case whence when IO::SEEK_SET then @orig_pos + offset when IO::SEEK_CUR then @io.pos + offset when IO::SEEK_END then @end_pos + offset else raise ArgumentError, "invalid whence" end if new_pos < @orig_pos new_pos = @orig_pos elsif new_pos > @end_pos new_pos = @end_pos end pending = new_pos - @io.pos return 0 if pending == 0 if @io.respond_to?(:seek) begin # avoid reading if the @io supports seeking @io.seek new_pos, IO::SEEK_SET pending = 0 rescue Errno::EINVAL end end # if seeking isn't supported or failed # negative seek requires that we rewind and read if pending < 0 @io.rewind pending = new_pos end while pending > 0 do size_read = @io.read([pending, 4096].min)&.size raise(EOFError, "end of file reached") if size_read.nil? pending -= size_read end @read = @io.pos - @orig_pos 0 end ## # Rewinds to the beginning of the tar file entry def rewind check_closed seek(0, IO::SEEK_SET) end end PK!Spackage/tar_reader.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## # TarReader reads tar files and allows iteration over their items class Gem::Package::TarReader include Enumerable ## # Creates a new TarReader on +io+ and yields it to the block, if given. def self.new(io) reader = super return reader unless block_given? begin yield reader ensure reader.close end nil end attr_reader :io # :nodoc: ## # Creates a new tar file reader on +io+ which needs to respond to #pos, # #eof?, #read, #getc and #pos= def initialize(io) @io = io @init_pos = io.pos end ## # Close the tar file def close end ## # Iterates over files in the tarball yielding each entry def each return enum_for __method__ unless block_given? until @io.eof? do begin header = Gem::Package::TarHeader.from @io rescue ArgumentError => e # Specialize only exceptions from Gem::Package::TarHeader.strict_oct raise e unless e.message.match?(/ is not an octal string$/) raise Gem::Package::TarInvalidError, e.message end return if header.empty? entry = Gem::Package::TarReader::Entry.new header, @io yield entry entry.close end end alias_method :each_entry, :each ## # NOTE: Do not call #rewind during #each def rewind if @init_pos == 0 @io.rewind else @io.pos = @init_pos end end ## # Seeks through the tar file until it finds the +entry+ with +name+ and # yields it. Rewinds the tar file to the beginning when the block # terminates. def seek(name) # :yields: entry found = find do |entry| entry.full_name == name end return unless found yield found ensure rewind end end require_relative "tar_reader/entry" PK!5aapackage/tar_writer.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## # Allows writing of tar files class Gem::Package::TarWriter class FileOverflow < StandardError; end ## # IO wrapper that allows writing a limited amount of data class BoundedStream ## # Maximum number of bytes that can be written attr_reader :limit ## # Number of bytes written attr_reader :written ## # Wraps +io+ and allows up to +limit+ bytes to be written def initialize(io, limit) @io = io @limit = limit @written = 0 end ## # Writes +data+ onto the IO, raising a FileOverflow exception if the # number of bytes will be more than #limit def write(data) if data.bytesize + @written > @limit raise FileOverflow, "You tried to feed more data than fits in the file." end @io.write data @written += data.bytesize data.bytesize end end ## # IO wrapper that provides only #write class RestrictedStream ## # Creates a new RestrictedStream wrapping +io+ def initialize(io) @io = io end ## # Writes +data+ onto the IO def write(data) @io.write data end end ## # Creates a new TarWriter, yielding it if a block is given def self.new(io) writer = super return writer unless block_given? begin yield writer ensure writer.close end nil end ## # Creates a new TarWriter that will write to +io+ def initialize(io) @io = io @closed = false end ## # Adds file +name+ with permissions +mode+ and mtime +mtime+ (sets # Gem.source_date_epoch if not specified), and yields an IO for # writing the file to def add_file(name, mode, mtime = nil) # :yields: io check_closed name, prefix = split_name name init_pos = @io.pos @io.write Gem::Package::TarHeader::EMPTY_HEADER # placeholder for the header yield RestrictedStream.new(@io) if block_given? size = @io.pos - init_pos - 512 remainder = (512 - (size % 512)) % 512 @io.write "\0" * remainder final_pos = @io.pos @io.pos = init_pos header = Gem::Package::TarHeader.new name: name, mode: mode, size: size, prefix: prefix, mtime: mtime || Gem.source_date_epoch @io.write header @io.pos = final_pos self end ## # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing # the file. The +digest_algorithm+ is written to a read-only +name+.sum # file following the given file contents containing the digest name and # hexdigest separated by a tab. # # The created digest object is returned. def add_file_digest(name, mode, digest_algorithms) # :yields: io digests = digest_algorithms.map do |digest_algorithm| digest = digest_algorithm.new digest_name = if digest.respond_to? :name digest.name else digest_algorithm.class.name[/::([^:]+)\z/, 1] end [digest_name, digest] end digests = Hash[*digests.flatten] add_file name, mode do |io| Gem::Package::DigestIO.wrap io, digests do |digest_io| yield digest_io end end digests end ## # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing # the file. The +signer+ is used to add a digest file using its # digest_algorithm per add_file_digest and a cryptographic signature in # +name+.sig. If the signer has no key only the checksum file is added. # # Returns the digest. def add_file_signed(name, mode, signer) digest_algorithms = [ signer.digest_algorithm, Gem::Security.create_digest("SHA512"), ].compact.uniq digests = add_file_digest name, mode, digest_algorithms do |io| yield io end signature_digest = digests.values.compact.find do |digest| digest_name = if digest.respond_to? :name digest.name else digest.class.name[/::([^:]+)\z/, 1] end digest_name == signer.digest_name end raise "no #{signer.digest_name} in #{digests.values.compact}" unless signature_digest if signer.key signature = signer.sign signature_digest.digest add_file_simple "#{name}.sig", 0o444, signature.length do |io| io.write signature end end digests end ## # Add file +name+ with permissions +mode+ +size+ bytes long. Yields an IO # to write the file to. def add_file_simple(name, mode, size) # :yields: io check_closed name, prefix = split_name name header = Gem::Package::TarHeader.new(name: name, mode: mode, size: size, prefix: prefix, mtime: Gem.source_date_epoch).to_s @io.write header os = BoundedStream.new @io, size yield os if block_given? min_padding = size - os.written @io.write("\0" * min_padding) remainder = (512 - (size % 512)) % 512 @io.write("\0" * remainder) self end ## # Adds symlink +name+ with permissions +mode+, linking to +target+. def add_symlink(name, target, mode) check_closed name, prefix = split_name name header = Gem::Package::TarHeader.new(name: name, mode: mode, size: 0, typeflag: "2", linkname: target, prefix: prefix, mtime: Gem.source_date_epoch).to_s @io.write header self end ## # Raises IOError if the TarWriter is closed def check_closed raise IOError, "closed #{self.class}" if closed? end ## # Closes the TarWriter def close check_closed @io.write "\0" * 1024 flush @closed = true end ## # Is the TarWriter closed? def closed? @closed end ## # Flushes the TarWriter's IO def flush check_closed @io.flush if @io.respond_to? :flush end ## # Creates a new directory in the tar file +name+ with +mode+ def mkdir(name, mode) check_closed name, prefix = split_name(name) header = Gem::Package::TarHeader.new name: name, mode: mode, typeflag: "5", size: 0, prefix: prefix, mtime: Gem.source_date_epoch @io.write header self end ## # Splits +name+ into a name and prefix that can fit in the TarHeader def split_name(name) # :nodoc: if name.bytesize > 256 raise Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)") end prefix = "" if name.bytesize > 100 parts = name.split("/", -1) # parts are never empty here name = parts.pop # initially empty for names with a trailing slash ("foo/.../bar/") prefix = parts.join("/") # if empty, then it's impossible to split (parts is empty too) while !parts.empty? && (prefix.bytesize > 155 || name.empty?) name = parts.pop + "/" + name prefix = parts.join("/") end if name.bytesize > 100 || prefix.empty? raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)") end if prefix.bytesize > 155 raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)") end end [name, prefix] end end PK!^x{HHpackage/source.rbnu[# frozen_string_literal: true class Gem::Package::Source # :nodoc: end PK!bbpackage/file_source.rbnu[# frozen_string_literal: true ## # The primary source of gems is a file on disk, including all usages # internal to rubygems. # # This is a private class, do not depend on it directly. Instead, pass a path # object to `Gem::Package.new`. class Gem::Package::FileSource < Gem::Package::Source # :nodoc: all attr_reader :path def initialize(path) @path = path end def start @start ||= File.read path, 20 end def present? File.exist? path end def with_write_io(&block) File.open path, "wb", &block end def with_read_io(&block) File.open path, "rb", &block end end PK!lSpackage/tar_header.rbnu[# frozen_string_literal: true # rubocop:disable Style/AsciiComments # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. # rubocop:enable Style/AsciiComments ## #-- # struct tarfile_entry_posix { # char name[100]; # ASCII + (Z unless filled) # char mode[8]; # 0 padded, octal, null # char uid[8]; # ditto # char gid[8]; # ditto # char size[12]; # 0 padded, octal, null # char mtime[12]; # 0 padded, octal, null # char checksum[8]; # 0 padded, octal, null, space # char typeflag[1]; # file: "0" dir: "5" # char linkname[100]; # ASCII + (Z unless filled) # char magic[6]; # "ustar\0" # char version[2]; # "00" # char uname[32]; # ASCIIZ # char gname[32]; # ASCIIZ # char devmajor[8]; # 0 padded, octal, null # char devminor[8]; # o padded, octal, null # char prefix[155]; # ASCII + (Z unless filled) # }; #++ # A header for a tar file class Gem::Package::TarHeader ## # Fields in the tar header FIELDS = [ :checksum, :devmajor, :devminor, :gid, :gname, :linkname, :magic, :mode, :mtime, :name, :prefix, :size, :typeflag, :uid, :uname, :version, ].freeze ## # Pack format for a tar header PACK_FORMAT = ("a100" + # name "a8" + # mode "a8" + # uid "a8" + # gid "a12" + # size "a12" + # mtime "a7a" + # chksum "a" + # typeflag "a100" + # linkname "a6" + # magic "a2" + # version "a32" + # uname "a32" + # gname "a8" + # devmajor "a8" + # devminor "a155").freeze # prefix ## # Unpack format for a tar header UNPACK_FORMAT = ("A100" + # name "A8" + # mode "A8" + # uid "A8" + # gid "A12" + # size "A12" + # mtime "A8" + # checksum "A" + # typeflag "A100" + # linkname "A6" + # magic "A2" + # version "A32" + # uname "A32" + # gname "A8" + # devmajor "A8" + # devminor "A155").freeze # prefix attr_reader(*FIELDS) EMPTY_HEADER = ("\0" * 512).b.freeze # :nodoc: ## # Creates a tar header from IO +stream+ def self.from(stream) header = stream.read 512 return EMPTY if header == EMPTY_HEADER fields = header.unpack UNPACK_FORMAT new name: fields.shift, mode: strict_oct(fields.shift), uid: oct_or_256based(fields.shift), gid: oct_or_256based(fields.shift), size: strict_oct(fields.shift), mtime: strict_oct(fields.shift), checksum: strict_oct(fields.shift), typeflag: fields.shift, linkname: fields.shift, magic: fields.shift, version: strict_oct(fields.shift), uname: fields.shift, gname: fields.shift, devmajor: strict_oct(fields.shift), devminor: strict_oct(fields.shift), prefix: fields.shift, empty: false end def self.strict_oct(str) str.strip! return str.oct if /\A[0-7]*\z/.match?(str) raise ArgumentError, "#{str.inspect} is not an octal string" end def self.oct_or_256based(str) # \x80 flags a positive 256-based number # \ff flags a negative 256-based number # In case we have a match, parse it as a signed binary value # in big-endian order, except that the high-order bit is ignored. return str.unpack1("@4N") if /\A[\x80\xff]/n.match?(str) strict_oct(str) end ## # Creates a new TarHeader using +vals+ def initialize(vals) unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] raise ArgumentError, ":name, :size, :prefix and :mode required" end @checksum = vals[:checksum] || "" @devmajor = vals[:devmajor] || 0 @devminor = vals[:devminor] || 0 @gid = vals[:gid] || 0 @gname = vals[:gname] || "wheel" @linkname = vals[:linkname] @magic = vals[:magic] || "ustar" @mode = vals[:mode] @mtime = vals[:mtime] || 0 @name = vals[:name] @prefix = vals[:prefix] @size = vals[:size] @typeflag = vals[:typeflag] @typeflag = "0" if @typeflag.nil? || @typeflag.empty? @uid = vals[:uid] || 0 @uname = vals[:uname] || "wheel" @version = vals[:version] || "00" @empty = vals[:empty] end EMPTY = new({ # :nodoc: checksum: 0, gname: "", linkname: "", magic: "", mode: 0, name: "", prefix: "", size: 0, uname: "", version: 0, empty: true, }).freeze private_constant :EMPTY ## # Is the tar entry empty? def empty? @empty end def ==(other) # :nodoc: self.class === other && @checksum == other.checksum && @devmajor == other.devmajor && @devminor == other.devminor && @gid == other.gid && @gname == other.gname && @linkname == other.linkname && @magic == other.magic && @mode == other.mode && @mtime == other.mtime && @name == other.name && @prefix == other.prefix && @size == other.size && @typeflag == other.typeflag && @uid == other.uid && @uname == other.uname && @version == other.version end def to_s # :nodoc: update_checksum header end ## # Updates the TarHeader's checksum def update_checksum header = header " " * 8 @checksum = oct calculate_checksum(header), 6 end ## # Header's full name, including prefix def full_name if prefix != "" File.join prefix, name else name end end private def calculate_checksum(header) header.sum(0) end def header(checksum = @checksum) header = [ name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), oct(mtime, 11), checksum, " ", typeflag, linkname, magic, oct(version, 2), uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix, ] header = header.pack PACK_FORMAT header.ljust 512, "\0" end def oct(num, len) format("%0#{len}o", num) end end PK!package/old.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ ## # The format class knows the guts of the ancient .gem file format and provides # the capability to read such ancient gems. # # Please pretend this doesn't exist. class Gem::Package::Old < Gem::Package undef_method :spec= ## # Creates a new old-format package reader for +gem+. Old-format packages # cannot be written. def initialize(gem, security_policy) require "fileutils" require "zlib" Gem.load_yaml @contents = nil @gem = gem @security_policy = security_policy @spec = nil end ## # A list of file names contained in this gem def contents verify return @contents if @contents @gem.with_read_io do |io| read_until_dashes io # spec header = file_list io @contents = header.map {|file| file["path"] } end end ## # Extracts the files in this package into +destination_dir+ def extract_files(destination_dir) verify errstr = "Error reading files from gem" @gem.with_read_io do |io| read_until_dashes io # spec header = file_list io raise Gem::Exception, errstr unless header header.each do |entry| full_name = entry["path"] destination = install_location full_name, destination_dir file_data = String.new read_until_dashes io do |line| file_data << line end file_data = file_data.strip.unpack1("m") file_data = Zlib::Inflate.inflate file_data raise Gem::Package::FormatError, "#{full_name} in #{@gem} is corrupt" if file_data.length != entry["size"].to_i FileUtils.rm_rf destination FileUtils.mkdir_p File.dirname(destination), mode: dir_mode && 0o755 File.open destination, "wb", file_mode(entry["mode"]) do |out| out.write file_data end verbose destination end end rescue Zlib::DataError raise Gem::Exception, errstr end ## # Reads the file list section from the old-format gem +io+ def file_list(io) # :nodoc: header = String.new read_until_dashes io do |line| header << line end Gem::SafeYAML.safe_load header end ## # Reads lines until a "---" separator is found def read_until_dashes(io) # :nodoc: while (line = io.gets) && line.chomp.strip != "---" do yield line if block_given? end end ## # Skips the Ruby self-install header in +io+. def skip_ruby(io) # :nodoc: loop do line = io.gets return if line.chomp == "__END__" break unless line end raise Gem::Exception, "Failed to find end of Ruby script while reading gem" end ## # The specification for this gem def spec verify return @spec if @spec yaml = String.new @gem.with_read_io do |io| skip_ruby io read_until_dashes io do |line| yaml << line end end begin @spec = Gem::Specification.from_yaml yaml rescue Psych::SyntaxError raise Gem::Exception, "Failed to parse gem specification out of gem file" end rescue ArgumentError raise Gem::Exception, "Failed to parse gem specification out of gem file" end ## # Raises an exception if a security policy that verifies data is active. # Old format gems cannot be verified as signed. def verify return true unless @security_policy raise Gem::Security::Exception, "old format gems do not contain signatures and cannot be verified" if @security_policy.verify_data true end end PK!䌸..package/io_source.rbnu[# frozen_string_literal: true ## # Supports reading and writing gems from/to a generic IO object. This is # useful for other applications built on top of rubygems, such as # rubygems.org. # # This is a private class, do not depend on it directly. Instead, pass an IO # object to `Gem::Package.new`. class Gem::Package::IOSource < Gem::Package::Source # :nodoc: all attr_reader :io def initialize(io) @io = io end def start @start ||= begin if io.pos > 0 raise Gem::Package::Error, "Cannot read start unless IO is at start" end value = io.read 20 io.rewind value end end def present? true end def with_read_io yield io ensure io.rewind end def with_write_io yield io ensure io.rewind end def path end end PK!c_MMpackage/digest_io.rbnu[# frozen_string_literal: true ## # IO wrapper that creates digests of contents written to the IO it wraps. class Gem::Package::DigestIO ## # Collected digests for wrapped writes. # # { # 'SHA1' => #, # 'SHA512' => #, # } attr_reader :digests ## # Wraps +io+ and updates digest for each of the digest algorithms in # the +digests+ Hash. Returns the digests hash. Example: # # io = StringIO.new # digests = { # 'SHA1' => OpenSSL::Digest.new('SHA1'), # 'SHA512' => OpenSSL::Digest.new('SHA512'), # } # # Gem::Package::DigestIO.wrap io, digests do |digest_io| # digest_io.write "hello" # end # # digests['SHA1'].hexdigest #=> "aaf4c61d[...]" # digests['SHA512'].hexdigest #=> "9b71d224[...]" def self.wrap(io, digests) digest_io = new io, digests yield digest_io digests end ## # Creates a new DigestIO instance. Using ::wrap is recommended, see the # ::wrap documentation for documentation of +io+ and +digests+. def initialize(io, digests) @io = io @digests = digests end ## # Writes +data+ to the underlying IO and updates the digests def write(data) result = @io.write data @digests.each do |_, digest| digest << data end result end end PK!3Lj defaults.rbnu[# frozen_string_literal: true module Gem DEFAULT_HOST = "https://rubygems.org" @post_install_hooks ||= [] @done_installing_hooks ||= [] @post_uninstall_hooks ||= [] @pre_uninstall_hooks ||= [] @pre_install_hooks ||= [] ## # An Array of the default sources that come with RubyGems def self.default_sources @default_sources ||= %w[https://rubygems.org/] end ## # Default spec directory path to be used if an alternate value is not # specified in the environment def self.default_spec_cache_dir default_spec_cache_dir = File.join Gem.user_home, ".gem", "specs" unless File.exist?(default_spec_cache_dir) default_spec_cache_dir = File.join Gem.cache_home, "gem", "specs" end default_spec_cache_dir end ## # Default home directory path to be used if an alternate value is not # specified in the environment def self.default_dir @default_dir ||= File.join(RbConfig::CONFIG["rubylibprefix"], "gems", RbConfig::CONFIG["ruby_version_dir_name"] || RbConfig::CONFIG["ruby_version"]) end ## # Returns binary extensions dir for specified RubyGems base dir or nil # if such directory cannot be determined. # # By default, the binary extensions are located side by side with their # Ruby counterparts, therefore nil is returned def self.default_ext_dir_for(base_dir) nil end ## # Paths where RubyGems' .rb files and bin files are installed def self.default_rubygems_dirs nil # default to standard layout end ## # Path to specification files of default gems. def self.default_specifications_dir @default_specifications_dir ||= File.join(Gem.default_dir, "specifications", "default") end ## # Finds the user's home directory. #-- # Some comments from the ruby-talk list regarding finding the home # directory: # # I have HOME, USERPROFILE and HOMEDRIVE + HOMEPATH. Ruby seems # to be depending on HOME in those code samples. I propose that # it should fallback to USERPROFILE and HOMEDRIVE + HOMEPATH (at # least on Win32). #++ #-- # #++ def self.find_home Dir.home.dup rescue StandardError if Gem.win_platform? File.expand_path File.join(ENV["HOMEDRIVE"] || ENV["SystemDrive"], "/") else File.expand_path "/" end end private_class_method :find_home ## # The home directory for the user. def self.user_home @user_home ||= find_home end ## # Path for gems in the user's home directory def self.user_dir gem_dir = File.join(Gem.user_home, ".gem") gem_dir = File.join(Gem.data_home, "gem") unless File.exist?(gem_dir) parts = [gem_dir, ruby_engine] ruby_version_dir_name = RbConfig::CONFIG["ruby_version_dir_name"] || RbConfig::CONFIG["ruby_version"] parts << ruby_version_dir_name unless ruby_version_dir_name.empty? File.join parts end ## # The path to standard location of the user's configuration directory. def self.config_home @config_home ||= ENV["XDG_CONFIG_HOME"] || File.join(Gem.user_home, ".config") end ## # Finds the user's config file def self.find_config_file gemrc = File.join Gem.user_home, ".gemrc" if File.exist? gemrc gemrc else File.join Gem.config_home, "gem", "gemrc" end end ## # The path to standard location of the user's .gemrc file. def self.config_file @config_file ||= find_config_file end ## # The path to standard location of the user's state file. def self.state_file @state_file ||= File.join(Gem.state_home, "gem", "last_update_check") end ## # The path to standard location of the user's cache directory. def self.cache_home @cache_home ||= ENV["XDG_CACHE_HOME"] || File.join(Gem.user_home, ".cache") end ## # The path to standard location of the user's data directory. def self.data_home @data_home ||= ENV["XDG_DATA_HOME"] || File.join(Gem.user_home, ".local", "share") end ## # The path to standard location of the user's state directory. def self.state_home @state_home ||= ENV["XDG_STATE_HOME"] || File.join(Gem.user_home, ".local", "state") end ## # How String Gem paths should be split. Overridable for esoteric platforms. def self.path_separator File::PATH_SEPARATOR end ## # Default gem load path def self.default_path path = [] path << user_dir if user_home && File.exist?(user_home) path << default_dir path << vendor_dir if vendor_dir && File.directory?(vendor_dir) path end ## # Deduce Ruby's --program-prefix and --program-suffix from its install name def self.default_exec_format exec_format = begin RbConfig::CONFIG["ruby_install_name"].sub("ruby", "%s") rescue StandardError "%s" end unless exec_format.include?("%s") raise Gem::Exception, "[BUG] invalid exec_format #{exec_format.inspect}, no %s" end exec_format end ## # The default directory for binaries def self.default_bindir RbConfig::CONFIG["bindir"] end def self.ruby_engine RUBY_ENGINE end ## # The default signing key path def self.default_key_path default_key_path = File.join Gem.user_home, ".gem", "gem-private_key.pem" unless File.exist?(default_key_path) default_key_path = File.join Gem.data_home, "gem", "gem-private_key.pem" end default_key_path end ## # The default signing certificate chain path def self.default_cert_path default_cert_path = File.join Gem.user_home, ".gem", "gem-public_cert.pem" unless File.exist?(default_cert_path) default_cert_path = File.join Gem.data_home, "gem", "gem-public_cert.pem" end default_cert_path end ## # Enables automatic installation into user directory def self.default_user_install # :nodoc: if !ENV.key?("GEM_HOME") && File.exist?(Gem.dir) && !File.writable?(Gem.dir) Gem.ui.say "Defaulting to user installation because default installation directory (#{Gem.dir}) is not writable." return true end false end ## # Install extensions into lib as well as into the extension directory. def self.install_extension_in_lib # :nodoc: Gem.configuration.install_extension_in_lib end ## # Directory where vendor gems are installed. def self.vendor_dir # :nodoc: if vendor_dir = ENV["GEM_VENDOR"] return vendor_dir.dup end return nil unless RbConfig::CONFIG.key? "vendordir" File.join RbConfig::CONFIG["vendordir"], "gems", RbConfig::CONFIG["ruby_version_dir_name"] || RbConfig::CONFIG["ruby_version"] end ## # Default options for gem commands for Ruby packagers. # # The options here should be structured as an array of string "gem" # command names as keys and a string of the default options as values. # # Example: # # def self.operating_system_defaults # { # 'install' => '--no-rdoc --no-ri --env-shebang', # 'update' => '--no-rdoc --no-ri --env-shebang' # } # end def self.operating_system_defaults {} end ## # Default options for gem commands for Ruby implementers. # # The options here should be structured as an array of string "gem" # command names as keys and a string of the default options as values. # # Example: # # def self.platform_defaults # { # 'install' => '--no-rdoc --no-ri --env-shebang', # 'update' => '--no-rdoc --no-ri --env-shebang' # } # end def self.platform_defaults {} end end PK!)request_set/lockfile.rbnu[# frozen_string_literal: true ## # Parses a gem.deps.rb.lock file and constructs a LockSet containing the # dependencies found inside. If the lock file is missing no LockSet is # constructed. class Gem::RequestSet::Lockfile ## # Raised when a lockfile cannot be parsed class ParseError < Gem::Exception ## # The column where the error was encountered attr_reader :column ## # The line where the error was encountered attr_reader :line ## # The location of the lock file attr_reader :path ## # Raises a ParseError with the given +message+ which was encountered at a # +line+ and +column+ while parsing. def initialize(message, column, line, path) @line = line @column = column @path = path super "#{message} (at line #{line} column #{column})" end end ## # Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+ # location. def self.build(request_set, gem_deps_file, dependencies = nil) request_set.resolve dependencies ||= requests_to_deps request_set.sorted_requests new request_set, gem_deps_file, dependencies end def self.requests_to_deps(requests) # :nodoc: deps = {} requests.each do |request| spec = request.spec name = request.name requirement = request.request.dependency.requirement deps[name] = if [Gem::Resolver::VendorSpecification, Gem::Resolver::GitSpecification].include? spec.class Gem::Requirement.source_set else requirement end end deps end ## # The platforms for this Lockfile attr_reader :platforms def initialize(request_set, gem_deps_file, dependencies) @set = request_set @dependencies = dependencies @gem_deps_file = File.expand_path(gem_deps_file) @gem_deps_dir = File.dirname(@gem_deps_file) @platforms = [] end def add_DEPENDENCIES(out) # :nodoc: out << "DEPENDENCIES" out.concat @dependencies.sort.map {|name, requirement| " #{name}#{requirement.for_lockfile}" } out << nil end def add_GEM(out, spec_groups) # :nodoc: return if spec_groups.empty? source_groups = spec_groups.values.flatten.group_by do |request| request.spec.source.uri end source_groups.sort_by {|group,| group.to_s }.map do |group, requests| out << "GEM" out << " remote: #{group}" out << " specs:" requests.sort_by(&:name).each do |request| next if request.spec.name == "bundler" platform = "-#{request.spec.platform}" unless request.spec.platform == Gem::Platform::RUBY out << " #{request.name} (#{request.version}#{platform})" request.full_spec.dependencies.sort.each do |dependency| next if dependency.type == :development requirement = dependency.requirement out << " #{dependency.name}#{requirement.for_lockfile}" end end out << nil end end def add_GIT(out, git_requests) return if git_requests.empty? by_repository_revision = git_requests.group_by do |request| source = request.spec.source [source.repository, source.rev_parse] end by_repository_revision.each do |(repository, revision), requests| out << "GIT" out << " remote: #{repository}" out << " revision: #{revision}" out << " specs:" requests.sort_by(&:name).each do |request| out << " #{request.name} (#{request.version})" dependencies = request.spec.dependencies.sort_by(&:name) dependencies.each do |dep| out << " #{dep.name}#{dep.requirement.for_lockfile}" end end out << nil end end def relative_path_from(dest, base) # :nodoc: dest = File.expand_path(dest) base = File.expand_path(base) if dest.index(base) == 0 offset = dest[base.size + 1..-1] return "." unless offset offset else dest end end def add_PATH(out, path_requests) # :nodoc: return if path_requests.empty? out << "PATH" path_requests.each do |request| directory = File.expand_path(request.spec.source.uri) out << " remote: #{relative_path_from directory, @gem_deps_dir}" out << " specs:" out << " #{request.name} (#{request.version})" end out << nil end def add_PLATFORMS(out) # :nodoc: out << "PLATFORMS" platforms = requests.map {|request| request.spec.platform }.uniq platforms = platforms.sort_by(&:to_s) platforms.each do |platform| out << " #{platform}" end out << nil end def spec_groups requests.group_by {|request| request.spec.class } end ## # The contents of the lock file. def to_s out = [] groups = spec_groups add_PATH out, groups.delete(Gem::Resolver::VendorSpecification) { [] } add_GIT out, groups.delete(Gem::Resolver::GitSpecification) { [] } add_GEM out, groups add_PLATFORMS out add_DEPENDENCIES out out.join "\n" end ## # Writes the lock file alongside the gem dependencies file def write content = to_s File.open "#{@gem_deps_file}.lock", "w" do |io| io.write content end end private def requests @set.sorted_requests end end require_relative "lockfile/tokenizer" PK!8~VV!request_set/gem_dependency_api.rbnu[# frozen_string_literal: true ## # A semi-compatible DSL for the Bundler Gemfile and Isolate gem dependencies # files. # # To work with both the Bundler Gemfile and Isolate formats this # implementation takes some liberties to allow compatibility with each, most # notably in #source. # # A basic gem dependencies file will look like the following: # # source 'https://rubygems.org' # # gem 'rails', '3.2.14a # gem 'devise', '~> 2.1', '>= 2.1.3' # gem 'cancan' # gem 'airbrake' # gem 'pg' # # RubyGems recommends saving this as gem.deps.rb over Gemfile or Isolate. # # To install the gems in this Gemfile use `gem install -g` to install it and # create a lockfile. The lockfile will ensure that when you make changes to # your gem dependencies file a minimum amount of change is made to the # dependencies of your gems. # # RubyGems can activate all the gems in your dependencies file at startup # using the RUBYGEMS_GEMDEPS environment variable or through Gem.use_gemdeps. # See Gem.use_gemdeps for details and warnings. # # See `gem help install` and `gem help gem_dependencies` for further details. class Gem::RequestSet::GemDependencyAPI ENGINE_MAP = { # :nodoc: jruby: %w[jruby], jruby_18: %w[jruby], jruby_19: %w[jruby], maglev: %w[maglev], mri: %w[ruby], mri_18: %w[ruby], mri_19: %w[ruby], mri_20: %w[ruby], mri_21: %w[ruby], rbx: %w[rbx], truffleruby: %w[truffleruby], ruby: %w[ruby rbx maglev truffleruby], ruby_18: %w[ruby rbx maglev truffleruby], ruby_19: %w[ruby rbx maglev truffleruby], ruby_20: %w[ruby rbx maglev truffleruby], ruby_21: %w[ruby rbx maglev truffleruby], }.freeze mswin = Gem::Platform.new "x86-mswin32" mswin64 = Gem::Platform.new "x64-mswin64" x86_mingw = Gem::Platform.new "x86-mingw32" x64_mingw = Gem::Platform.new "x64-mingw32" PLATFORM_MAP = { # :nodoc: jruby: Gem::Platform::RUBY, jruby_18: Gem::Platform::RUBY, jruby_19: Gem::Platform::RUBY, maglev: Gem::Platform::RUBY, mingw: x86_mingw, mingw_18: x86_mingw, mingw_19: x86_mingw, mingw_20: x86_mingw, mingw_21: x86_mingw, mri: Gem::Platform::RUBY, mri_18: Gem::Platform::RUBY, mri_19: Gem::Platform::RUBY, mri_20: Gem::Platform::RUBY, mri_21: Gem::Platform::RUBY, mswin: mswin, mswin_18: mswin, mswin_19: mswin, mswin_20: mswin, mswin_21: mswin, mswin64: mswin64, mswin64_19: mswin64, mswin64_20: mswin64, mswin64_21: mswin64, rbx: Gem::Platform::RUBY, ruby: Gem::Platform::RUBY, ruby_18: Gem::Platform::RUBY, ruby_19: Gem::Platform::RUBY, ruby_20: Gem::Platform::RUBY, ruby_21: Gem::Platform::RUBY, truffleruby: Gem::Platform::RUBY, x64_mingw: x64_mingw, x64_mingw_20: x64_mingw, x64_mingw_21: x64_mingw, }.freeze gt_eq_0 = Gem::Requirement.new ">= 0" tilde_gt_1_8_0 = Gem::Requirement.new "~> 1.8.0" tilde_gt_1_9_0 = Gem::Requirement.new "~> 1.9.0" tilde_gt_2_0_0 = Gem::Requirement.new "~> 2.0.0" tilde_gt_2_1_0 = Gem::Requirement.new "~> 2.1.0" VERSION_MAP = { # :nodoc: jruby: gt_eq_0, jruby_18: tilde_gt_1_8_0, jruby_19: tilde_gt_1_9_0, maglev: gt_eq_0, mingw: gt_eq_0, mingw_18: tilde_gt_1_8_0, mingw_19: tilde_gt_1_9_0, mingw_20: tilde_gt_2_0_0, mingw_21: tilde_gt_2_1_0, mri: gt_eq_0, mri_18: tilde_gt_1_8_0, mri_19: tilde_gt_1_9_0, mri_20: tilde_gt_2_0_0, mri_21: tilde_gt_2_1_0, mswin: gt_eq_0, mswin_18: tilde_gt_1_8_0, mswin_19: tilde_gt_1_9_0, mswin_20: tilde_gt_2_0_0, mswin_21: tilde_gt_2_1_0, mswin64: gt_eq_0, mswin64_19: tilde_gt_1_9_0, mswin64_20: tilde_gt_2_0_0, mswin64_21: tilde_gt_2_1_0, rbx: gt_eq_0, ruby: gt_eq_0, ruby_18: tilde_gt_1_8_0, ruby_19: tilde_gt_1_9_0, ruby_20: tilde_gt_2_0_0, ruby_21: tilde_gt_2_1_0, truffleruby: gt_eq_0, x64_mingw: gt_eq_0, x64_mingw_20: tilde_gt_2_0_0, x64_mingw_21: tilde_gt_2_1_0, }.freeze WINDOWS = { # :nodoc: mingw: :only, mingw_18: :only, mingw_19: :only, mingw_20: :only, mingw_21: :only, mri: :never, mri_18: :never, mri_19: :never, mri_20: :never, mri_21: :never, mswin: :only, mswin_18: :only, mswin_19: :only, mswin_20: :only, mswin_21: :only, mswin64: :only, mswin64_19: :only, mswin64_20: :only, mswin64_21: :only, rbx: :never, ruby: :never, ruby_18: :never, ruby_19: :never, ruby_20: :never, ruby_21: :never, x64_mingw: :only, x64_mingw_20: :only, x64_mingw_21: :only, }.freeze ## # The gems required by #gem statements in the gem.deps.rb file attr_reader :dependencies ## # A set of gems that are loaded via the +:git+ option to #gem attr_reader :git_set # :nodoc: ## # A Hash containing gem names and files to require from those gems. attr_reader :requires ## # A set of gems that are loaded via the +:path+ option to #gem attr_reader :vendor_set # :nodoc: ## # The groups of gems to exclude from installation attr_accessor :without_groups # :nodoc: ## # Creates a new GemDependencyAPI that will add dependencies to the # Gem::RequestSet +set+ based on the dependency API description in +path+. def initialize(set, path) @set = set @path = path @current_groups = nil @current_platforms = nil @current_repository = nil @dependencies = {} @default_sources = true @git_set = @set.git_set @git_sources = {} @installing = false @requires = Hash.new {|h, name| h[name] = [] } @vendor_set = @set.vendor_set @source_set = @set.source_set @gem_sources = {} @without_groups = [] git_source :github do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include? "/" "https://github.com/#{repo_name}.git" end git_source :bitbucket do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include? "/" user, = repo_name.split "/", 2 "https://#{user}@bitbucket.org/#{repo_name}.git" end end ## # Adds +dependencies+ to the request set if any of the +groups+ are allowed. # This is used for gemspec dependencies. def add_dependencies(groups, dependencies) # :nodoc: return unless (groups & @without_groups).empty? dependencies.each do |dep| @set.gem dep.name, *dep.requirement.as_list end end private :add_dependencies ## # Finds a gemspec with the given +name+ that lives at +path+. def find_gemspec(name, path) # :nodoc: glob = File.join path, "#{name}.gemspec" spec_files = Dir[glob] case spec_files.length when 1 then spec_file = spec_files.first spec = Gem::Specification.load spec_file return spec if spec raise ArgumentError, "invalid gemspec #{spec_file}" when 0 then raise ArgumentError, "no gemspecs found at #{Dir.pwd}" else raise ArgumentError, "found multiple gemspecs at #{Dir.pwd}, " \ "use the name: option to specify the one you want" end end ## # Changes the behavior of gem dependency file loading to installing mode. # In installing mode certain restrictions are ignored such as ruby version # mismatch checks. def installing=(installing) # :nodoc: @installing = installing end ## # Loads the gem dependency file and returns self. def load instance_eval File.read(@path), @path, 1 self end ## # :category: Gem Dependencies DSL # # :call-seq: # gem(name) # gem(name, *requirements) # gem(name, *requirements, options) # # Specifies a gem dependency with the given +name+ and +requirements+. You # may also supply +options+ following the +requirements+ # # +options+ include: # # require: :: # RubyGems does not provide any autorequire features so requires in a gem # dependencies file are recorded but ignored. # # In bundler the require: option overrides the file to require during # Bundler.require. By default the name of the dependency is required in # Bundler. A single file or an Array of files may be given. # # To disable requiring any file give +false+: # # gem 'rake', require: false # # group: :: # Place the dependencies in the given dependency group. A single group or # an Array of groups may be given. # # See also #group # # platform: :: # Only install the dependency on the given platform. A single platform or # an Array of platforms may be given. # # See #platform for a list of platforms available. # # path: :: # Install this dependency from an unpacked gem in the given directory. # # gem 'modified_gem', path: 'vendor/modified_gem' # # git: :: # Install this dependency from a git repository: # # gem 'private_gem', git: 'git@my.company.example:private_gem.git' # # gist: :: # Install this dependency from the gist ID: # # gem 'bang', gist: '1232884' # # github: :: # Install this dependency from a github git repository: # # gem 'private_gem', github: 'my_company/private_gem' # # submodules: :: # Set to +true+ to include submodules when fetching the git repository for # git:, gist: and github: dependencies. # # ref: :: # Use the given commit name or SHA for git:, gist: and github: # dependencies. # # branch: :: # Use the given branch for git:, gist: and github: dependencies. # # tag: :: # Use the given tag for git:, gist: and github: dependencies. def gem(name, *requirements) options = requirements.pop if requirements.last.is_a?(Hash) options ||= {} options[:git] = @current_repository if @current_repository source_set = false source_set ||= gem_path name, options source_set ||= gem_git name, options source_set ||= gem_git_source name, options source_set ||= gem_source name, options duplicate = @dependencies.include? name @dependencies[name] = if requirements.empty? && !source_set Gem::Requirement.default elsif source_set Gem::Requirement.source_set else Gem::Requirement.create requirements end return unless gem_platforms name, options groups = gem_group name, options return unless (groups & @without_groups).empty? pin_gem_source name, :default unless source_set gem_requires name, options if duplicate warn <<-WARNING Gem dependencies file #{@path} requires #{name} more than once. WARNING end @set.gem name, *requirements end ## # Handles the git: option from +options+ for gem +name+. # # Returns +true+ if the gist or git option was handled. def gem_git(name, options) # :nodoc: if gist = options.delete(:gist) options[:git] = "https://gist.github.com/#{gist}.git" end return unless repository = options.delete(:git) pin_gem_source name, :git, repository reference = gem_git_reference options submodules = options.delete :submodules @git_set.add_git_gem name, repository, reference, submodules true end ## # Handles the git options from +options+ for git gem. # # Returns reference for the git gem. def gem_git_reference(options) # :nodoc: ref = options.delete :ref branch = options.delete :branch tag = options.delete :tag reference = nil reference ||= ref reference ||= branch reference ||= tag if ref && branch warn <<-WARNING Gem dependencies file #{@path} includes git reference for both ref and branch but only ref is used. WARNING end if (ref || branch) && tag warn <<-WARNING Gem dependencies file #{@path} includes git reference for both ref/branch and tag but only ref/branch is used. WARNING end reference end private :gem_git ## # Handles a git gem option from +options+ for gem +name+ for a git source # registered through git_source. # # Returns +true+ if the custom source option was handled. def gem_git_source(name, options) # :nodoc: return unless git_source = (@git_sources.keys & options.keys).last source_callback = @git_sources[git_source] source_param = options.delete git_source git_url = source_callback.call source_param options[:git] = git_url gem_git name, options true end private :gem_git_source ## # Handles the :group and :groups +options+ for the gem with the given # +name+. def gem_group(name, options) # :nodoc: g = options.delete :group all_groups = g ? Array(g) : [] groups = options.delete :groups all_groups |= groups if groups all_groups |= @current_groups if @current_groups all_groups end private :gem_group ## # Handles the path: option from +options+ for gem +name+. # # Returns +true+ if the path option was handled. def gem_path(name, options) # :nodoc: return unless directory = options.delete(:path) pin_gem_source name, :path, directory @vendor_set.add_vendor_gem name, directory true end private :gem_path ## # Handles the source: option from +options+ for gem +name+. # # Returns +true+ if the source option was handled. def gem_source(name, options) # :nodoc: return unless source = options.delete(:source) pin_gem_source name, :source, source @source_set.add_source_gem name, source true end private :gem_source ## # Handles the platforms: option from +options+. Returns true if the # platform matches the current platform. def gem_platforms(name, options) # :nodoc: platform_names = Array(options.delete(:platform)) platform_names.concat Array(options.delete(:platforms)) platform_names.concat @current_platforms if @current_platforms return true if platform_names.empty? platform_names.any? do |platform_name| raise ArgumentError, "unknown platform #{platform_name.inspect}" unless platform = PLATFORM_MAP[platform_name] next false unless Gem::Platform.match_gem? platform, name if engines = ENGINE_MAP[platform_name] next false unless engines.include? Gem.ruby_engine end case WINDOWS[platform_name] when :only then next false unless Gem.win_platform? when :never then next false if Gem.win_platform? end VERSION_MAP[platform_name].satisfied_by? Gem.ruby_version end end private :gem_platforms ## # Records the require: option from +options+ and adds those files, or the # default file to the require list for +name+. def gem_requires(name, options) # :nodoc: if options.include? :require if requires = options.delete(:require) @requires[name].concat Array requires end else @requires[name] << name end raise ArgumentError, "Unhandled gem options #{options.inspect}" unless options.empty? end private :gem_requires ## # :category: Gem Dependencies DSL # # Block form for specifying gems from a git +repository+. # # git 'https://github.com/rails/rails.git' do # gem 'activesupport' # gem 'activerecord' # end def git(repository) @current_repository = repository yield ensure @current_repository = nil end ## # Defines a custom git source that uses +name+ to expand git repositories # for use in gems built from git repositories. You must provide a block # that accepts a git repository name for expansion. def git_source(name, &callback) @git_sources[name] = callback end ## # Returns the basename of the file the dependencies were loaded from def gem_deps_file # :nodoc: File.basename @path end ## # :category: Gem Dependencies DSL # # Loads dependencies from a gemspec file. # # +options+ include: # # name: :: # The name portion of the gemspec file. Defaults to searching for any # gemspec file in the current directory. # # gemspec name: 'my_gem' # # path: :: # The path the gemspec lives in. Defaults to the current directory: # # gemspec 'my_gem', path: 'gemspecs', name: 'my_gem' # # development_group: :: # The group to add development dependencies to. By default this is # :development. Only one group may be specified. def gemspec(options = {}) name = options.delete(:name) || "{,*}" path = options.delete(:path) || "." development_group = options.delete(:development_group) || :development spec = find_gemspec name, path groups = gem_group spec.name, {} self_dep = Gem::Dependency.new spec.name, spec.version add_dependencies groups, [self_dep] add_dependencies groups, spec.runtime_dependencies @dependencies[spec.name] = Gem::Requirement.source_set spec.dependencies.each do |dep| @dependencies[dep.name] = dep.requirement end groups << development_group add_dependencies groups, spec.development_dependencies @vendor_set.add_vendor_gem spec.name, path gem_requires spec.name, options end ## # :category: Gem Dependencies DSL # # Block form for placing a dependency in the given +groups+. # # group :development do # gem 'debugger' # end # # group :development, :test do # gem 'minitest' # end # # Groups can be excluded at install time using `gem install -g --without # development`. See `gem help install` and `gem help gem_dependencies` for # further details. def group(*groups) @current_groups = groups yield ensure @current_groups = nil end ## # Pins the gem +name+ to the given +source+. Adding a gem with the same # name from a different +source+ will raise an exception. def pin_gem_source(name, type = :default, source = nil) source_description = case type when :default then "(default)" when :path then "path: #{source}" when :git then "git: #{source}" when :source then "source: #{source}" else "(unknown)" end raise ArgumentError, "duplicate source #{source_description} for gem #{name}" if @gem_sources.fetch(name, source) != source @gem_sources[name] = source end private :pin_gem_source ## # :category: Gem Dependencies DSL # # Block form for restricting gems to a set of platforms. # # The gem dependencies platform is different from Gem::Platform. A platform # gem.deps.rb platform matches on the ruby engine, the ruby version and # whether or not windows is allowed. # # :ruby, :ruby_XY :: # Matches non-windows, non-jruby implementations where X and Y can be used # to match releases in the 1.8, 1.9, 2.0 or 2.1 series. # # :mri, :mri_XY :: # Matches non-windows C Ruby (Matz Ruby) or only the 1.8, 1.9, 2.0 or # 2.1 series. # # :mingw, :mingw_XY :: # Matches 32 bit C Ruby on MinGW or only the 1.8, 1.9, 2.0 or 2.1 series. # # :x64_mingw, :x64_mingw_XY :: # Matches 64 bit C Ruby on MinGW or only the 1.8, 1.9, 2.0 or 2.1 series. # # :mswin, :mswin_XY :: # Matches 32 bit C Ruby on Microsoft Windows or only the 1.8, 1.9, 2.0 or # 2.1 series. # # :mswin64, :mswin64_XY :: # Matches 64 bit C Ruby on Microsoft Windows or only the 1.8, 1.9, 2.0 or # 2.1 series. # # :jruby, :jruby_XY :: # Matches JRuby or JRuby in 1.8 or 1.9 mode. # # :maglev :: # Matches Maglev # # :rbx :: # Matches non-windows Rubinius # # NOTE: There is inconsistency in what environment a platform matches. You # may need to read the source to know the exact details. def platform(*platforms) @current_platforms = platforms yield ensure @current_platforms = nil end ## # :category: Gem Dependencies DSL # # Block form for restricting gems to a particular set of platforms. See # #platform. alias_method :platforms, :platform ## # :category: Gem Dependencies DSL # # Restricts this gem dependencies file to the given ruby +version+. # # You may also provide +engine:+ and +engine_version:+ options to restrict # this gem dependencies file to a particular ruby engine and its engine # version. This matching is performed by using the RUBY_ENGINE and # RUBY_ENGINE_VERSION constants. def ruby(version, options = {}) engine = options[:engine] engine_version = options[:engine_version] raise ArgumentError, "You must specify engine_version along with the Ruby engine" if engine && !engine_version return true if @installing unless version == RUBY_VERSION message = "Your Ruby version is #{RUBY_VERSION}, " \ "but your #{gem_deps_file} requires #{version}" raise Gem::RubyVersionMismatch, message end if engine && engine != Gem.ruby_engine message = "Your Ruby engine is #{Gem.ruby_engine}, " \ "but your #{gem_deps_file} requires #{engine}" raise Gem::RubyVersionMismatch, message end if engine_version if engine_version != RUBY_ENGINE_VERSION message = "Your Ruby engine version is #{Gem.ruby_engine} #{RUBY_ENGINE_VERSION}, " \ "but your #{gem_deps_file} requires #{engine} #{engine_version}" raise Gem::RubyVersionMismatch, message end end true end ## # :category: Gem Dependencies DSL # # Sets +url+ as a source for gems for this dependency API. RubyGems uses # the default configured sources if no source was given. If a source is set # only that source is used. # # This method differs in behavior from Bundler: # # * The +:gemcutter+, # +:rubygems+ and +:rubyforge+ sources are not # supported as they are deprecated in bundler. # * The +prepend:+ option is not supported. If you wish to order sources # then list them in your preferred order. def source(url) Gem.sources.clear if @default_sources @default_sources = false Gem.sources << url end end PK!D3 !request_set/lockfile/tokenizer.rbnu[# frozen_string_literal: true # ) frozen_string_literal: true require_relative "parser" class Gem::RequestSet::Lockfile::Tokenizer Token = Struct.new :type, :value, :column, :line EOF = Token.new :EOF def self.from_file(file) new File.read(file), file end def initialize(input, filename = nil, line = 0, pos = 0) @line = line @line_pos = pos @tokens = [] @filename = filename tokenize input end def make_parser(set, platforms) Gem::RequestSet::Lockfile::Parser.new self, set, platforms, @filename end def to_a @tokens.map {|token| [token.type, token.value, token.column, token.line] } end def skip(type) @tokens.shift while !@tokens.empty? && peek.type == type end ## # Calculates the column (by byte) and the line of the current token based on # +byte_offset+. def token_pos(byte_offset) # :nodoc: [byte_offset - @line_pos, @line] end def empty? @tokens.empty? end def unshift(token) @tokens.unshift token end def next_token @tokens.shift end alias_method :shift, :next_token def peek @tokens.first || EOF end private def tokenize(input) require "strscan" s = StringScanner.new input until s.eos? do pos = s.pos pos = s.pos if leading_whitespace = s.scan(/ +/) if s.scan(/[<|=>]{7}/) message = "your #{@filename} contains merge conflict markers" column, line = token_pos pos raise Gem::RequestSet::Lockfile::ParseError.new message, column, line, @filename end @tokens << if s.scan(/\r?\n/) token = Token.new(:newline, nil, *token_pos(pos)) @line_pos = s.pos @line += 1 token elsif s.scan(/[A-Z]+/) if leading_whitespace text = s.matched text += s.scan(/[^\s)]*/).to_s # in case of no match Token.new(:text, text, *token_pos(pos)) else Token.new(:section, s.matched, *token_pos(pos)) end elsif s.scan(/([a-z]+):\s/) s.pos -= 1 # rewind for possible newline Token.new(:entry, s[1], *token_pos(pos)) elsif s.scan(/\(/) Token.new(:l_paren, nil, *token_pos(pos)) elsif s.scan(/\)/) Token.new(:r_paren, nil, *token_pos(pos)) elsif s.scan(/<=|>=|=|~>|<|>|!=/) Token.new(:requirement, s.matched, *token_pos(pos)) elsif s.scan(/,/) Token.new(:comma, nil, *token_pos(pos)) elsif s.scan(/!/) Token.new(:bang, nil, *token_pos(pos)) elsif s.scan(/[^\s),!]*/) Token.new(:text, s.matched, *token_pos(pos)) else raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}" end end @tokens end end PK!/W2request_set/lockfile/parser.rbnu[# frozen_string_literal: true class Gem::RequestSet::Lockfile::Parser ### # Parses lockfiles def initialize(tokenizer, set, platforms, filename = nil) @tokens = tokenizer @filename = filename @set = set @platforms = platforms end def parse until @tokens.empty? do token = get case token.type when :section then @tokens.skip :newline case token.value when "DEPENDENCIES" then parse_DEPENDENCIES when "GIT" then parse_GIT when "GEM" then parse_GEM when "PATH" then parse_PATH when "PLATFORMS" then parse_PLATFORMS else token = get until @tokens.empty? || peek.first == :section end else raise "BUG: unhandled token #{token.type} (#{token.value.inspect}) at line #{token.line} column #{token.column}" end end end ## # Gets the next token for a Lockfile def get(expected_types = nil, expected_value = nil) # :nodoc: token = @tokens.shift if expected_types && !Array(expected_types).include?(token.type) unget token message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " \ "expected #{expected_types.inspect}" raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename end if expected_value && expected_value != token.value unget token message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " \ "expected [#{expected_types.inspect}, " \ "#{expected_value.inspect}]" raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename end token end def parse_DEPENDENCIES # :nodoc: while !@tokens.empty? && peek.type == :text do token = get :text requirements = [] case peek[0] when :bang then get :bang requirements << pinned_requirement(token.value) when :l_paren then get :l_paren loop do op = get(:requirement).value version = get(:text).value requirements << "#{op} #{version}" break unless peek.type == :comma get :comma end get :r_paren if peek[0] == :bang requirements.clear requirements << pinned_requirement(token.value) get :bang end end @set.gem token.value, *requirements skip :newline end end def parse_GEM # :nodoc: sources = [] while peek.first(2) == [:entry, "remote"] do get :entry, "remote" data = get(:text).value skip :newline sources << Gem::Source.new(data) end sources << Gem::Source.new(Gem::DEFAULT_HOST) if sources.empty? get :entry, "specs" skip :newline set = Gem::Resolver::LockSet.new sources last_specs = nil while !@tokens.empty? && peek.type == :text do token = get :text name = token.value column = token.column case peek[0] when :newline then last_specs.each do |spec| spec.add_dependency Gem::Dependency.new name if column == 6 end when :l_paren then get :l_paren token = get [:text, :requirement] type = token.type data = token.value if type == :text && column == 4 version, platform = data.split "-", 2 platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY last_specs = set.add name, version, platform else dependency = parse_dependency name, data last_specs.each do |spec| spec.add_dependency dependency end end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_GIT # :nodoc: get :entry, "remote" repository = get(:text).value skip :newline get :entry, "revision" revision = get(:text).value skip :newline type = peek.type value = peek.value if type == :entry && %w[branch ref tag].include?(value) get get :text skip :newline end get :entry, "specs" skip :newline set = Gem::Resolver::GitSet.new set.root_dir = @set.install_dir last_spec = nil while !@tokens.empty? && peek.type == :text do token = get :text name = token.value column = token.column case peek[0] when :newline then last_spec.add_dependency Gem::Dependency.new name if column == 6 when :l_paren then get :l_paren token = get [:text, :requirement] type = token.type data = token.value if type == :text && column == 4 last_spec = set.add_git_spec name, data, repository, revision, true else dependency = parse_dependency name, data last_spec.add_dependency dependency end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_PATH # :nodoc: get :entry, "remote" directory = get(:text).value skip :newline get :entry, "specs" skip :newline set = Gem::Resolver::VendorSet.new last_spec = nil while !@tokens.empty? && peek.first == :text do token = get :text name = token.value column = token.column case peek[0] when :newline then last_spec.add_dependency Gem::Dependency.new name if column == 6 when :l_paren then get :l_paren token = get [:text, :requirement] type = token.type data = token.value if type == :text && column == 4 last_spec = set.add_vendor_gem name, directory else dependency = parse_dependency name, data last_spec.dependencies << dependency end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_PLATFORMS # :nodoc: while !@tokens.empty? && peek.first == :text do name = get(:text).value @platforms << name skip :newline end end ## # Parses the requirements following the dependency +name+ and the +op+ for # the first token of the requirements and returns a Gem::Dependency object. def parse_dependency(name, op) # :nodoc: return Gem::Dependency.new name, op unless peek[0] == :text version = get(:text).value requirements = ["#{op} #{version}"] while peek.type == :comma do get :comma op = get(:requirement).value version = get(:text).value requirements << "#{op} #{version}" end Gem::Dependency.new name, requirements end private def skip(type) # :nodoc: @tokens.skip type end ## # Peeks at the next token for Lockfile def peek # :nodoc: @tokens.peek end def pinned_requirement(name) # :nodoc: requirement = Gem::Dependency.new name specification = @set.sets.flat_map do |set| set.find_all(requirement) end.compact.first specification&.version end ## # Ungets the last token retrieved by #get def unget(token) # :nodoc: @tokens.unshift token end end PK!zg source.rbnu[# frozen_string_literal: true require_relative "text" ## # A Source knows how to list and fetch gems from a RubyGems marshal index. # # There are other Source subclasses for installed gems, local gems, the # Compact Index API and so-forth. class Gem::Source include Comparable include Gem::Text FILES = { # :nodoc: released: "specs", latest: "latest_specs", prerelease: "prerelease_specs", }.freeze ## # The URI this source will fetch gems from. attr_reader :uri ## # Creates a new Source which will use the index located at +uri+. def initialize(uri) require_relative "uri" @uri = Gem::Uri.parse!(uri) @update_cache = nil end ## # Sources are ordered by installation preference. def <=>(other) case other when Gem::Source::Installed, Gem::Source::Local, Gem::Source::Lock, Gem::Source::SpecificFile, Gem::Source::Git, Gem::Source::Vendor then -1 when Gem::Source then unless @uri return 0 unless other.uri return 1 end return -1 unless other.uri # Returning 1 here ensures that when sorting a list of sources, the # original ordering of sources supplied by the user is preserved. return 1 unless @uri.to_s == other.uri.to_s 0 end end def ==(other) # :nodoc: self.class === other && @uri == other.uri end alias_method :eql?, :== # :nodoc: ## # Returns a Set that can fetch specifications from this source. # # The set will optionally fetch prereleases if requested. # def dependency_resolver_set(prerelease = false) new_dependency_resolver_set.tap {|set| set.prerelease = prerelease } end def hash # :nodoc: @uri.hash end ## # Returns the local directory to write +uri+ to. def cache_dir(uri) # Correct for windows paths escaped_path = uri.path.sub(%r{^/([a-z]):/}i, '/\\1-/') File.join Gem.spec_cache_dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path) end ## # Returns true when it is possible and safe to update the cache directory. def update_cache? return @update_cache unless @update_cache.nil? @update_cache = begin File.stat(Gem.user_home).uid == Process.uid rescue Errno::ENOENT false end end ## # Fetches a specification for the given Gem::NameTuple. def fetch_spec(name_tuple) fetcher = Gem::RemoteFetcher.fetcher spec_file_name = name_tuple.spec_name source_uri = enforce_trailing_slash(uri) + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}" cache_dir = cache_dir source_uri local_spec = File.join cache_dir, spec_file_name if File.exist? local_spec spec = Gem.read_binary local_spec Gem.load_safe_marshal spec = begin Gem::SafeMarshal.safe_load(spec) rescue StandardError nil end return spec if spec end source_uri.path << ".rz" spec = fetcher.fetch_path source_uri spec = Gem::Util.inflate spec if update_cache? require "fileutils" FileUtils.mkdir_p cache_dir File.open local_spec, "wb" do |io| io.write spec end end Gem.load_safe_marshal # TODO: Investigate setting Gem::Specification#loaded_from to a URI Gem::SafeMarshal.safe_load spec end ## # Loads +type+ kind of specs fetching from +@uri+ if the on-disk cache is # out of date. # # +type+ is one of the following: # # :released => Return the list of all released specs # :latest => Return the list of only the highest version of each gem # :prerelease => Return the list of all prerelease only specs # def load_specs(type) file = FILES[type] fetcher = Gem::RemoteFetcher.fetcher file_name = "#{file}.#{Gem.marshal_version}" spec_path = enforce_trailing_slash(uri) + "#{file_name}.gz" cache_dir = cache_dir spec_path local_file = File.join(cache_dir, file_name) retried = false if update_cache? require "fileutils" FileUtils.mkdir_p cache_dir end spec_dump = fetcher.cache_update_path spec_path, local_file, update_cache? Gem.load_safe_marshal begin Gem::NameTuple.from_list Gem::SafeMarshal.safe_load(spec_dump) rescue ArgumentError if update_cache? && !retried FileUtils.rm local_file retried = true retry else raise Gem::Exception.new("Invalid spec cache file in #{local_file}") end end end ## # Downloads +spec+ and writes it to +dir+. See also # Gem::RemoteFetcher#download. def download(spec, dir = Dir.pwd) fetcher = Gem::RemoteFetcher.fetcher fetcher.download spec, uri.to_s, dir end def pretty_print(q) # :nodoc: q.object_group(self) do q.group 2, "[Remote:", "]" do q.breakable q.text @uri.to_s if api = uri q.breakable q.text "API URI: " q.text api.to_s end end end end def typo_squatting?(host, distance_threshold = 4) return if @uri.host.nil? levenshtein_distance(@uri.host, host).between? 1, distance_threshold end private def new_dependency_resolver_set return Gem::Resolver::IndexSet.new self if uri.scheme == "file" fetch_uri = if uri.host == "rubygems.org" index_uri = uri.dup index_uri.host = "index.rubygems.org" index_uri else uri end bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" begin fetcher = Gem::RemoteFetcher.fetcher response = fetcher.fetch_path bundler_api_uri, nil, true rescue Gem::RemoteFetcher::FetchError Gem::Resolver::IndexSet.new self else Gem::Resolver::APISet.new response.uri + "./info/" end end def enforce_trailing_slash(uri) uri.merge(uri.path.gsub(%r{/+$}, "") + "/") end end require_relative "source/git" require_relative "source/installed" require_relative "source/specific_file" require_relative "source/local" require_relative "source/lock" require_relative "source/vendor" PK!kS source_list.rbnu[# frozen_string_literal: true ## # The SourceList represents the sources rubygems has been configured to use. # A source may be created from an array of sources: # # Gem::SourceList.from %w[https://rubygems.example https://internal.example] # # Or by adding them: # # sources = Gem::SourceList.new # sources << 'https://rubygems.example' # # The most common way to get a SourceList is Gem.sources. class Gem::SourceList include Enumerable ## # Creates a new SourceList def initialize @sources = [] end ## # The sources in this list attr_reader :sources ## # Creates a new SourceList from an array of sources. def self.from(ary) list = new list.replace ary list end def initialize_copy(other) # :nodoc: @sources = @sources.dup end ## # Appends +obj+ to the source list which may be a Gem::Source, Gem::URI or URI # String. def <<(obj) src = case obj when Gem::Source obj else Gem::Source.new(obj) end @sources << src unless @sources.include?(src) src end ## # Prepends +obj+ to the beginning of the source list which may be a Gem::Source, Gem::URI or URI # Moves +obj+ to the beginning of the list if already present. # String. def prepend(obj) src = case obj when Gem::Source obj else Gem::Source.new(obj) end @sources.delete(src) if @sources.include?(src) @sources.unshift(src) src end ## # Appends +obj+ to the end of the source list, moving it if already present. # +obj+ may be a Gem::Source, Gem::URI or URI String. # Moves +obj+ to the end of the list if already present. def append(obj) src = case obj when Gem::Source obj else Gem::Source.new(obj) end @sources.delete(src) if @sources.include?(src) @sources << src src end ## # Replaces this SourceList with the sources in +other+ See #<< for # acceptable items in +other+. def replace(other) clear other.each do |x| self << x end self end ## # Removes all sources from the SourceList. def clear @sources.clear end ## # Yields each source URI in the list. def each @sources.each {|s| yield s.uri.to_s } end ## # Yields each source in the list. def each_source(&b) @sources.each(&b) end ## # Returns true if there are no sources in this SourceList. def empty? @sources.empty? end def ==(other) # :nodoc: to_a == other end ## # Returns an Array of source URI Strings. def to_a @sources.map {|x| x.uri.to_s } end alias_method :to_ary, :to_a ## # Returns the first source in the list. def first @sources.first end ## # Returns true if this source list includes +other+ which may be a # Gem::Source or a source URI. def include?(other) if other.is_a? Gem::Source @sources.include? other else @sources.find {|x| x.uri.to_s == other.to_s } end end ## # Deletes +source+ from the source list which may be a Gem::Source or a URI. def delete(source) if source.is_a? Gem::Source @sources.delete source else @sources.delete_if {|x| x.uri.to_s == source.to_s } end end end PK!vvendored_timeout.rbnu[# frozen_string_literal: true # Ruby 3.3 and RubyGems 3.5 is already load Gem::Timeout from lib/rubygems/timeout.rb # We should avoid to load it again require_relative "vendor/timeout/lib/timeout" unless defined?(Gem::Timeout) PK!xu%% resolver.rbnu[# frozen_string_literal: true require_relative "dependency" require_relative "exceptions" require_relative "util/list" ## # Given a set of Gem::Dependency objects as +needed+ and a way to query the # set of available specs via +set+, calculates a set of ActivationRequest # objects which indicate all the specs that should be activated to meet the # all the requirements. class Gem::Resolver require_relative "vendored_molinillo" ## # If the DEBUG_RESOLVER environment variable is set then debugging mode is # enabled for the resolver. This will display information about the state # of the resolver while a set of dependencies is being resolved. DEBUG_RESOLVER = !ENV["DEBUG_RESOLVER"].nil? ## # Set to true if all development dependencies should be considered. attr_accessor :development ## # Set to true if immediate development dependencies should be considered. attr_accessor :development_shallow ## # When true, no dependencies are looked up for requested gems. attr_accessor :ignore_dependencies ## # List of dependencies that could not be found in the configured sources. attr_reader :stats ## # Hash of gems to skip resolution. Keyed by gem name, with arrays of # gem specifications as values. attr_accessor :skip_gems ## # attr_accessor :soft_missing ## # Combines +sets+ into a ComposedSet that allows specification lookup in a # uniform manner. If one of the +sets+ is itself a ComposedSet its sets are # flattened into the result ComposedSet. def self.compose_sets(*sets) sets.compact! sets = sets.flat_map do |set| case set when Gem::Resolver::BestSet then set when Gem::Resolver::ComposedSet then set.sets else set end end case sets.length when 0 then raise ArgumentError, "one set in the composition must be non-nil" when 1 then sets.first else Gem::Resolver::ComposedSet.new(*sets) end end ## # Creates a Resolver that queries only against the already installed gems # for the +needed+ dependencies. def self.for_current_gems(needed) new needed, Gem::Resolver::CurrentSet.new end ## # Create Resolver object which will resolve the tree starting # with +needed+ Dependency objects. # # +set+ is an object that provides where to look for specifications to # satisfy the Dependencies. This defaults to IndexSet, which will query # rubygems.org. def initialize(needed, set = nil) @set = set || Gem::Resolver::IndexSet.new @needed = needed @development = false @development_shallow = false @ignore_dependencies = false @skip_gems = {} @soft_missing = false @stats = Gem::Resolver::Stats.new end def explain(stage, *data) # :nodoc: return unless DEBUG_RESOLVER d = data.map(&:pretty_inspect).join(", ") $stderr.printf "%10s %s\n", stage.to_s.upcase, d end def explain_list(stage) # :nodoc: return unless DEBUG_RESOLVER data = yield $stderr.printf "%10s (%d entries)\n", stage.to_s.upcase, data.size unless data.empty? require "pp" PP.pp data, $stderr end end ## # Creates an ActivationRequest for the given +dep+ and the last +possible+ # specification. # # Returns the Specification and the ActivationRequest def activation_request(dep, possible) # :nodoc: spec = possible.pop explain :activate, [spec.full_name, possible.size] explain :possible, possible activation_request = Gem::Resolver::ActivationRequest.new spec, dep, possible [spec, activation_request] end def requests(s, act, reqs = []) # :nodoc: return reqs if @ignore_dependencies s.fetch_development_dependencies if @development s.dependencies.reverse_each do |d| next if d.type == :development && !@development next if d.type == :development && @development_shallow && act.development? next if d.type == :development && @development_shallow && act.parent reqs << Gem::Resolver::DependencyRequest.new(d, act) @stats.requirement! end @set.prefetch reqs @stats.record_requirements reqs reqs end include Gem::Molinillo::UI def output @output ||= debug? ? $stdout : File.open(IO::NULL, "w") end def debug? DEBUG_RESOLVER end include Gem::Molinillo::SpecificationProvider ## # Proceed with resolution! Returns an array of ActivationRequest objects. def resolve Gem::Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }).tsort.filter_map(&:payload) rescue Gem::Molinillo::VersionConflict => e conflict = e.conflicts.values.first raise Gem::DependencyResolutionError, Conflict.new(conflict.requirement_trees.first.first, conflict.existing, conflict.requirement) ensure @output.close if defined?(@output) && !debug? end ## # Extracts the specifications that may be able to fulfill +dependency+ and # returns those that match the local platform and all those that match. def find_possible(dependency) # :nodoc: all = @set.find_all dependency if (skip_dep_gems = skip_gems[dependency.name]) && !skip_dep_gems.empty? matching = all.select do |api_spec| skip_dep_gems.any? {|s| api_spec.version == s.version } end all = matching unless matching.empty? end matching_platform = select_local_platforms all [matching_platform, all] end ## # Returns the gems in +specs+ that match the local platform. def select_local_platforms(specs) # :nodoc: specs.select do |spec| Gem::Platform.installable? spec end end def search_for(dependency) possibles, all = find_possible(dependency) if !@soft_missing && possibles.empty? exc = Gem::UnsatisfiableDependencyError.new dependency, all exc.errors = @set.errors raise exc end groups = Hash.new {|hash, key| hash[key] = [] } # create groups & sources in the same loop sources = possibles.map do |spec| source = spec.source groups[source] << spec source end.uniq.reverse activation_requests = [] sources.each do |source| groups[source]. sort_by {|spec| [spec.version, -Gem::Platform.platform_specificity_match(spec.platform, Gem::Platform.local)] }. map {|spec| ActivationRequest.new spec, dependency }. each {|activation_request| activation_requests << activation_request } end activation_requests end def dependencies_for(specification) return [] if @ignore_dependencies spec = specification.spec requests(spec, specification) end def requirement_satisfied_by?(requirement, activated, spec) matches_spec = requirement.matches_spec? spec return matches_spec if @soft_missing matches_spec && spec.spec.required_ruby_version.satisfied_by?(Gem.ruby_version) && spec.spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version) end def name_for(dependency) dependency.name end def allow_missing?(dependency) @soft_missing end def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by.with_index do |dependency, i| name = name_for(dependency) [ activated.vertex_named(name).payload ? 0 : 1, amount_constrained(dependency), conflicts[name] ? 0 : 1, activated.vertex_named(name).payload ? 0 : search_for(dependency).count, i, # for stable sort ] end end SINGLE_POSSIBILITY_CONSTRAINT_PENALTY = 1_000_000 private_constant :SINGLE_POSSIBILITY_CONSTRAINT_PENALTY if defined?(private_constant) # returns an integer \in (-\infty, 0] # a number closer to 0 means the dependency is less constraining # # dependencies w/ 0 or 1 possibilities (ignoring version requirements) # are given very negative values, so they _always_ sort first, # before dependencies that are unconstrained def amount_constrained(dependency) @amount_constrained ||= {} @amount_constrained[dependency.name] ||= begin name_dependency = Gem::Dependency.new(dependency.name) dependency_request_for_name = Gem::Resolver::DependencyRequest.new(name_dependency, dependency.requester) all = @set.find_all(dependency_request_for_name).size if all <= 1 all - SINGLE_POSSIBILITY_CONSTRAINT_PENALTY else search = search_for(dependency).size search - all end end end private :amount_constrained end require_relative "resolver/activation_request" require_relative "resolver/conflict" require_relative "resolver/dependency_request" require_relative "resolver/requirement_list" require_relative "resolver/stats" require_relative "resolver/set" require_relative "resolver/api_set" require_relative "resolver/composed_set" require_relative "resolver/best_set" require_relative "resolver/current_set" require_relative "resolver/git_set" require_relative "resolver/index_set" require_relative "resolver/installer_set" require_relative "resolver/lock_set" require_relative "resolver/vendor_set" require_relative "resolver/source_set" require_relative "resolver/specification" require_relative "resolver/spec_specification" require_relative "resolver/api_specification" require_relative "resolver/git_specification" require_relative "resolver/index_specification" require_relative "resolver/installed_specification" require_relative "resolver/local_specification" require_relative "resolver/lock_specification" require_relative "resolver/vendor_specification" PK!Fߺ̎core_ext/tcpsocket_init.rbnu[# frozen_string_literal: true require "socket" module CoreExtensions module TCPSocketExt def self.prepended(base) base.prepend Initializer end module Initializer CONNECTION_TIMEOUT = 5 IPV4_DELAY_SECONDS = 0.1 def initialize(host, serv, *rest) mutex = Thread::Mutex.new addrs = [] threads = [] cond_var = Thread::ConditionVariable.new Addrinfo.foreach(host, serv, nil, :STREAM) do |addr| Thread.report_on_exception = false threads << Thread.new(addr) do # give head start to ipv6 addresses sleep IPV4_DELAY_SECONDS if addr.ipv4? # raises Errno::ECONNREFUSED when ip:port is unreachable Socket.tcp(addr.ip_address, serv, connect_timeout: CONNECTION_TIMEOUT).close mutex.synchronize do addrs << addr.ip_address cond_var.signal end end end mutex.synchronize do timeout_time = CONNECTION_TIMEOUT + Time.now.to_f while addrs.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0 cond_var.wait(mutex, remaining_time) end host = addrs.shift unless addrs.empty? end threads.each {|t| t.kill.join if t.alive? } super(host, serv, *rest) end end end end TCPSocket.prepend CoreExtensions::TCPSocketExt PK!core_ext/kernel_require.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require "monitor" module Kernel RUBYGEMS_ACTIVATION_MONITOR = Monitor.new # :nodoc: # Make sure we have a reference to Ruby's original Kernel#require unless defined?(gem_original_require) # :stopdoc: alias_method :gem_original_require, :require private :gem_original_require # :startdoc: end ## # When RubyGems is required, Kernel#require is replaced with our own which # is capable of loading gems on demand. # # When you call require 'x', this is what happens: # * If the file can be loaded from the existing Ruby loadpath, it # is. # * Otherwise, installed gems are searched for a file that matches. # If it's found in gem 'y', that gem is activated (added to the # loadpath). # # The normal require functionality of returning false if # that file has already been loaded is preserved. def require(path) # :doc: return gem_original_require(path) unless Gem.discover_gems_on_require RUBYGEMS_ACTIVATION_MONITOR.synchronize do path = File.path(path) # If +path+ belongs to a default gem, we activate it and then go straight # to normal require if spec = Gem.find_default_spec(path) name = spec.name next if Gem.loaded_specs[name] # Ensure -I beats a default gem resolved_path = begin rp = nil load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths Gem.suffixes.find do |s| $LOAD_PATH[0...load_path_check_index].find do |lp| if File.symlink? lp # for backward compatibility next end full_path = File.expand_path(File.join(lp, "#{path}#{s}")) rp = full_path if File.file?(full_path) end end rp end next if resolved_path Kernel.send(:gem, name, Gem::Requirement.default_prerelease) Gem.load_bundler_extensions(Gem.loaded_specs[name].version) if name == "bundler" next end # If there are no unresolved deps, then we can use just try # normal require handle loading a gem from the rescue below. if Gem::Specification.unresolved_deps.empty? next end # If +path+ is for a gem that has already been loaded, don't # bother trying to find it in an unresolved gem, just go straight # to normal require. #-- # TODO request access to the C implementation of this to speed up RubyGems if Gem::Specification.find_active_stub_by_path(path) next end # Attempt to find +path+ in any unresolved gems... found_specs = Gem::Specification.find_in_unresolved path # If there are no directly unresolved gems, then try and find +path+ # in any gems that are available via the currently unresolved gems. # For example, given: # # a => b => c => d # # If a and b are currently active with c being unresolved and d.rb is # requested, then find_in_unresolved_tree will find d.rb in d because # it's a dependency of c. # if found_specs.empty? found_specs = Gem::Specification.find_in_unresolved_tree path found_specs.each(&:activate) # We found +path+ directly in an unresolved gem. Now we figure out, of # the possible found specs, which one we should activate. else # Check that all the found specs are just different # versions of the same gem names = found_specs.map(&:name).uniq if names.size > 1 raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ", "}" end # Ok, now find a gem that has no conflicts, starting # at the highest version. valid = found_specs.find {|s| !s.has_conflicts? } unless valid le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" le.name = names.first raise le end valid.activate end end begin gem_original_require(path) rescue LoadError => load_error if load_error.path == path && RUBYGEMS_ACTIVATION_MONITOR.synchronize { Gem.try_activate(path) } return gem_original_require(path) end raise load_error end end private :require end PK!icore_ext/kernel_gem.rbnu[# frozen_string_literal: true module Kernel ## # Use Kernel#gem to activate a specific version of +gem_name+. # # +requirements+ is a list of version requirements that the # specified gem must match, most commonly "= example.version.number". See # Gem::Requirement for how to specify a version requirement. # # If you will be activating the latest version of a gem, there is no need to # call Kernel#gem, Kernel#require will do the right thing for you. # # Kernel#gem returns true if the gem was activated, otherwise false. If the # gem could not be found, didn't match the version requirements, or a # different version was already activated, an exception will be raised. # # Kernel#gem should be called *before* any require statements (otherwise # RubyGems may load a conflicting library version). # # Kernel#gem only loads prerelease versions when prerelease +requirements+ # are given: # # gem 'rake', '>= 1.1.a', '< 2' # # In older RubyGems versions, the environment variable GEM_SKIP could be # used to skip activation of specified gems, for example to test out changes # that haven't been installed yet. Now RubyGems defers to -I and the # RUBYLIB environment variable to skip activation of a gem. # # Example: # # GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb def gem(gem_name, *requirements) # :doc: skip_list = (ENV["GEM_SKIP"] || "").split(/:/) raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name if gem_name.is_a? Gem::Dependency unless Gem::Deprecate.skip warn "#{Gem.location_of_caller.join ":"}:Warning: Kernel.gem no longer "\ "accepts a Gem::Dependency object, please pass the name "\ "and requirements directly" end requirements = gem_name.requirement gem_name = gem_name.name end dep = Gem::Dependency.new(gem_name, *requirements) loaded = Gem.loaded_specs[gem_name] return false if loaded && dep.matches_spec?(loaded) spec = dep.to_spec if spec if Gem::LOADED_SPECS_MUTEX.owned? spec.activate else Gem::LOADED_SPECS_MUTEX.synchronize { spec.activate } end end end private :gem end PK!,f\core_ext/kernel_warn.rbnu[# frozen_string_literal: true module Kernel rubygems_path = "#{__dir__}/" # Frames to be skipped start with this path. original_warn = instance_method(:warn) remove_method :warn class << self remove_method :warn end module_function define_method(:warn) {|*messages, **kw| unless uplevel = kw[:uplevel] return original_warn.bind_call(self, *messages, **kw) end # Ensure `uplevel` fits a `long` uplevel, = [uplevel].pack("l!").unpack("l!") if uplevel >= 0 start = 0 while uplevel >= 0 loc, = caller_locations(start, 1) unless loc # No more backtrace start += uplevel break end start += 1 next unless path = loc.path unless path.start_with?(rubygems_path, " # # Otherwise Gem::Source#<=> is used. def <=>(other) case other when Gem::Source::SpecificFile then return nil if @spec.name != other.spec.name @spec.version <=> other.spec.version else super end end end PK!~}} source/git.rbnu[# frozen_string_literal: true ## # A git gem for use in a gem dependencies file. # # Example: # # source = # Gem::Source::Git.new 'rake', 'git@example:rake.git', 'rake-10.1.0', false # # source.specs class Gem::Source::Git < Gem::Source ## # The name of the gem created by this git gem. attr_reader :name ## # The commit reference used for checking out this git gem. attr_reader :reference ## # When false the cache for this repository will not be updated. attr_accessor :remote ## # The git repository this gem is sourced from. attr_reader :repository ## # The directory for cache and git gem installation attr_accessor :root_dir ## # Does this repository need submodules checked out too? attr_reader :need_submodules ## # Creates a new git gem source for a gems from loaded from +repository+ at # the given +reference+. The +name+ is only used to track the repository # back to a gem dependencies file, it has no real significance as a git # repository may contain multiple gems. If +submodules+ is true, submodules # will be checked out when the gem is installed. def initialize(name, repository, reference, submodules = false) require_relative "../uri" @uri = Gem::Uri.parse(repository) @name = name @repository = repository @reference = reference || "HEAD" @need_submodules = submodules @remote = true @root_dir = Gem.dir end def <=>(other) case other when Gem::Source::Git then 0 when Gem::Source::Vendor, Gem::Source::Lock then -1 when Gem::Source then 1 end end def ==(other) # :nodoc: super && @name == other.name && @repository == other.repository && @reference == other.reference && @need_submodules == other.need_submodules end def git_command ENV.fetch("git", "git") end ## # Checks out the files for the repository into the install_dir. def checkout # :nodoc: cache return false unless File.exist? repo_cache_dir unless File.exist? install_dir system git_command, "clone", "--quiet", "--no-checkout", repo_cache_dir, install_dir end Dir.chdir install_dir do system git_command, "fetch", "--quiet", "--force", "--tags", install_dir success = system git_command, "reset", "--quiet", "--hard", rev_parse if @need_submodules require "open3" _, status = Open3.capture2e(git_command, "submodule", "update", "--quiet", "--init", "--recursive") success &&= status.success? end success end end ## # Creates a local cache repository for the git gem. def cache # :nodoc: return unless @remote if File.exist? repo_cache_dir Dir.chdir repo_cache_dir do system git_command, "fetch", "--quiet", "--force", "--tags", @repository, "refs/heads/*:refs/heads/*" end else system git_command, "clone", "--quiet", "--bare", "--no-hardlinks", @repository, repo_cache_dir end end ## # Directory where git gems get unpacked and so-forth. def base_dir # :nodoc: File.join @root_dir, "bundler" end ## # A short reference for use in git gem directories def dir_shortref # :nodoc: rev_parse[0..11] end ## # Nothing to download for git gems def download(full_spec, path) # :nodoc: end ## # The directory where the git gem will be installed. def install_dir # :nodoc: return unless File.exist? repo_cache_dir File.join base_dir, "gems", "#{@name}-#{dir_shortref}" end def pretty_print(q) # :nodoc: q.object_group(self) do q.group 2, "[Git: ", "]" do q.breakable q.text @repository q.breakable q.text @reference end end end ## # The directory where the git gem's repository will be cached. def repo_cache_dir # :nodoc: File.join @root_dir, "cache", "bundler", "git", "#{@name}-#{uri_hash}" end ## # Converts the git reference for the repository into a commit hash. def rev_parse # :nodoc: hash = nil Dir.chdir repo_cache_dir do hash = Gem::Util.popen(git_command, "rev-parse", @reference).strip end raise Gem::Exception, "unable to find reference #{@reference} in #{@repository}" unless $?.success? hash end ## # Loads all gemspecs in the repository def specs checkout return [] unless install_dir Dir.chdir install_dir do Dir["{,*,*/*}.gemspec"].filter_map do |spec_file| directory = File.dirname spec_file file = File.basename spec_file Dir.chdir directory do spec = Gem::Specification.load file if spec spec.base_dir = base_dir spec.extension_dir = File.join base_dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version, "#{name}-#{dir_shortref}" spec.full_gem_path = File.dirname spec.loaded_from if spec end spec end end end end ## # A hash for the git gem based on the git repository Gem::URI. def uri_hash # :nodoc: require_relative "../openssl" normalized = if @repository.match?(%r{^\w+://(\w+@)?}) uri = Gem::URI(@repository).normalize.to_s.sub %r{/$},"" uri.sub(/\A(\w+)/) { $1.downcase } else @repository end OpenSSL::Digest::SHA1.hexdigest normalized end end PK!¨y-  source/local.rbnu[# frozen_string_literal: true ## # The local source finds gems in the current directory for fulfilling # dependencies. class Gem::Source::Local < Gem::Source def initialize # :nodoc: @specs = nil @api_uri = nil @uri = nil @load_specs_names = {} end ## # Local sorts before Gem::Source and after Gem::Source::Installed def <=>(other) case other when Gem::Source::Installed, Gem::Source::Lock then -1 when Gem::Source::Local then 0 when Gem::Source then 1 end end def inspect # :nodoc: keys = @specs ? @specs.keys.sort : "NOT LOADED" format("#<%s specs: %p>", self.class, keys) end def load_specs(type) # :nodoc: @load_specs_names[type] ||= begin names = [] @specs = {} Dir["*.gem"].each do |file| pkg = Gem::Package.new(file) spec = pkg.spec rescue SystemCallError, Gem::Package::FormatError # ignore else tup = spec.name_tuple @specs[tup] = [File.expand_path(file), pkg] case type when :released unless pkg.spec.version.prerelease? names << pkg.spec.name_tuple end when :prerelease if pkg.spec.version.prerelease? names << pkg.spec.name_tuple end when :latest tup = pkg.spec.name_tuple cur = names.find {|x| x.name == tup.name } if !cur names << tup elsif cur.version < tup.version names.delete cur names << tup end else names << pkg.spec.name_tuple end end names end end def find_gem(gem_name, version = Gem::Requirement.default, prerelease = false) # :nodoc: load_specs :complete found = [] @specs.each do |n, data| next unless n.name == gem_name s = data[1].spec if version.satisfied_by?(s.version) if prerelease found << s elsif !s.version.prerelease? || version.prerelease? found << s end end end found.max_by(&:version) end def fetch_spec(name) # :nodoc: load_specs :complete if data = @specs[name] data.last.spec else raise Gem::Exception, "Unable to find spec for #{name.inspect}" end end def download(spec, cache_dir = nil) # :nodoc: load_specs :complete @specs.each do |_name, data| return data[0] if data[1].spec == spec end raise Gem::Exception, "Unable to find file for '#{spec.full_name}'" end def pretty_print(q) # :nodoc: q.object_group(self) do q.group 2, "[Local gems:", "]" do q.breakable if @specs q.seplist @specs.keys do |v| q.text v.full_name end end end end end end PK!_t'+source/vendor.rbnu[# frozen_string_literal: true ## # This represents a vendored source that is similar to an installed gem. class Gem::Source::Vendor < Gem::Source::Installed ## # Creates a new Vendor source for a gem that was unpacked at +path+. def initialize(path) @uri = path end def <=>(other) case other when Gem::Source::Lock then -1 when Gem::Source::Vendor then 0 when Gem::Source then 1 end end end PK!Gcsource/installed.rbnu[# frozen_string_literal: true ## # Represents an installed gem. This is used for dependency resolution. class Gem::Source::Installed < Gem::Source def initialize # :nodoc: @uri = nil end ## # Installed sources sort before all other sources def <=>(other) case other when Gem::Source::Git, Gem::Source::Lock, Gem::Source::Vendor then -1 when Gem::Source::Installed then 0 when Gem::Source then 1 end end ## # We don't need to download an installed gem def download(spec, path) nil end def pretty_print(q) # :nodoc: q.object_group(self) do q.text "[Installed]" end end end PK!_Nsource/lock.rbnu[# frozen_string_literal: true ## # A Lock source wraps an installed gem's source and sorts before other sources # during dependency resolution. This allows RubyGems to prefer gems from # dependency lock files. class Gem::Source::Lock < Gem::Source ## # The wrapped Gem::Source attr_reader :wrapped ## # Creates a new Lock source that wraps +source+ and moves it earlier in the # sort list. def initialize(source) @wrapped = source end def <=>(other) # :nodoc: case other when Gem::Source::Lock then @wrapped <=> other.wrapped when Gem::Source then 1 end end def ==(other) # :nodoc: (self <=> other) == 0 end def hash # :nodoc: @wrapped.hash ^ 3 end ## # Delegates to the wrapped source's fetch_spec method. def fetch_spec(name_tuple) @wrapped.fetch_spec name_tuple end def uri # :nodoc: @wrapped.uri end end PK!N:A:Aconfig_file.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "user_interaction" require "rbconfig" ## # Gem::ConfigFile RubyGems options and gem command options from gemrc. # # gemrc is a YAML file that uses strings to match gem command arguments and # symbols to match RubyGems options. # # Gem command arguments use a String key that matches the command name and # allow you to specify default arguments: # # install: --no-rdoc --no-ri # update: --no-rdoc --no-ri # # You can use gem: to set default arguments for all commands. # # RubyGems options use symbol keys. Valid options are: # # +:backtrace+:: See #backtrace # +:sources+:: Sets Gem::sources # +:verbose+:: See #verbose # +:concurrent_downloads+:: See #concurrent_downloads # # gemrc files may exist in various locations and are read and merged in # the following order: # # - system wide (/etc/gemrc) # - per user (~/.gemrc) # - per environment (gemrc files listed in the GEMRC environment variable) class Gem::ConfigFile include Gem::UserInteraction DEFAULT_BACKTRACE = true DEFAULT_BULK_THRESHOLD = 1000 DEFAULT_VERBOSITY = true DEFAULT_UPDATE_SOURCES = true DEFAULT_CONCURRENT_DOWNLOADS = 8 DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365 DEFAULT_IPV4_FALLBACK_ENABLED = false # TODO: Use false as default value for this option in RubyGems 4.0 DEFAULT_INSTALL_EXTENSION_IN_LIB = true ## # For Ruby packagers to set configuration defaults. Set in # rubygems/defaults/operating_system.rb OPERATING_SYSTEM_DEFAULTS = Gem.operating_system_defaults ## # For Ruby implementers to set configuration defaults. Set in # rubygems/defaults/#{RUBY_ENGINE}.rb PLATFORM_DEFAULTS = Gem.platform_defaults # :stopdoc: SYSTEM_CONFIG_PATH = begin require "etc" Etc.sysconfdir rescue LoadError, NoMethodError RbConfig::CONFIG["sysconfdir"] || "/etc" end # :startdoc: SYSTEM_WIDE_CONFIG_FILE = File.join SYSTEM_CONFIG_PATH, "gemrc" ## # List of arguments supplied to the config file object. attr_reader :args ## # Where to look for gems (deprecated) attr_accessor :path ## # Where to install gems (deprecated) attr_accessor :home ## # True if we print backtraces on errors. attr_writer :backtrace ## # Bulk threshold value. If the number of missing gems are above this # threshold value, then a bulk download technique is used. (deprecated) attr_accessor :bulk_threshold ## # Verbose level of output: # * false -- No output # * true -- Normal output # * :loud -- Extra output attr_accessor :verbose ## # Number of gem downloads that should be performed concurrently. attr_accessor :concurrent_downloads ## # True if we want to update the SourceInfoCache every time, false otherwise attr_accessor :update_sources ## # True if we want to force specification of gem server when pushing a gem attr_accessor :disable_default_gem_server # openssl verify mode value, used for remote https connection attr_reader :ssl_verify_mode ## # Path name of directory or file of openssl CA certificate, used for remote # https connection attr_accessor :ssl_ca_cert ## # sources to look for gems attr_accessor :sources ## # Expiration length to sign a certificate attr_accessor :cert_expiration_length_days ## # Install extensions into lib as well as into the extension directory. attr_accessor :install_extension_in_lib ## # == Experimental == # Fallback to IPv4 when IPv6 is not reachable or slow (default: false) attr_accessor :ipv4_fallback_enabled ## # Path name of directory or file of openssl client certificate, used for remote https connection with client authentication attr_reader :ssl_client_cert ## # Create the config file object. +args+ is the list of arguments # from the command line. # # The following command line options are handled early here rather # than later at the time most command options are processed. # # --config-file, --config-file==NAME:: # Obviously these need to be handled by the ConfigFile object to ensure we # get the right config file. # # --backtrace:: # Backtrace needs to be turned on early so that errors before normal # option parsing can be properly handled. # # --debug:: # Enable Ruby level debug messages. Handled early for the same reason as # --backtrace. #-- # TODO: parse options upstream, pass in options directly def initialize(args) set_config_file_name(args) @backtrace = DEFAULT_BACKTRACE @bulk_threshold = DEFAULT_BULK_THRESHOLD @verbose = DEFAULT_VERBOSITY @update_sources = DEFAULT_UPDATE_SOURCES @concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS @cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS @install_extension_in_lib = DEFAULT_INSTALL_EXTENSION_IN_LIB @ipv4_fallback_enabled = ENV["IPV4_FALLBACK_ENABLED"] == "true" || DEFAULT_IPV4_FALLBACK_ENABLED operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS) platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS) system_config = load_file SYSTEM_WIDE_CONFIG_FILE user_config = load_file config_file_name environment_config = (ENV["GEMRC"] || ""). split(File::PATH_SEPARATOR).inject({}) do |result, file| result.merge load_file file end @hash = operating_system_config.merge platform_config unless args.index "--norc" @hash = @hash.merge system_config @hash = @hash.merge user_config @hash = @hash.merge environment_config end @hash.transform_keys! do |k| # gemhome and gempath are not working with symbol keys if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days install_extension_in_lib ipv4_fallback_enabled sources disable_default_gem_server ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k) k.to_sym else k end end # HACK: these override command-line args, which is bad @backtrace = @hash[:backtrace] if @hash.key? :backtrace @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold @verbose = @hash[:verbose] if @hash.key? :verbose @update_sources = @hash[:update_sources] if @hash.key? :update_sources # TODO: We should handle concurrent_downloads same as other options @cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days @install_extension_in_lib = @hash[:install_extension_in_lib] if @hash.key? :install_extension_in_lib @ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled @home = @hash[:gemhome] if @hash.key? :gemhome @path = @hash[:gempath] if @hash.key? :gempath @sources = @hash[:sources] if @hash.key? :sources @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert @api_keys = nil @rubygems_api_key = nil handle_arguments args end ## # Hash of RubyGems.org and alternate API keys def api_keys load_api_keys unless @api_keys @api_keys end ## # Checks the permissions of the credentials file. If they are not 0600 an # error message is displayed and RubyGems aborts. def check_credentials_permissions return if Gem.win_platform? # windows doesn't write 0600 as 0600 return unless File.exist? credentials_path existing_permissions = File.stat(credentials_path).mode & 0o777 return if existing_permissions == 0o600 alert_error <<-ERROR Your gem push credentials file located at: \t#{credentials_path} has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required. To fix this error run: \tchmod 0600 #{credentials_path} You should reset your credentials at: \thttps://rubygems.org/profile/edit if you believe they were disclosed to a third party. ERROR terminate_interaction 1 end ## # Location of RubyGems.org credentials def credentials_path credentials = File.join Gem.user_home, ".gem", "credentials" if File.exist? credentials credentials else File.join Gem.data_home, "gem", "credentials" end end def load_api_keys check_credentials_permissions @api_keys = if File.exist? credentials_path load_file(credentials_path) else @hash end if @api_keys.key? :rubygems_api_key @rubygems_api_key = @api_keys[:rubygems_api_key] @api_keys[:rubygems] = @api_keys.delete :rubygems_api_key unless @api_keys.key? :rubygems end end ## # Returns the RubyGems.org API key def rubygems_api_key load_api_keys unless @rubygems_api_key @rubygems_api_key end ## # Sets the RubyGems.org API key to +api_key+ def rubygems_api_key=(api_key) set_api_key :rubygems_api_key, api_key @rubygems_api_key = api_key end ## # Set a specific host's API key to +api_key+ def set_api_key(host, api_key) check_credentials_permissions config = load_file(credentials_path).merge(host => api_key) dirname = File.dirname credentials_path require "fileutils" FileUtils.mkdir_p(dirname) permissions = 0o600 & ~File.umask File.open(credentials_path, "w", permissions) do |f| f.write self.class.dump_with_rubygems_yaml(config) end load_api_keys # reload end ## # Remove the +~/.gem/credentials+ file to clear all the current sessions. def unset_api_key! return false unless File.exist?(credentials_path) File.delete(credentials_path) end def load_file(filename) yaml_errors = [ArgumentError] return {} unless filename && !filename.empty? && File.exist?(filename) begin config = self.class.load_with_rubygems_config_hash(File.read(filename)) if config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") } warn "Failed to load #{filename} because it doesn't contain valid YAML hash" return {} else return config end rescue *yaml_errors => e warn "Failed to load #{filename}, #{e}" rescue Errno::EACCES warn "Failed to load #{filename} due to permissions problem." end {} end # True if the backtrace option has been specified, or debug is on. def backtrace @backtrace || $DEBUG end # Check state file is writable. Creates empty file if not present to ensure we can write to it. def state_file_writable? if File.exist?(state_file_name) File.writable?(state_file_name) else require "fileutils" FileUtils.mkdir_p File.dirname(state_file_name) File.open(state_file_name, "w") {} true end rescue Errno::EACCES false end # The name of the configuration file. def config_file_name @config_file_name || Gem.config_file end # The name of the state file. def state_file_name Gem.state_file end # Reads time of last update check from state file def last_update_check if File.readable?(state_file_name) File.read(state_file_name).to_i else 0 end end # Writes time of last update check to state file def last_update_check=(timestamp) File.write(state_file_name, timestamp.to_s) if state_file_writable? end # Delegates to @hash def each(&block) hash = @hash.dup hash.delete :update_sources hash.delete :verbose hash.delete :backtrace hash.delete :bulk_threshold yield :update_sources, @update_sources yield :verbose, @verbose yield :backtrace, @backtrace yield :bulk_threshold, @bulk_threshold yield "config_file_name", @config_file_name if @config_file_name hash.each(&block) end # Handle the command arguments. def handle_arguments(arg_list) @args = [] arg_list.each do |arg| case arg when /^--(backtrace|traceback)$/ then @backtrace = true when /^--debug$/ then $DEBUG = true warn "NOTE: Debugging mode prints all exceptions even when rescued" else @args << arg end end end # Really verbose mode gives you extra output. def really_verbose case verbose when true, false, nil then false else true end end # to_yaml only overwrites things you can't override on the command line. def to_yaml # :nodoc: yaml_hash = {} yaml_hash[:backtrace] = @hash.fetch(:backtrace, DEFAULT_BACKTRACE) yaml_hash[:bulk_threshold] = @hash.fetch(:bulk_threshold, DEFAULT_BULK_THRESHOLD) yaml_hash[:sources] = Gem.sources.to_a yaml_hash[:update_sources] = @hash.fetch(:update_sources, DEFAULT_UPDATE_SOURCES) yaml_hash[:verbose] = @hash.fetch(:verbose, DEFAULT_VERBOSITY) yaml_hash[:concurrent_downloads] = @hash.fetch(:concurrent_downloads, DEFAULT_CONCURRENT_DOWNLOADS) yaml_hash[:install_extension_in_lib] = @hash.fetch(:install_extension_in_lib, DEFAULT_INSTALL_EXTENSION_IN_LIB) yaml_hash[:ssl_verify_mode] = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode yaml_hash[:ssl_ca_cert] = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert yaml_hash[:ssl_client_cert] = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert keys = yaml_hash.keys.map(&:to_s) keys << "debug" re = Regexp.union(*keys) @hash.each do |key, value| key = key.to_s next if key&.match?(re) yaml_hash[key.to_s] = value end self.class.dump_with_rubygems_yaml(yaml_hash) end # Writes out this config file, replacing its source. def write require "fileutils" FileUtils.mkdir_p File.dirname(config_file_name) File.open config_file_name, "w" do |io| io.write to_yaml end end # Return the configuration information for +key+. def [](key) @hash[key] || @hash[key.to_s] end # Set configuration option +key+ to +value+. def []=(key, value) @hash[key] = value end def ==(other) # :nodoc: self.class === other && @backtrace == other.backtrace && @bulk_threshold == other.bulk_threshold && @verbose == other.verbose && @update_sources == other.update_sources && @hash == other.hash end attr_reader :hash protected :hash def self.dump_with_rubygems_yaml(content) content.transform_keys! do |k| k.is_a?(Symbol) ? ":#{k}" : k end require_relative "yaml_serializer" Gem::YAMLSerializer.dump(content) end def self.load_with_rubygems_config_hash(yaml) require_relative "yaml_serializer" content = Gem::YAMLSerializer.load(yaml) deep_transform_config_keys!(content) end private def self.deep_transform_config_keys!(config) config.transform_keys! do |k| if k.match?(/\A:(.*)\Z/) k[1..-1].to_sym elsif k.include?("__") || k.match?(%r{/\Z}) if k.is_a?(Symbol) k.to_s.gsub(/__/,".").gsub(%r{/\Z}, "").to_sym else k.dup.gsub(/__/,".").gsub(%r{/\Z}, "") end else k end end config.transform_values! do |v| if v.is_a?(String) if v.match?(/\A:(.*)\Z/) v[1..-1].to_sym elsif v.match?(/\A[+-]?\d+\Z/) v.to_i elsif v.match?(/\Atrue|false\Z/) v == "true" elsif v.empty? nil else v end elsif v.empty? nil elsif v.is_a?(Hash) deep_transform_config_keys!(v) else v end end config end def set_config_file_name(args) @config_file_name = ENV["GEMRC"] need_config_file_name = false args.each do |arg| if need_config_file_name @config_file_name = arg need_config_file_name = false elsif arg =~ /^--config-file=(.*)/ @config_file_name = $1 elsif /^--config-file$/.match?(arg) need_config_file_name = true end end end end PK!&&remote_fetcher.rbnu[# frozen_string_literal: true require_relative "../rubygems" require_relative "request" require_relative "request/connection_pools" require_relative "s3_uri_signer" require_relative "uri_formatter" require_relative "uri" require_relative "user_interaction" ## # RemoteFetcher handles the details of fetching gems and gem information from # a remote source. class Gem::RemoteFetcher include Gem::UserInteraction ## # A FetchError exception wraps up the various possible IO and HTTP failures # that could happen while downloading from the internet. class FetchError < Gem::Exception ## # The URI which was being accessed when the exception happened. attr_accessor :uri, :original_uri def initialize(message, uri) uri = Gem::Uri.new(uri) super uri.redact_credentials_from(message) @original_uri = uri.to_s @uri = uri.redacted.to_s end def to_s # :nodoc: "#{super} (#{uri})" end end ## # A FetchError that indicates that the reason for not being # able to fetch data was that the host could not be contacted class UnknownHostError < FetchError end deprecate_constant(:UnknownHostError) @fetcher = nil ## # Cached RemoteFetcher instance. def self.fetcher @fetcher ||= new Gem.configuration[:http_proxy] end attr_accessor :headers ## # Initialize a remote fetcher using the source URI and possible proxy # information. # # +proxy+ # * [String]: explicit specification of proxy; overrides any environment # variable setting # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, # HTTP_PROXY_PASS) # * :no_proxy: ignore environment variables and _don't_ use a proxy # # +headers+: A set of additional HTTP headers to be sent to the server when # fetching the gem. def initialize(proxy = nil, dns = nil, headers = {}) require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled require_relative "vendored_net_http" require_relative "vendor/uri/lib/uri" Socket.do_not_reverse_lookup = true @proxy = proxy @pools = {} @pool_lock = Thread::Mutex.new @pool_size = 1 @cert_files = Gem::Request.get_cert_files @headers = headers end ## # Given a name and requirement, downloads this gem into cache and returns the # filename. Returns nil if the gem cannot be located. #-- # Should probably be integrated with #download below, but that will be a # larger, more encompassing effort. -erikh def download_to_cache(dependency) found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency return if found.empty? spec, source = found.max_by {|(s,_)| s.version } download spec, source.uri end ## # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is # already there. If the source_uri is local the gem cache dir copy is # always replaced. def download(spec, source_uri, install_dir = Gem.dir) install_cache_dir = File.join install_dir, "cache" cache_dir = if Dir.pwd == install_dir # see fetch_command install_dir elsif File.writable?(install_cache_dir) || (File.writable?(install_dir) && !File.exist?(install_cache_dir)) install_cache_dir else File.join Gem.user_dir, "cache" end gem_file_name = File.basename spec.cache_file local_gem_path = File.join cache_dir, gem_file_name require "fileutils" begin FileUtils.mkdir_p cache_dir rescue StandardError nil end unless File.exist? cache_dir source_uri = Gem::Uri.new(source_uri) scheme = source_uri.scheme # Gem::URI.parse gets confused by MS Windows paths with forward slashes. scheme = nil if /^[a-z]$/i.match?(scheme) # REFACTOR: split this up and dispatch on scheme (eg download_http) # REFACTOR: be sure to clean up fake fetcher when you do this... cleaner case scheme when "http", "https", "s3" then unless File.exist? local_gem_path begin verbose "Downloading gem #{gem_file_name}" remote_gem_path = source_uri + "gems/#{gem_file_name}" cache_update_path remote_gem_path, local_gem_path rescue FetchError raise if spec.original_platform == spec.platform alternate_name = "#{spec.original_name}.gem" verbose "Failed, downloading gem #{alternate_name}" remote_gem_path = source_uri + "gems/#{alternate_name}" cache_update_path remote_gem_path, local_gem_path end end when "file" then begin path = source_uri.path path = File.dirname(path) if File.extname(path) == ".gem" remote_gem_path = Gem::Util.correct_for_windows_path(File.join(path, "gems", gem_file_name)) FileUtils.cp(remote_gem_path, local_gem_path) rescue Errno::EACCES local_gem_path = source_uri.to_s end verbose "Using local gem #{local_gem_path}" when nil then source_path = if Gem.win_platform? && source_uri.scheme && !source_uri.path.include?(":") "#{source_uri.scheme}:#{source_uri.path}" else source_uri.path end source_path = Gem::UriFormatter.new(source_path).unescape begin FileUtils.cp source_path, local_gem_path unless File.identical?(source_path, local_gem_path) rescue Errno::EACCES local_gem_path = source_uri.to_s end verbose "Using local gem #{local_gem_path}" else raise ArgumentError, "unsupported URI scheme #{source_uri.scheme}" end local_gem_path end ## # File Fetcher. Dispatched by +fetch_path+. Use it instead. def fetch_file(uri, *_) Gem.read_binary Gem::Util.correct_for_windows_path uri.path end ## # HTTP Fetcher. Dispatched by +fetch_path+. Use it instead. def fetch_http(uri, last_modified = nil, head = false, depth = 0) fetch_type = head ? Gem::Net::HTTP::Head : Gem::Net::HTTP::Get response = request uri, fetch_type, last_modified do |req| headers.each {|k,v| req.add_field(k,v) } end case response when Gem::Net::HTTPOK, Gem::Net::HTTPNotModified then response.uri = uri head ? response : response.body when Gem::Net::HTTPMovedPermanently, Gem::Net::HTTPFound, Gem::Net::HTTPSeeOther, Gem::Net::HTTPTemporaryRedirect then raise FetchError.new("too many redirects", uri) if depth > 10 unless location = response["Location"] raise FetchError.new("redirecting but no redirect location was given", uri) end location = Gem::Uri.new location if https?(uri) && !https?(location) raise FetchError.new("redirecting to non-https resource: #{location}", uri) end fetch_http(location, last_modified, head, depth + 1) else raise FetchError.new("bad response #{response.message} #{response.code}", uri) end end alias_method :fetch_https, :fetch_http ## # Downloads +uri+ and returns it as a String. def fetch_path(uri, mtime = nil, head = false) uri = Gem::Uri.new uri method = { "http" => "fetch_http", "https" => "fetch_http", "s3" => "fetch_s3", "file" => "fetch_file", }.fetch(uri.scheme) { raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" } data = send method, uri, mtime, head if data && !head && uri.to_s.end_with?(".gz") begin data = Gem::Util.gunzip data rescue Zlib::GzipFile::Error raise FetchError.new("server did not return a valid file", uri) end end data rescue Gem::Timeout::Error, IOError, SocketError, SystemCallError, *(OpenSSL::SSL::SSLError if Gem::HAVE_OPENSSL) => e raise FetchError.new("#{e.class}: #{e}", uri) end def fetch_s3(uri, mtime = nil, head = false) begin public_uri = s3_uri_signer(uri, head ? "HEAD" : "GET").sign rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e raise FetchError.new(e.message, "s3://#{uri.host}") end fetch_https public_uri, mtime, head end # we have our own signing code here to avoid a dependency on the aws-sdk gem def s3_uri_signer(uri, method) Gem::S3URISigner.new(uri, method) end ## # Downloads +uri+ to +path+ if necessary. If no path is given, it just # passes the data. def cache_update_path(uri, path = nil, update = true) mtime = begin path && File.stat(path).mtime rescue StandardError nil end data = fetch_path(uri, mtime) if data.nil? # indicates the server returned 304 Not Modified return Gem.read_binary(path) end if update && path Gem.write_binary(path, data) end data end ## # Performs a Gem::Net::HTTP request of type +request_class+ on +uri+ returning # a Gem::Net::HTTP response object. request maintains a table of persistent # connections to reduce connect overhead. def request(uri, request_class, last_modified = nil) proxy = proxy_for @proxy, uri pool = pools_for(proxy).pool_for uri request = Gem::Request.new uri, request_class, last_modified, pool request.fetch do |req| yield req if block_given? end end def https?(uri) uri.scheme.casecmp("https").zero? end def close_all @pools.each_value(&:close_all) end private def proxy_for(proxy, uri) Gem::Request.proxy_uri(proxy || Gem::Request.get_proxy_from_env(uri.scheme)) end def pools_for(proxy) @pool_lock.synchronize do @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files, @pool_size end end end PK!s!,ނrequirement.rbnu[# frozen_string_literal: true require_relative "version" ## # A Requirement is a set of one or more version restrictions. It supports a # few (=, !=, >, <, >=, <=, ~>) different restriction operators. # # See Gem::Version for a description on how versions and requirements work # together in RubyGems. class Gem::Requirement OPS = { # :nodoc: "=" => lambda {|v, r| v == r }, "!=" => lambda {|v, r| v != r }, ">" => lambda {|v, r| v > r }, "<" => lambda {|v, r| v < r }, ">=" => lambda {|v, r| v >= r }, "<=" => lambda {|v, r| v <= r }, "~>" => lambda {|v, r| v >= r && v.release < r.bump }, }.freeze SOURCE_SET_REQUIREMENT = Struct.new(:for_lockfile).new "!" # :nodoc: quoted = Regexp.union(OPS.keys) PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*".freeze # :nodoc: ## # A regular expression that matches a requirement PATTERN = /\A#{PATTERN_RAW}\z/ ## # The default requirement matches any non-prerelease version DefaultRequirement = [">=", Gem::Version.new(0)].freeze ## # The default requirement matches any version DefaultPrereleaseRequirement = [">=", Gem::Version.new("0.a")].freeze ## # Raised when a bad requirement is encountered class BadRequirementError < ArgumentError; end ## # Factory method to create a Gem::Requirement object. Input may be # a Version, a String, or nil. Intended to simplify client code. # # If the input is "weird", the default version requirement is # returned. def self.create(*inputs) return new inputs if inputs.length > 1 input = inputs.shift case input when Gem::Requirement then input when Gem::Version, Array then new input when "!" then source_set else if input.respond_to? :to_str new [input.to_str] else default end end end def self.default new ">= 0" end def self.default_prerelease new ">= 0.a" end ### # A source set requirement, used for Gemfiles and lockfiles def self.source_set # :nodoc: SOURCE_SET_REQUIREMENT end ## # Parse +obj+, returning an [op, version] pair. +obj+ can # be a String or a Gem::Version. # # If +obj+ is a String, it can be either a full requirement # specification, like ">= 1.2", or a simple version number, # like "1.2". # # parse("> 1.0") # => [">", Gem::Version.new("1.0")] # parse("1.0") # => ["=", Gem::Version.new("1.0")] # parse(Gem::Version.new("1.0")) # => ["=, Gem::Version.new("1.0")] def self.parse(obj) return ["=", obj] if Gem::Version === obj unless PATTERN =~ obj.to_s raise BadRequirementError, "Illformed requirement [#{obj.inspect}]" end op = -($1 || "=") version = -$2 if op == ">=" && version == "0" DefaultRequirement elsif op == ">=" && version == "0.a" DefaultPrereleaseRequirement else [op, Gem::Version.new(version)] end end ## # An array of requirement pairs. The first element of the pair is # the op, and the second is the Gem::Version. attr_reader :requirements # :nodoc: ## # Constructs a requirement from +requirements+. Requirements can be # Strings, Gem::Versions, or Arrays of those. +nil+ and duplicate # requirements are ignored. An empty set of +requirements+ is the # same as ">= 0". def initialize(*requirements) requirements = requirements.flatten requirements.compact! requirements.uniq! if requirements.empty? @requirements = [DefaultRequirement] else @requirements = requirements.map! {|r| self.class.parse r } end end ## # Concatenates the +new+ requirements onto this requirement. def concat(new) new = new.flatten new.compact! new.uniq! new = new.map {|r| self.class.parse r } @requirements.concat new end ## # Formats this requirement for use in a Gem::RequestSet::Lockfile. def for_lockfile # :nodoc: return if @requirements == [DefaultRequirement] list = requirements.sort_by do |_, version| version end.map do |op, version| "#{op} #{version}" end.uniq " (#{list.join ", "})" end ## # true if this gem has no requirements. def none? if @requirements.size == 1 @requirements[0] == DefaultRequirement else false end end ## # true if the requirement is for only an exact version def exact? return false unless @requirements.size == 1 @requirements[0][0] == "=" end def as_list # :nodoc: requirements.map {|op, version| "#{op} #{version}" } end def hash # :nodoc: requirements.map {|r| r.first == "~>" ? [r[0], r[1].to_s] : r }.sort.hash end def marshal_dump # :nodoc: [@requirements] end def marshal_load(array) # :nodoc: @requirements = array[0] raise TypeError, "wrong @requirements" unless Array === @requirements && @requirements.all? {|r| r.size == 2 && (r.first.is_a?(String) || r[0] = "=") && r.last.is_a?(Gem::Version) } end def yaml_initialize(tag, vals) # :nodoc: vals.each do |ivar, val| instance_variable_set "@#{ivar}", val end end def init_with(coder) # :nodoc: yaml_initialize coder.tag, coder.map end def encode_with(coder) # :nodoc: coder.add "requirements", @requirements end ## # A requirement is a prerelease if any of the versions inside of it # are prereleases def prerelease? requirements.any? {|r| r.last.prerelease? } end def pretty_print(q) # :nodoc: q.group 1, "Gem::Requirement.new(", ")" do q.pp as_list end end ## # True if +version+ satisfies this Requirement. def satisfied_by?(version) raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless Gem::Version === version requirements.all? {|op, rv| OPS.fetch(op).call version, rv } end alias_method :===, :satisfied_by? alias_method :=~, :satisfied_by? ## # True if the requirement will not always match the latest version. def specific? return true if @requirements.length > 1 # GIGO, > 1, > 2 is silly !%w[> >=].include? @requirements.first.first # grab the operator end def to_s # :nodoc: as_list.join ", " end def ==(other) # :nodoc: return unless Gem::Requirement === other # An == check is always necessary return false unless _sorted_requirements == other._sorted_requirements # An == check is sufficient unless any requirements use ~> return true unless _tilde_requirements.any? # If any requirements use ~> we use the stricter `#eql?` that also checks # that version precision is the same _tilde_requirements.eql?(other._tilde_requirements) end protected def _sorted_requirements @_sorted_requirements ||= requirements.sort_by(&:to_s) end def _tilde_requirements @_tilde_requirements ||= _sorted_requirements.select {|r| r.first == "~>" } end def initialize_copy(other) # :nodoc: @requirements = other.requirements.dup super end end class Gem::Version # This is needed for compatibility with older yaml # gemspecs. Requirement = Gem::Requirement # :nodoc: end PK!""5rdoc.rbnu[# frozen_string_literal: true require_relative "../rubygems" begin require "rdoc/rubygems_hook" module Gem ## # Returns whether RDoc defines its own install hooks through a RubyGems # plugin. This and whatever is guarded by it can be removed once no # supported Ruby ships with RDoc older than 6.9.0. def self.rdoc_hooks_defined_via_plugin? Gem::Version.new(::RDoc::VERSION) >= Gem::Version.new("6.9.0") end if rdoc_hooks_defined_via_plugin? RDoc = ::RDoc::RubyGemsHook else RDoc = ::RDoc::RubygemsHook Gem.done_installing(&Gem::RDoc.method(:generation_hook)) end end rescue LoadError end PK!C  available_set.rbnu[# frozen_string_literal: true class Gem::AvailableSet include Enumerable Tuple = Struct.new(:spec, :source) attr_accessor :remote # :nodoc: def initialize @set = [] @sorted = nil @remote = true end attr_reader :set def add(spec, source) @set << Tuple.new(spec, source) @sorted = nil self end def <<(o) case o when Gem::AvailableSet s = o.set when Array s = o.map do |sp,so| if !sp.is_a?(Gem::Specification) || !so.is_a?(Gem::Source) raise TypeError, "Array must be in [[spec, source], ...] form" end Tuple.new(sp,so) end else raise TypeError, "must be a Gem::AvailableSet" end @set += s @sorted = nil self end ## # Yields each Tuple in this AvailableSet def each return enum_for __method__ unless block_given? @set.each do |tuple| yield tuple end end ## # Yields the Gem::Specification for each Tuple in this AvailableSet def each_spec return enum_for __method__ unless block_given? each do |tuple| yield tuple.spec end end def empty? @set.empty? end def all_specs @set.map(&:spec) end def match_platform! @set.reject! {|t| !Gem::Platform.match_spec?(t.spec) } @sorted = nil self end def sorted @sorted ||= @set.sort do |a,b| i = b.spec <=> a.spec i != 0 ? i : (a.source <=> b.source) end end def size @set.size end def source_for(spec) f = @set.find {|t| t.spec == spec } f.source end ## # Converts this AvailableSet into a RequestSet that can be used to install # gems. # # If +development+ is :none then no development dependencies are installed. # Other options are :shallow for only direct development dependencies of the # gems in this set or :all for all development dependencies. def to_request_set(development = :none) request_set = Gem::RequestSet.new request_set.development = development == :all each_spec do |spec| request_set.always_install << spec request_set.gem spec.name, spec.version request_set.import spec.development_dependencies if development == :shallow end request_set end ## # # Used by the Resolver, the protocol to use a AvailableSet as a # search Set. def find_all(req) dep = req.dependency match = @set.find_all do |t| dep.match? t.spec end match.map do |t| Gem::Resolver::LocalSpecification.new(self, t.spec, t.source) end end def prefetch(reqs) end def pick_best! return self if empty? @set = [sorted.first] @sorted = nil self end def remove_installed!(dep) @set.reject! do |_t| # already locally installed Gem::Specification.any? do |installed_spec| dep.name == installed_spec.name && dep.requirement.satisfied_by?(installed_spec.version) end end @sorted = nil self end def inject_into_list(dep_list) @set.each {|t| dep_list.add t.spec } end end PK!GyIIvendored_tsort.rbnu[# frozen_string_literal: true require_relative "vendor/tsort/lib/tsort" PK!r yaml_serializer.rbnu[# frozen_string_literal: true module Gem # A stub yaml serializer that can handle only hashes and strings (as of now). module YAMLSerializer module_function def dump(hash) yaml = String.new("---") yaml << dump_hash(hash) end def dump_hash(hash) yaml = String.new("\n") hash.each do |k, v| yaml << k << ":" if v.is_a?(Hash) yaml << dump_hash(v).gsub(/^(?!$)/, " ") # indent all non-empty lines elsif v.is_a?(Array) # Expected to be array of strings if v.empty? yaml << " []\n" else yaml << "\n- " << v.map {|s| s.to_s.gsub(/\s+/, " ").inspect }.join("\n- ") << "\n" end else yaml << " " << v.to_s.gsub(/\s+/, " ").inspect << "\n" end end yaml end ARRAY_REGEX = / ^ (?:[ ]*-[ ]) # '- ' before array items (['"]?) # optional opening quote (.*) # value \1 # matching closing quote $ /xo HASH_REGEX = / ^ ([ ]*) # indentations ([^#]+) # key excludes comment char '#' (?::(?=(?:\s|$))) # : (without the lookahead the #key includes this when : is present in value) [ ]? (['"]?) # optional opening quote (.*) # value \3 # matching closing quote $ /xo def load(str) res = {} stack = [res] last_hash = nil last_empty_key = nil str.split(/\r?\n/) do |line| if match = HASH_REGEX.match(line) indent, key, quote, val = match.captures val = strip_comment(val) depth = indent.size / 2 if quote.empty? && val.empty? new_hash = {} stack[depth][key] = new_hash stack[depth + 1] = new_hash last_empty_key = key last_hash = stack[depth] else val = [] if val == "[]" # empty array stack[depth][key] = val end elsif match = ARRAY_REGEX.match(line) _, val = match.captures val = strip_comment(val) last_hash[last_empty_key] = [] unless last_hash[last_empty_key].is_a?(Array) last_hash[last_empty_key].push(val) end end res end def strip_comment(val) if val.include?("#") && !val.start_with?("#") val.split("#", 2).first.strip else val end end class << self private :dump_hash end end end PK!бsafe_marshal.rbnu[# frozen_string_literal: true require "stringio" require_relative "safe_marshal/reader" require_relative "safe_marshal/visitors/to_ruby" module Gem ### # This module is used for safely loading Marshal specs from a gem. The # `safe_load` method defined on this module is specifically designed for # loading Gem specifications. module SafeMarshal PERMITTED_CLASSES = %w[ Date Time Rational Gem::Dependency Gem::NameTuple Gem::Platform Gem::Requirement Gem::Specification Gem::Version Gem::Version::Requirement YAML::Syck::DefaultKey YAML::PrivateType ].freeze private_constant :PERMITTED_CLASSES PERMITTED_SYMBOLS = %w[ development runtime name number platform dependencies ].freeze private_constant :PERMITTED_SYMBOLS PERMITTED_IVARS = { "String" => %w[E encoding @taguri @debug_created_info], "Time" => %w[ offset zone nano_num nano_den submicro @_zone @marshal_with_utc_coercion ], "Gem::Dependency" => %w[ @name @requirement @prerelease @version_requirement @version_requirements @type @force_ruby_platform ], "Gem::NameTuple" => %w[@name @version @platform], "Gem::Platform" => %w[@os @cpu @version], "Psych::PrivateType" => %w[@value @type_id], }.freeze private_constant :PERMITTED_IVARS def self.safe_load(input) load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, permitted_ivars: PERMITTED_IVARS) end def self.load(input, permitted_classes: [::Symbol], permitted_symbols: [], permitted_ivars: {}) root = Reader.new(StringIO.new(input, "r").binmode).read! Visitors::ToRuby.new( permitted_classes: permitted_classes, permitted_symbols: permitted_symbols, permitted_ivars: permitted_ivars, ).visit(root) end end end PK!{installer_uninstaller_utils.rbnu[# frozen_string_literal: true ## # Helper methods for both Gem::Installer and Gem::Uninstaller module Gem::InstallerUninstallerUtils def regenerate_plugins_for(spec, plugins_dir) plugins = spec.plugins return if plugins.empty? require "pathname" spec.plugins.each do |plugin| plugin_script_path = File.join plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}" File.open plugin_script_path, "wb" do |file| file.puts "require_relative '#{Pathname.new(plugin).relative_path_from(Pathname.new(plugins_dir))}'" end verbose plugin_script_path end end def remove_plugins_for(spec, plugins_dir) FileUtils.rm_f Gem::Util.glob_files_in_dir("#{spec.name}#{Gem.plugin_suffix_pattern}", plugins_dir) end end PK!ߚww exceptions.rbnu[# frozen_string_literal: true require_relative "unknown_command_spell_checker" ## # Base exception class for RubyGems. All exception raised by RubyGems are a # subclass of this one. class Gem::Exception < RuntimeError; end class Gem::CommandLineError < Gem::Exception; end class Gem::UnknownCommandError < Gem::Exception attr_reader :unknown_command def initialize(unknown_command) self.class.attach_correctable @unknown_command = unknown_command super("Unknown command #{unknown_command}") end def self.attach_correctable return if method_defined?(:corrections) if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker) end end end class Gem::DependencyError < Gem::Exception; end class Gem::DependencyRemovalException < Gem::Exception; end ## # Raised by Gem::Resolver when a Gem::Dependency::Conflict reaches the # toplevel. Indicates which dependencies were incompatible through #conflict # and #conflicting_dependencies class Gem::DependencyResolutionError < Gem::DependencyError attr_reader :conflict def initialize(conflict) @conflict = conflict a, b = conflicting_dependencies super "conflicting dependencies #{a} and #{b}\n#{@conflict.explanation}" end def conflicting_dependencies @conflict.conflicting_dependencies end end ## # Raised when attempting to uninstall a gem that isn't in GEM_HOME. class Gem::GemNotInHomeException < Gem::Exception attr_accessor :spec end ### # Raised when removing a gem with the uninstall command fails class Gem::UninstallError < Gem::Exception attr_accessor :spec end class Gem::DocumentError < Gem::Exception; end ## # Potentially raised when a specification is validated. class Gem::EndOfYAMLException < Gem::Exception; end ## # Signals that a file permission error is preventing the user from # operating on the given directory. class Gem::FilePermissionError < Gem::Exception attr_reader :directory def initialize(directory) @directory = directory super "You don't have write permissions for the #{directory} directory." end end ## # Used to raise parsing and loading errors class Gem::FormatException < Gem::Exception attr_accessor :file_path end class Gem::GemNotFoundException < Gem::Exception; end class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException ## # Creates a new SpecificGemNotFoundException for a gem with the given +name+ # and +version+. Any +errors+ encountered when attempting to find the gem # are also stored. def initialize(name, version, errors = nil) super "Could not find a valid gem '#{name}' (#{version}) locally or in a repository" @name = name @version = version @errors = errors end ## # The name of the gem that could not be found. attr_reader :name ## # The version of the gem that could not be found. attr_reader :version ## # Errors encountered attempting to find the gem. attr_reader :errors end Gem.deprecate_constant :SpecificGemNotFoundException ## # Raised by Gem::Resolver when dependencies conflict and create the # inability to find a valid possible spec for a request. class Gem::ImpossibleDependenciesError < Gem::Exception attr_reader :conflicts attr_reader :request def initialize(request, conflicts) @request = request @conflicts = conflicts super build_message end def build_message # :nodoc: requester = @request.requester requester = requester ? requester.spec.full_name : "The user" dependency = @request.dependency message = "#{requester} requires #{dependency} but it conflicted:\n".dup @conflicts.each do |_, conflict| message << conflict.explanation end message end def dependency @request.dependency end end class Gem::InstallError < Gem::Exception; end class Gem::RuntimeRequirementNotMetError < Gem::InstallError attr_accessor :suggestion def message [suggestion, super].compact.join("\n\t") end end ## # Potentially raised when a specification is validated. class Gem::InvalidSpecificationException < Gem::Exception; end class Gem::OperationNotSupportedError < Gem::Exception; end ## # Signals that a remote operation cannot be conducted, probably due to not # being connected (or just not finding host). #-- # TODO: create a method that tests connection to the preferred gems server. # All code dealing with remote operations will want this. Failure in that # method should raise this error. class Gem::RemoteError < Gem::Exception; end class Gem::RemoteInstallationCancelled < Gem::Exception; end class Gem::RemoteInstallationSkipped < Gem::Exception; end ## # Represents an error communicating via HTTP. class Gem::RemoteSourceException < Gem::Exception; end ## # Raised when a gem dependencies file specifies a ruby version that does not # match the current version. class Gem::RubyVersionMismatch < Gem::Exception; end ## # Raised by Gem::Validator when something is not right in a gem. class Gem::VerificationError < Gem::Exception; end ## # Raised by Gem::WebauthnListener when an error occurs during security # device verification. class Gem::WebauthnVerificationError < Gem::Exception def initialize(message) super "Security device verification failed: #{message}" end end ## # Raised to indicate that a system exit should occur with the specified # exit_code class Gem::SystemExitException < SystemExit ## # The exit code for the process alias_method :exit_code, :status ## # Creates a new SystemExitException with the given +exit_code+ def initialize(exit_code) super exit_code, "Exiting RubyGems with exit_code #{exit_code}" end end ## # Raised by Resolver when a dependency requests a gem for which # there is no spec. class Gem::UnsatisfiableDependencyError < Gem::DependencyError ## # The unsatisfiable dependency. This is a # Gem::Resolver::DependencyRequest, not a Gem::Dependency attr_reader :dependency ## # Errors encountered which may have contributed to this exception attr_accessor :errors ## # Creates a new UnsatisfiableDependencyError for the unsatisfiable # Gem::Resolver::DependencyRequest +dep+ def initialize(dep, platform_mismatch = nil) if platform_mismatch && !platform_mismatch.empty? plats = platform_mismatch.map {|x| x.platform.to_s }.sort.uniq super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(", ")}" else if dep.explicit? super "Unable to resolve dependency: user requested '#{dep}'" else super "Unable to resolve dependency: '#{dep.request_context}' requires '#{dep}'" end end @dependency = dep @errors = [] end ## # The name of the unresolved dependency def name @dependency.name end ## # The Requirement of the unresolved dependency (not Version). def version @dependency.requirement end end PK!/aslsl installer.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "installer_uninstaller_utils" require_relative "exceptions" require_relative "package" require_relative "ext" require_relative "user_interaction" ## # The installer installs the files contained in the .gem into the Gem.home. # # Gem::Installer does the work of putting files in all the right places on the # filesystem including unpacking the gem into its gem dir, installing the # gemspec in the specifications dir, storing the cached gem in the cache dir, # and installing either wrappers or symlinks for executables. # # The installer invokes pre and post install hooks. Hooks can be added either # through a rubygems_plugin.rb file in an installed gem or via a # rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb # file. See Gem.pre_install and Gem.post_install for details. class Gem::Installer ## # Paths where env(1) might live. Some systems are broken and have it in # /bin ENV_PATHS = %w[/usr/bin/env /bin/env].freeze ## # Deprecated in favor of Gem::Ext::BuildError ExtensionBuildError = Gem::Ext::BuildError # :nodoc: include Gem::UserInteraction include Gem::InstallerUninstallerUtils ## # The directory a gem's executables will be installed into attr_reader :bin_dir attr_reader :build_root # :nodoc: ## # The gem repository the gem will be installed into attr_reader :gem_home ## # The options passed when the Gem::Installer was instantiated. attr_reader :options ## # The gem package instance. attr_reader :package class << self ## # Overrides the executable format. # # This is a sprintf format with a "%s" which will be replaced with the # executable name. It is based off the ruby executable name's difference # from "ruby". attr_writer :exec_format # Defaults to use Ruby's program prefix and suffix. def exec_format @exec_format ||= Gem.default_exec_format end end ## # Construct an installer object for the gem file located at +path+ def self.at(path, options = {}) security_policy = options[:security_policy] package = Gem::Package.new path, security_policy new package, options end class FakePackage attr_accessor :spec attr_accessor :dir_mode attr_accessor :prog_mode attr_accessor :data_mode def initialize(spec) @spec = spec end def extract_files(destination_dir, pattern = "*") FileUtils.mkdir_p destination_dir spec.files.each do |file| file = File.join destination_dir, file next if File.exist? file FileUtils.mkdir_p File.dirname(file) File.open file, "w" do |fp| fp.puts "# #{file}" end end end def copy_to(path) end end ## # Construct an installer object for an ephemeral gem (one where we don't # actually have a .gem file, just a spec) def self.for_spec(spec, options = {}) # FIXME: we should have a real Package class for this new FakePackage.new(spec), options end ## # Constructs an Installer instance that will install the gem at +package+ which # can either be a path or an instance of Gem::Package. +options+ is a Hash # with the following keys: # # :bin_dir:: Where to put a bin wrapper if needed. # :development:: Whether or not development dependencies should be installed. # :env_shebang:: Use /usr/bin/env in bin wrappers. # :force:: Overrides all version checks and security policy checks, except # for a signed-gems-only policy. # :format_executable:: Format the executable the same as the Ruby executable. # If your Ruby is ruby18, foo_exec will be installed as # foo_exec18. # :ignore_dependencies:: Don't raise if a dependency is missing. # :install_dir:: The directory to install the gem into. # :security_policy:: Use the specified security policy. See Gem::Security # :user_install:: Indicate that the gem should be unpacked into the users # personal gem directory. # :only_install_dir:: Only validate dependencies against what is in the # install_dir # :wrappers:: Install wrappers if true, symlinks if false. # :build_args:: An Array of arguments to pass to the extension builder # process. If not set, then Gem::Command.build_args is used # :post_install_message:: Print gem post install message if true def initialize(package, options = {}) require "fileutils" @options = options @package = package process_options @package.dir_mode = options[:dir_mode] @package.prog_mode = options[:prog_mode] @package.data_mode = options[:data_mode] end ## # Checks if +filename+ exists in +@bin_dir+. # # If +@force+ is set +filename+ is overwritten. # # If +filename+ exists and it is a RubyGems wrapper for a different gem, then # the user is consulted. # # If +filename+ exists and +@bin_dir+ is Gem.default_bindir (/usr/local) the # user is consulted. # # Otherwise +filename+ is overwritten. def check_executable_overwrite(filename) # :nodoc: return if @force generated_bin = File.join @bin_dir, formatted_program_filename(filename) return unless File.exist? generated_bin ruby_executable = false existing = nil File.open generated_bin, "rb" do |io| line = io.gets shebang = /^#!.*ruby/o # TruffleRuby uses a bash prelude in default launchers if load_relative_enabled? || RUBY_ENGINE == "truffleruby" until line.nil? || shebang.match?(line) do line = io.gets end end next unless line&.match?(shebang) io.gets # blankline # TODO: detect a specially formatted comment instead of trying # to find a string inside Ruby code. next unless io.gets&.include?("This file was generated by RubyGems") ruby_executable = true existing = io.read.slice(/ ^\s*( Gem\.activate_and_load_bin_path\( | load \s Gem\.activate_bin_path\( ) (['"])(.*?)(\2), /x, 3) end return if spec.name == existing # somebody has written to RubyGems' directory, overwrite, too bad return if Gem.default_bindir != @bin_dir && !ruby_executable question = "#{spec.name}'s executable \"#{filename}\" conflicts with ".dup if ruby_executable question << (existing || "an unknown executable") return if ask_yes_no "#{question}\nOverwrite the executable?", false conflict = "installed executable from #{existing}" else question << generated_bin return if ask_yes_no "#{question}\nOverwrite the executable?", false conflict = generated_bin end raise Gem::InstallError, "\"#{filename}\" from #{spec.name} conflicts with #{conflict}" end ## # Lazy accessor for the spec's gem directory. def gem_dir @gem_dir ||= File.join(gem_home, "gems", spec.full_name) end ## # Lazy accessor for the installer's spec. def spec @package.spec end ## # Installs the gem and returns a loaded Gem::Specification for the installed # gem. # # The gem will be installed with the following structure: # # @gem_home/ # cache/.gem #=> a cached copy of the installed gem # gems//... #=> extracted files # specifications/.gemspec #=> the Gem::Specification def install pre_install_checks run_pre_install_hooks # Set loaded_from to ensure extension_dir is correct spec.loaded_from = spec_file # Completely remove any previous gem files FileUtils.rm_rf gem_dir FileUtils.rm_rf spec.extension_dir dir_mode = options[:dir_mode] FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 extract_files build_extensions write_build_info_file run_post_build_hooks generate_bin generate_plugins write_spec write_cache_file File.chmod(dir_mode, gem_dir) if dir_mode say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? Gem::Specification.add_spec(spec) unless @install_dir load_plugin run_post_install_hooks spec rescue Errno::EACCES => e # Permission denied - /path/to/foo raise Gem::FilePermissionError, e.message.split(" - ").last end def run_pre_install_hooks # :nodoc: Gem.pre_install_hooks.each do |hook| next unless hook.call(self) == false location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/ message = "pre-install hook#{location} failed for #{spec.full_name}" raise Gem::InstallError, message end end def run_post_build_hooks # :nodoc: Gem.post_build_hooks.each do |hook| next unless hook.call(self) == false FileUtils.rm_rf gem_dir location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/ message = "post-build hook#{location} failed for #{spec.full_name}" raise Gem::InstallError, message end end def run_post_install_hooks # :nodoc: Gem.post_install_hooks.each do |hook| hook.call self end end ## # # Return an Array of Specifications contained within the gem_home # we'll be installing into. def installed_specs @installed_specs ||= begin specs = [] Gem::Util.glob_files_in_dir("*.gemspec", File.join(gem_home, "specifications")).each do |path| spec = Gem::Specification.load path specs << spec if spec end specs end end ## # Ensure that the dependency is satisfied by the current installation of # gem. If it is not an exception is raised. # # spec :: Gem::Specification # dependency :: Gem::Dependency def ensure_dependency(spec, dependency) unless installation_satisfies_dependency? dependency raise Gem::InstallError, "#{spec.name} requires #{dependency}" end true end ## # True if the gems in the system satisfy +dependency+. def installation_satisfies_dependency?(dependency) return true if @options[:development] && dependency.type == :development return true if installed_specs.detect {|s| dependency.matches_spec? s } return false if @only_install_dir !dependency.matching_specs.empty? end ## # The location of the spec file that is installed. # def spec_file File.join gem_home, "specifications", "#{spec.full_name}.gemspec" end def default_spec_dir dir = File.join(gem_home, "specifications", "default") FileUtils.mkdir_p dir dir end ## # The location of the default spec file for default gems. # def default_spec_file File.join default_spec_dir, "#{spec.full_name}.gemspec" end ## # Writes the .gemspec specification (in Ruby) to the gem home's # specifications directory. def write_spec spec.installed_by_version = Gem.rubygems_version Gem.write_binary(spec_file, spec.to_ruby_for_cache) end ## # Writes the full .gemspec specification (in Ruby) to the gem home's # specifications/default directory. # # In contrast to #write_spec, this keeps file lists, so the `gem contents` # command works. def write_default_spec Gem.write_binary(default_spec_file, spec.to_ruby) end ## # Creates windows .bat files for easy running of commands def generate_windows_script(filename, bindir) if Gem.win_platform? script_name = formatted_program_filename(filename) + ".bat" script_path = File.join bindir, File.basename(script_name) File.open script_path, "w" do |file| file.puts windows_stub_script(bindir, filename) end verbose script_path end end def generate_bin # :nodoc: executables = spec.executables return if executables.nil? || executables.empty? if @gem_home == Gem.user_dir # If we get here, then one of the following likely happened: # - `--user-install` was specified # - `Gem::PathSupport#home` fell back to `Gem.user_dir` # - GEM_HOME was manually set to `Gem.user_dir` check_that_user_bin_dir_is_in_path(executables) end ensure_writable_dir @bin_dir executables.each do |filename| bin_path = File.join gem_dir, spec.bindir, filename next unless File.exist? bin_path mode = File.stat(bin_path).mode dir_mode = options[:prog_mode] || (mode | 0o111) unless dir_mode == mode File.chmod dir_mode, bin_path end check_executable_overwrite filename if @wrappers generate_bin_script filename, @bin_dir else generate_bin_symlink filename, @bin_dir end end end def generate_plugins # :nodoc: latest = Gem::Specification.latest_spec_for(spec.name) return if latest && latest.version > spec.version ensure_writable_dir @plugins_dir if spec.plugins.empty? remove_plugins_for(spec, @plugins_dir) else regenerate_plugins_for(spec, @plugins_dir) end rescue ArgumentError => e raise e, "#{latest.name} #{latest.version} #{spec.name} #{spec.version}: #{e.message}" end ## # Creates the scripts to run the applications in the gem. #-- # The Windows script is generated in addition to the regular one due to a # bug or misfeature in the Windows shell's pipe. See # https://blade.ruby-lang.org/ruby-talk/193379 def generate_bin_script(filename, bindir) bin_script_path = File.join bindir, formatted_program_filename(filename) Gem.open_file_with_lock(bin_script_path) do require "fileutils" FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers File.open(bin_script_path, "wb", 0o755) do |file| file.write app_script_text(filename) file.chmod(options[:prog_mode] || 0o755) end end verbose bin_script_path generate_windows_script filename, bindir end ## # Creates the symlinks to run the applications in the gem. Moves # the symlink if the gem being installed has a newer version. def generate_bin_symlink(filename, bindir) src = File.join gem_dir, spec.bindir, filename dst = File.join bindir, formatted_program_filename(filename) if File.exist? dst if File.symlink? dst link = File.readlink(dst).split File::SEPARATOR cur_version = Gem::Version.create(link[-3].sub(/^.*-/, "")) return if spec.version < cur_version end File.unlink dst end FileUtils.symlink src, dst, verbose: Gem.configuration.really_verbose rescue NotImplementedError, SystemCallError alert_warning "Unable to use symlinks, installing wrapper" generate_bin_script filename, bindir end ## # Generates a #! line for +bin_file_name+'s wrapper copying arguments if # necessary. # # If the :custom_shebang config is set, then it is used as a template # for how to create the shebang used for to run a gem's executables. # # The template supports 4 expansions: # # $env the path to the unix env utility # $ruby the path to the currently running ruby interpreter # $exec the path to the gem's executable # $name the name of the gem the executable is for # def shebang(bin_file_name) path = File.join gem_dir, spec.bindir, bin_file_name first_line = File.open(path, "rb", &:gets) || "" if first_line.start_with?("#!") # Preserve extra words on shebang line, like "-w". Thanks RPA. shebang = first_line.sub(/\A\#!.*?ruby\S*((\s+\S+)+)/, "#!#{Gem.ruby}") opts = $1 shebang.strip! # Avoid nasty ^M issues. end if which = Gem.configuration[:custom_shebang] # replace bin_file_name with "ruby" to avoid endless loops which = which.gsub(/ #{bin_file_name}$/," #{ruby_install_name}") which = which.gsub(/\$(\w+)/) do case $1 when "env" @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } when "ruby" "#{Gem.ruby}#{opts}" when "exec" bin_file_name when "name" spec.name end end "#!#{which}" elsif @env_shebang # Create a plain shebang line. @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } "#!#{@env_path} #{ruby_install_name}" else "#{bash_prolog_script}#!#{Gem.ruby}#{opts}" end end ## # Ensures the Gem::Specification written out for this gem is loadable upon # installation. def ensure_loadable_spec ruby = spec.to_ruby_for_cache begin eval ruby rescue StandardError, SyntaxError => e raise Gem::InstallError, "The specification for #{spec.full_name} is corrupt (#{e.class})" end end def ensure_dependencies_met # :nodoc: deps = spec.runtime_dependencies deps |= spec.development_dependencies if @development deps.each do |dep_gem| ensure_dependency spec, dep_gem end end def process_options # :nodoc: @options = { bin_dir: nil, env_shebang: false, force: false, only_install_dir: false, post_install_message: true, }.merge options @env_shebang = options[:env_shebang] @force = options[:force] @install_dir = options[:install_dir] @user_install = options[:user_install] @ignore_dependencies = options[:ignore_dependencies] @format_executable = options[:format_executable] @wrappers = options[:wrappers] @only_install_dir = options[:only_install_dir] @bin_dir = options[:bin_dir] @development = options[:development] @build_root = options[:build_root] @build_args = options[:build_args] @build_jobs = options[:build_jobs] @gem_home = @install_dir || user_install_dir || Gem.dir # If the user has asked for the gem to be installed in a directory that is # the system gem directory, then use the system bin directory, else create # (or use) a new bin dir under the gem_home. @bin_dir ||= Gem.bindir(@gem_home) @plugins_dir = Gem.plugindir(@gem_home) unless @build_root.nil? @bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, "")) @gem_home = File.join(@build_root, @gem_home.gsub(/^[a-zA-Z]:/, "")) @plugins_dir = File.join(@build_root, @plugins_dir.gsub(/^[a-zA-Z]:/, "")) alert_warning "You build with buildroot.\n Build root: #{@build_root}\n Bin dir: #{@bin_dir}\n Gem home: #{@gem_home}\n Plugins dir: #{@plugins_dir}" end end def check_that_user_bin_dir_is_in_path(executables) # :nodoc: user_bin_dir = @bin_dir || Gem.bindir(gem_home) user_bin_dir = user_bin_dir.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR path = ENV["PATH"] path = path.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR if Gem.win_platform? path = path.downcase user_bin_dir = user_bin_dir.downcase end path = path.split(File::PATH_SEPARATOR) unless path.include? user_bin_dir unless !Gem.win_platform? && (path.include? user_bin_dir.sub(ENV["HOME"], "~")) alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables (#{executables.join(", ")}) will not run." end end end def verify_gem_home # :nodoc: FileUtils.mkdir_p gem_home, mode: options[:dir_mode] && 0o755 end def verify_spec unless Gem::Specification::VALID_NAME_PATTERN.match?(spec.name) raise Gem::InstallError, "#{spec} has an invalid name" end if spec.raw_require_paths.any? {|path| path =~ /\R/ } raise Gem::InstallError, "#{spec} has an invalid require_paths" end if spec.extensions.any? {|ext| ext =~ /\R/ } raise Gem::InstallError, "#{spec} has an invalid extensions" end if /\R/.match?(spec.platform.to_s) raise Gem::InstallError, "#{spec.platform} is an invalid platform" end unless /\A\d+\z/.match?(spec.specification_version.to_s) raise Gem::InstallError, "#{spec} has an invalid specification_version" end if spec.dependencies.any? {|dep| dep.type != :runtime && dep.type != :development } raise Gem::InstallError, "#{spec} has an invalid dependencies" end if spec.dependencies.any? {|dep| dep.name =~ /(?:\R|[<>])/ } raise Gem::InstallError, "#{spec} has an invalid dependencies" end end ## # Return the text for an application file. def app_script_text(bin_file_name) # NOTE: that the `load` lines cannot be indented, as old RG versions match # against the beginning of the line <<~TEXT #{shebang bin_file_name} # # This file was generated by RubyGems. # # The application '#{spec.name}' is installed as part of a gem, and # this file is here to facilitate running it. # require 'rubygems' #{gemdeps_load(spec.name)} version = "#{Gem::Requirement.default_prerelease}" str = ARGV.first if str str = str.b[/\\A_(.*)_\\z/, 1] if str and Gem::Version.correct?(str) #{explicit_version_requirement(spec.name)} ARGV.shift end end if Gem.respond_to?(:activate_and_load_bin_path) Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version) else load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) end TEXT end def gemdeps_load(name) return "" if name == "bundler" <<~TEXT Gem.use_gemdeps TEXT end def explicit_version_requirement(name) code = "version = str" return code unless name == "bundler" code += <<~TEXT ENV['BUNDLER_VERSION'] = str TEXT end ## # return the stub script text used to launch the true Ruby script def windows_stub_script(bindir, bin_file_name) rb_topdir = RbConfig::TOPDIR || File.dirname(rb_config["bindir"]) # get ruby executable file name from RbConfig ruby_exe = "#{rb_config["RUBY_INSTALL_NAME"]}#{rb_config["EXEEXT"]}" ruby_exe = "ruby.exe" if ruby_exe.empty? if File.exist?(File.join(bindir, ruby_exe)) # stub & ruby.exe within same folder. Portable <<~TEXT @ECHO OFF @"%~dp0#{ruby_exe}" "%~dpn0" %* TEXT elsif bindir.downcase.start_with? rb_topdir.downcase # stub within ruby folder, but not standard bin. Portable require "pathname" from = Pathname.new bindir to = Pathname.new "#{rb_topdir}/bin" rel = to.relative_path_from from <<~TEXT @ECHO OFF @"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* TEXT else # outside ruby folder, maybe -user-install or bundler. Portable, but ruby # is dependent on PATH <<~TEXT @ECHO OFF @#{ruby_exe} "%~dpn0" %* TEXT end end ## # Builds extensions. Valid types of extensions are extconf.rb files, # configure scripts and rakefiles or mkrf_conf files. def build_extensions builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs builder.build_extensions end ## # Reads the file index and extracts each file into the gem directory. # # Ensures that files can't be installed outside the gem directory. def extract_files @package.extract_files gem_dir end ## # Extracts only the bin/ files from the gem into the gem directory. # This is used by default gems to allow a gem-aware stub to function # without the full gem installed. def extract_bin @package.extract_files gem_dir, "#{spec.bindir}/*" end ## # Prefix and suffix the program filename the same as ruby. def formatted_program_filename(filename) if @format_executable self.class.exec_format % File.basename(filename) else filename end end ## # # Return the target directory where the gem is to be installed. This # directory is not guaranteed to be populated. # def dir gem_dir.to_s end ## # Filename of the gem being installed. def gem @package.gem.path end ## # Performs various checks before installing the gem such as the install # repository is writable and its directories exist, required Ruby and # rubygems versions are met and that dependencies are installed. # # Version and dependency checks are skipped if this install is forced. # # The dependent check will be skipped if the install is ignoring dependencies. def pre_install_checks verify_gem_home # The name and require_paths must be verified first, since it could contain # ruby code that would be eval'ed in #ensure_loadable_spec verify_spec ensure_loadable_spec Gem.ensure_gem_subdirectories gem_home return true if @force ensure_dependencies_met unless @ignore_dependencies true end ## # Writes the file containing the arguments for building this gem's # extensions. def write_build_info_file return if build_args.empty? build_info_dir = File.join gem_home, "build_info" dir_mode = options[:dir_mode] FileUtils.mkdir_p build_info_dir, mode: dir_mode && 0o755 build_info_file = File.join build_info_dir, "#{spec.full_name}.info" File.open build_info_file, "w" do |io| build_args.each do |arg| io.puts arg end end File.chmod(dir_mode, build_info_dir) if dir_mode end ## # Writes the .gem file to the cache directory def write_cache_file cache_file = File.join gem_home, "cache", spec.file_name @package.copy_to cache_file end def ensure_writable_dir(dir) # :nodoc: require "fileutils" FileUtils.mkdir_p dir, mode: options[:dir_mode] && 0o755 raise Gem::FilePermissionError.new(dir) unless File.writable? dir end private def user_install_dir # never install to user home in --build-root mode return unless @build_root.nil? # Please note that @user_install might have three states: # * `true`: `--user-install` # * `false`: `--no-user-install` and # * `nil`: option was not specified if @user_install || (@user_install.nil? && Gem.default_user_install) Gem.user_dir end end def build_args @build_args ||= begin require_relative "command" Gem::Command.build_args end end def build_jobs @build_jobs ||= begin require "etc" Etc.nprocessors + 1 rescue LoadError 1 end end def rb_config Gem.target_rbconfig end def ruby_install_name rb_config["ruby_install_name"] end def load_relative_enabled? rb_config["LIBRUBY_RELATIVE"] == "yes" end def bash_prolog_script if load_relative_enabled? <<~EOS #!/bin/sh # -*- ruby -*- _=_\\ =begin bindir="${0%/*}" ruby="$bindir/#{ruby_install_name}" if [ ! -f "$ruby" ]; then ruby="#{ruby_install_name}" fi exec "$ruby" "-x" "$0" "$@" =end EOS else "" end end def load_plugin specs = Gem::Specification.find_all_by_name(spec.name) # If old version already exists, this plugin isn't loaded # immediately. It's for avoiding a case that multiple versions # are loaded at the same time. return unless specs.size == 1 plugin_files = spec.plugins.map do |plugin| File.join(@plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}") end Gem.load_plugin_files(plugin_files) end end PK!O33install_default_message.rbnu[require 'rubygems' require 'rubygems/user_interaction' ## # A post-install hook that displays "Successfully installed # some_gem-1.0 as a default gem" Gem.post_install do |installer| ui = Gem::DefaultUserInteraction.ui ui.say "Successfully installed #{installer.spec.full_name} as a default gem" end PK!v4kVV security.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "exceptions" require_relative "openssl" ## # = Signing gems # # The Gem::Security implements cryptographic signatures for gems. The section # below is a step-by-step guide to using signed gems and generating your own. # # == Walkthrough # # === Building your certificate # # In order to start signing your gems, you'll need to build a private key and # a self-signed certificate. Here's how: # # # build a private key and certificate for yourself: # $ gem cert --build you@example.com # # This could take anywhere from a few seconds to a minute or two, depending on # the speed of your computer (public key algorithms aren't exactly the # speediest crypto algorithms in the world). When it's finished, you'll see # the files "gem-private_key.pem" and "gem-public_cert.pem" in the current # directory. # # First things first: Move both files to ~/.gem if you don't already have a # key and certificate in that directory. Ensure the file permissions make the # key unreadable by others (by default the file is saved securely). # # Keep your private key hidden; if it's compromised, someone can sign packages # as you (note: PKI has ways of mitigating the risk of stolen keys; more on # that later). # # === Signing Gems # # In RubyGems 2 and newer there is no extra work to sign a gem. RubyGems will # automatically find your key and certificate in your home directory and use # them to sign newly packaged gems. # # If your certificate is not self-signed (signed by a third party) RubyGems # will attempt to load the certificate chain from the trusted certificates. # Use gem cert --add signing_cert.pem to add your signers as # trusted certificates. See below for further information on certificate # chains. # # If you build your gem it will automatically be signed. If you peek inside # your gem file, you'll see a couple of new files have been added: # # $ tar tf your-gem-1.0.gem # metadata.gz # metadata.gz.sig # metadata signature # data.tar.gz # data.tar.gz.sig # data signature # checksums.yaml.gz # checksums.yaml.gz.sig # checksums signature # # === Manually signing gems # # If you wish to store your key in a separate secure location you'll need to # set your gems up for signing by hand. To do this, set the # signing_key and cert_chain in the gemspec before # packaging your gem: # # s.signing_key = '/secure/path/to/gem-private_key.pem' # s.cert_chain = %w[/secure/path/to/gem-public_cert.pem] # # When you package your gem with these options set RubyGems will automatically # load your key and certificate from the secure paths. # # === Signed gems and security policies # # Now let's verify the signature. Go ahead and install the gem, but add the # following options: -P HighSecurity, like this: # # # install the gem with using the security policy "HighSecurity" # $ sudo gem install your.gem -P HighSecurity # # The -P option sets your security policy -- we'll talk about # that in just a minute. Eh, what's this? # # $ gem install -P HighSecurity your-gem-1.0.gem # ERROR: While executing gem ... (Gem::Security::Exception) # root cert /CN=you/DC=example is not trusted # # The culprit here is the security policy. RubyGems has several different # security policies. Let's take a short break and go over the security # policies. Here's a list of the available security policies, and a brief # description of each one: # # * NoSecurity - Well, no security at all. Signed packages are treated like # unsigned packages. # * LowSecurity - Pretty much no security. If a package is signed then # RubyGems will make sure the signature matches the signing # certificate, and that the signing certificate hasn't expired, but # that's it. A malicious user could easily circumvent this kind of # security. # * MediumSecurity - Better than LowSecurity and NoSecurity, but still # fallible. Package contents are verified against the signing # certificate, and the signing certificate is checked for validity, # and checked against the rest of the certificate chain (if you don't # know what a certificate chain is, stay tuned, we'll get to that). # The biggest improvement over LowSecurity is that MediumSecurity # won't install packages that are signed by untrusted sources. # Unfortunately, MediumSecurity still isn't totally secure -- a # malicious user can still unpack the gem, strip the signatures, and # distribute the gem unsigned. # * HighSecurity - Here's the bugger that got us into this mess. # The HighSecurity policy is identical to the MediumSecurity policy, # except that it does not allow unsigned gems. A malicious user # doesn't have a whole lot of options here; they can't modify the # package contents without invalidating the signature, and they can't # modify or remove signature or the signing certificate chain, or # RubyGems will simply refuse to install the package. Oh well, maybe # they'll have better luck causing problems for CPAN users instead :). # # The reason RubyGems refused to install your shiny new signed gem was because # it was from an untrusted source. Well, your code is infallible (naturally), # so you need to add yourself as a trusted source: # # # add trusted certificate # gem cert --add ~/.gem/gem-public_cert.pem # # You've now added your public certificate as a trusted source. Now you can # install packages signed by your private key without any hassle. Let's try # the install command above again: # # # install the gem with using the HighSecurity policy (and this time # # without any shenanigans) # $ gem install -P HighSecurity your-gem-1.0.gem # Successfully installed your-gem-1.0 # 1 gem installed # # This time RubyGems will accept your signed package and begin installing. # # While you're waiting for RubyGems to work it's magic, have a look at some of # the other security commands by running gem help cert: # # Options: # -a, --add CERT Add a trusted certificate. # -l, --list [FILTER] List trusted certificates where the # subject contains FILTER # -r, --remove FILTER Remove trusted certificates where the # subject contains FILTER # -b, --build EMAIL_ADDR Build private key and self-signed # certificate for EMAIL_ADDR # -C, --certificate CERT Signing certificate for --sign # -K, --private-key KEY Key for --sign or --build # -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA. # -s, --sign CERT Signs CERT with the key from -K # and the certificate from -C # -d, --days NUMBER_OF_DAYS Days before the certificate expires # -R, --re-sign Re-signs the certificate from -C with the key from -K # # We've already covered the --build option, and the # --add, --list, and --remove commands # seem fairly straightforward; they allow you to add, list, and remove the # certificates in your trusted certificate list. But what's with this # --sign option? # # === Certificate chains # # To answer that question, let's take a look at "certificate chains", a # concept I mentioned earlier. There are a couple of problems with # self-signed certificates: first of all, self-signed certificates don't offer # a whole lot of security. Sure, the certificate says Yukihiro Matsumoto, but # how do I know it was actually generated and signed by matz himself unless he # gave me the certificate in person? # # The second problem is scalability. Sure, if there are 50 gem authors, then # I have 50 trusted certificates, no problem. What if there are 500 gem # authors? 1000? Having to constantly add new trusted certificates is a # pain, and it actually makes the trust system less secure by encouraging # RubyGems users to blindly trust new certificates. # # Here's where certificate chains come in. A certificate chain establishes an # arbitrarily long chain of trust between an issuing certificate and a child # certificate. So instead of trusting certificates on a per-developer basis, # we use the PKI concept of certificate chains to build a logical hierarchy of # trust. Here's a hypothetical example of a trust hierarchy based (roughly) # on geography: # # -------------------------- # | rubygems@rubygems.org | # -------------------------- # | # ----------------------------------- # | | # ---------------------------- ----------------------------- # | seattlerb@seattlerb.org | | dcrubyists@richkilmer.com | # ---------------------------- ----------------------------- # | | | | # --------------- ---------------- ----------- -------------- # | drbrain | | zenspider | | pabs@dc | | tomcope@dc | # --------------- ---------------- ----------- -------------- # # # Now, rather than having 4 trusted certificates (one for drbrain, zenspider, # pabs@dc, and tomecope@dc), a user could actually get by with one # certificate, the "rubygems@rubygems.org" certificate. # # Here's how it works: # # I install "rdoc-3.12.gem", a package signed by "drbrain". I've never heard # of "drbrain", but his certificate has a valid signature from the # "seattle.rb@seattlerb.org" certificate, which in turn has a valid signature # from the "rubygems@rubygems.org" certificate. Voila! At this point, it's # much more reasonable for me to trust a package signed by "drbrain", because # I can establish a chain to "rubygems@rubygems.org", which I do trust. # # === Signing certificates # # The --sign option allows all this to happen. A developer # creates their build certificate with the --build option, then # has their certificate signed by taking it with them to their next regional # Ruby meetup (in our hypothetical example), and it's signed there by the # person holding the regional RubyGems signing certificate, which is signed at # the next RubyConf by the holder of the top-level RubyGems certificate. At # each point the issuer runs the same command: # # # sign a certificate with the specified key and certificate # # (note that this modifies client_cert.pem!) # $ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem # --sign client_cert.pem # # Then the holder of issued certificate (in this case, your buddy "drbrain"), # can start using this signed certificate to sign RubyGems. By the way, in # order to let everyone else know about his new fancy signed certificate, # "drbrain" would save his newly signed certificate as # ~/.gem/gem-public_cert.pem # # Obviously this RubyGems trust infrastructure doesn't exist yet. Also, in # the "real world", issuers actually generate the child certificate from a # certificate request, rather than sign an existing certificate. And our # hypothetical infrastructure is missing a certificate revocation system. # These are that can be fixed in the future... # # At this point you should know how to do all of these new and interesting # things: # # * build a gem signing key and certificate # * adjust your security policy # * modify your trusted certificate list # * sign a certificate # # == Manually verifying signatures # # In case you don't trust RubyGems you can verify gem signatures manually: # # 1. Fetch and unpack the gem # # gem fetch some_signed_gem # tar -xf some_signed_gem-1.0.gem # # 2. Grab the public key from the gemspec # # gem spec some_signed_gem-1.0.gem cert_chain | \ # ruby -rpsych -e 'puts Psych.load($stdin)' > public_key.crt # # 3. Generate a SHA1 hash of the data.tar.gz # # openssl dgst -sha1 < data.tar.gz > my.hash # # 4. Verify the signature # # openssl rsautl -verify -inkey public_key.crt -certin \ # -in data.tar.gz.sig > verified.hash # # 5. Compare your hash to the verified hash # # diff -s verified.hash my.hash # # 6. Repeat 5 and 6 with metadata.gz # # == OpenSSL Reference # # The .pem files generated by --build and --sign are PEM files. Here's a # couple of useful OpenSSL commands for manipulating them: # # # convert a PEM format X509 certificate into DER format: # # (note: Windows .cer files are X509 certificates in DER format) # $ openssl x509 -in input.pem -outform der -out output.der # # # print out the certificate in a human-readable format: # $ openssl x509 -in input.pem -noout -text # # And you can do the same thing with the private key file as well: # # # convert a PEM format RSA key into DER format: # $ openssl rsa -in input_key.pem -outform der -out output_key.der # # # print out the key in a human readable format: # $ openssl rsa -in input_key.pem -noout -text # # == Bugs/TODO # # * There's no way to define a system-wide trust list. # * custom security policies (from a YAML file, etc) # * Simple method to generate a signed certificate request # * Support for OCSP, SCVP, CRLs, or some other form of cert status check # (list is in order of preference) # * Support for encrypted private keys # * Some sort of semi-formal trust hierarchy (see long-winded explanation # above) # * Path discovery (for gem certificate chains that don't have a self-signed # root) -- by the way, since we don't have this, THE ROOT OF THE CERTIFICATE # CHAIN MUST BE SELF SIGNED if Policy#verify_root is true (and it is for the # MediumSecurity and HighSecurity policies) # * Better explanation of X509 naming (ie, we don't have to use email # addresses) # * Honor AIA field (see note about OCSP above) # * Honor extension restrictions # * Might be better to store the certificate chain as a PKCS#7 or PKCS#12 # file, instead of an array embedded in the metadata. # # == Original author # # Paul Duncan # https://pablotron.org/ module Gem::Security ## # Gem::Security default exception type class Exception < Gem::Exception; end ## # Used internally to select the signing digest from all computed digests DIGEST_NAME = "SHA256" # :nodoc: ## # Length of keys created by RSA and DSA keys RSA_DSA_KEY_LENGTH = 3072 ## # Default algorithm to use when building a key pair DEFAULT_KEY_ALGORITHM = "RSA" ## # Named curve used for Elliptic Curve EC_NAME = "secp384r1" ## # Cipher used to encrypt the key pair used to sign gems. # Must be in the list returned by OpenSSL::Cipher.ciphers KEY_CIPHER = OpenSSL::Cipher.new("AES-256-CBC") if defined?(OpenSSL::Cipher) ## # One day in seconds ONE_DAY = 86_400 ## # One year in seconds ONE_YEAR = ONE_DAY * 365 ## # The default set of extensions are: # # * The certificate is not a certificate authority # * The key for the certificate may be used for key and data encipherment # and digital signatures # * The certificate contains a subject key identifier EXTENSIONS = { "basicConstraints" => "CA:FALSE", "keyUsage" => "keyEncipherment,dataEncipherment,digitalSignature", "subjectKeyIdentifier" => "hash", }.freeze def self.alt_name_or_x509_entry(certificate, x509_entry) alt_name = certificate.extensions.find do |extension| extension.oid == "#{x509_entry}AltName" end return alt_name.value if alt_name certificate.send x509_entry end ## # Creates an unsigned certificate for +subject+ and +key+. The lifetime of # the key is from the current time to +age+ which defaults to one year. # # The +extensions+ restrict the key to the indicated uses. def self.create_cert(subject, key, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1) cert = OpenSSL::X509::Certificate.new cert.public_key = get_public_key(key) cert.version = 2 cert.serial = serial cert.not_before = Time.now cert.not_after = Time.now + age cert.subject = subject ef = OpenSSL::X509::ExtensionFactory.new nil, cert cert.extensions = extensions.map do |ext_name, value| ef.create_extension ext_name, value end cert end ## # Gets the right public key from a PKey instance def self.get_public_key(key) # Ruby 3.0 (Ruby/OpenSSL 2.2) or later return OpenSSL::PKey.read(key.public_to_der) if key.respond_to?(:public_to_der) return key.public_key unless key.is_a?(OpenSSL::PKey::EC) ec_key = OpenSSL::PKey::EC.new(key.group.curve_name) ec_key.public_key = key.public_key ec_key end ## # Creates a self-signed certificate with an issuer and subject from +email+, # a subject alternative name of +email+ and the given +extensions+ for the # +key+. def self.create_cert_email(email, key, age = ONE_YEAR, extensions = EXTENSIONS) subject = email_to_name email extensions = extensions.merge "subjectAltName" => "email:#{email}" create_cert_self_signed subject, key, age, extensions end ## # Creates a self-signed certificate with an issuer and subject of +subject+ # and the given +extensions+ for the +key+. def self.create_cert_self_signed(subject, key, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1) certificate = create_cert subject, key, age, extensions sign certificate, key, certificate, age, extensions, serial end ## # Creates a new digest instance using the specified +algorithm+. The default # is SHA256. def self.create_digest(algorithm = DIGEST_NAME) OpenSSL::Digest.new(algorithm) end ## # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC # are supported. def self.create_key(algorithm) if defined?(OpenSSL::PKey) case algorithm.downcase when "dsa" OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH) when "rsa" OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH) when "ec" OpenSSL::PKey::EC.generate(EC_NAME) else raise Gem::Security::Exception, "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported." end end end ## # Turns +email_address+ into an OpenSSL::X509::Name def self.email_to_name(email_address) email_address = email_address.gsub(/[^\w@.-]+/i, "_") cn, dcs = email_address.split "@" dcs = dcs.split "." OpenSSL::X509::Name.new([ ["CN", cn], *dcs.map {|dc| ["DC", dc] }, ]) end ## # Signs +expired_certificate+ with +private_key+ if the keys match and the # expired certificate was self-signed. #-- # TODO increment serial def self.re_sign(expired_certificate, private_key, age = ONE_YEAR, extensions = EXTENSIONS) raise Gem::Security::Exception, "incorrect signing key for re-signing " + expired_certificate.subject.to_s unless expired_certificate.check_private_key(private_key) unless expired_certificate.subject.to_s == expired_certificate.issuer.to_s subject = alt_name_or_x509_entry expired_certificate, :subject issuer = alt_name_or_x509_entry expired_certificate, :issuer raise Gem::Security::Exception, "#{subject} is not self-signed, contact #{issuer} " \ "to obtain a valid certificate" end serial = expired_certificate.serial + 1 create_cert_self_signed(expired_certificate.subject, private_key, age, extensions, serial) end ## # Resets the trust directory for verifying gems. def self.reset @trust_dir = nil end ## # Sign the public key from +certificate+ with the +signing_key+ and # +signing_cert+, using the Gem::Security::DIGEST_NAME. Uses the # default certificate validity range and extensions. # # Returns the newly signed certificate. def self.sign(certificate, signing_key, signing_cert, age = ONE_YEAR, extensions = EXTENSIONS, serial = 1) signee_subject = certificate.subject signee_key = certificate.public_key alt_name = certificate.extensions.find do |extension| extension.oid == "subjectAltName" end extensions = extensions.merge "subjectAltName" => alt_name.value if alt_name issuer_alt_name = signing_cert.extensions.find do |extension| extension.oid == "subjectAltName" end extensions = extensions.merge "issuerAltName" => issuer_alt_name.value if issuer_alt_name signed = create_cert signee_subject, signee_key, age, extensions, serial signed.issuer = signing_cert.subject signed.sign signing_key, Gem::Security::DIGEST_NAME end ## # Returns a Gem::Security::TrustDir which wraps the directory where trusted # certificates live. def self.trust_dir return @trust_dir if @trust_dir dir = File.join Gem.user_home, ".gem", "trust" @trust_dir ||= Gem::Security::TrustDir.new dir end ## # Enumerates the trusted certificates via Gem::Security::TrustDir. def self.trusted_certificates(&block) trust_dir.each_certificate(&block) end ## # Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given # +permissions+. If passed +cipher+ and +passphrase+ those arguments will be # passed to +to_pem+. def self.write(pemmable, path, permissions = 0o600, passphrase = nil, cipher = KEY_CIPHER) path = File.expand_path path File.open path, "wb", permissions do |io| if passphrase && cipher io.write pemmable.to_pem cipher, passphrase else io.write pemmable.to_pem end end path end reset end if Gem::HAVE_OPENSSL require_relative "security/policy" require_relative "security/policies" require_relative "security/trust_dir" end require_relative "security/signer" PK!stub_specification.rbnu[# frozen_string_literal: true ## # Gem::StubSpecification reads the stub: line from the gemspec. This prevents # us having to eval the entire gemspec in order to find out certain # information. class Gem::StubSpecification < Gem::BasicSpecification # :nodoc: PREFIX = "# stub: " # :nodoc: OPEN_MODE = "r:UTF-8:-" class StubLine # :nodoc: all attr_reader :name, :version, :platform, :require_paths, :extensions, :full_name NO_EXTENSIONS = [].freeze # These are common require paths. REQUIRE_PATHS = { # :nodoc: "lib" => "lib", "test" => "test", "ext" => "ext", }.freeze # These are common require path lists. This hash is used to optimize # and consolidate require_path objects. Most specs just specify "lib" # in their require paths, so lets take advantage of that by pre-allocating # a require path list for that case. REQUIRE_PATH_LIST = { # :nodoc: "lib" => ["lib"].freeze, }.freeze def initialize(data, extensions) parts = data[PREFIX.length..-1].split(" ", 4) @name = -parts[0] @version = if Gem::Version.correct?(parts[1]) Gem::Version.new(parts[1]) else Gem::Version.new(0) end @platform = Gem::Platform.new parts[2] @extensions = extensions @full_name = if platform == Gem::Platform::RUBY "#{name}-#{version}" else "#{name}-#{version}-#{platform}" end path_list = parts.last @require_paths = REQUIRE_PATH_LIST[path_list] || path_list.split("\0").map! do |x| REQUIRE_PATHS[x] || x end end end def self.default_gemspec_stub(filename, base_dir, gems_dir) new filename, base_dir, gems_dir, true end def self.gemspec_stub(filename, base_dir, gems_dir) new filename, base_dir, gems_dir, false end attr_reader :base_dir, :gems_dir def initialize(filename, base_dir, gems_dir, default_gem) super() self.loaded_from = filename @data = nil @name = nil @spec = nil @base_dir = base_dir @gems_dir = gems_dir @default_gem = default_gem end ## # True when this gem has been activated def activated? @activated ||= !loaded_spec.nil? end def default_gem? @default_gem end def build_extensions # :nodoc: return if default_gem? return if extensions.empty? to_spec.build_extensions end ## # If the gemspec contains a stubline, returns a StubLine instance. Otherwise # returns the full Gem::Specification. def data unless @data begin saved_lineno = $. Gem.open_file loaded_from, OPEN_MODE do |file| file.readline # discard encoding line stubline = file.readline if stubline.start_with?(PREFIX) extline = file.readline extensions = if extline.delete_prefix!(PREFIX) extline.chomp! extline.split "\0" else StubLine::NO_EXTENSIONS end stubline.chomp! # readline(chomp: true) allocates 3x as much as .readline.chomp! @data = StubLine.new stubline, extensions end rescue EOFError end ensure $. = saved_lineno end end @data ||= to_spec end private :data def raw_require_paths # :nodoc: data.require_paths end def missing_extensions? return false if RUBY_ENGINE == "jruby" return false if default_gem? return false if extensions.empty? return false if File.exist? gem_build_complete_path to_spec.missing_extensions? end ## # Name of the gem def name data.name end ## # Platform of the gem def platform data.platform end ## # Extensions for this gem def extensions data.extensions end ## # Version of the gem def version data.version end def full_name data.full_name end ## # The full Gem::Specification for this gem, loaded from evalling its gemspec def spec @spec ||= loaded_spec if @data @spec ||= Gem::Specification.load(loaded_from) end alias_method :to_spec, :spec ## # Is this StubSpecification valid? i.e. have we found a stub line, OR does # the filename contain a valid gemspec? def valid? data end ## # Is there a stub line present for this StubSpecification? def stubbed? data.is_a? StubLine end def ==(other) # :nodoc: self.class === other && name == other.name && version == other.version && platform == other.platform end alias_method :eql?, :== # :nodoc: def hash # :nodoc: name.hash ^ version.hash ^ platform.hash end def <=>(other) # :nodoc: sort_obj <=> other.sort_obj end def sort_obj # :nodoc: [name, version, Gem::Platform.sort_priority(platform)] end private def loaded_spec spec = Gem.loaded_specs[name] return unless spec && spec.version == version && spec.default_gem? == default_gem? spec end end PK!!!vendored_net_http.rbnu[# frozen_string_literal: true # Ruby 3.3 and RubyGems 3.5 is already load Gem::Timeout from lib/rubygems/net/http.rb # We should avoid to load it again require_relative "vendor/net-http/lib/net/http" unless defined?(Gem::Net::HTTP) PK!specification.rbnu[# frozen_string_literal: true # #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "basic_specification" require_relative "stub_specification" require_relative "platform" require_relative "specification_record" require_relative "util/list" require "rbconfig" ## # The Specification class contains the information for a gem. Typically # defined in a .gemspec file or a Rakefile, and looks like this: # # Gem::Specification.new do |s| # s.name = 'example' # s.version = '0.1.0' # s.licenses = ['MIT'] # s.summary = "This is an example!" # s.description = "Much longer explanation of the example!" # s.authors = ["Ruby Coder"] # s.email = 'rubycoder@example.com' # s.files = ["lib/example.rb"] # s.homepage = 'https://rubygems.org/gems/example' # s.metadata = { "source_code_uri" => "https://github.com/example/example" } # end # # Starting in RubyGems 2.0, a Specification can hold arbitrary # metadata. See #metadata for restrictions on the format and size of metadata # items you may add to a specification. # # Specifications must be deterministic, as in the example above. For instance, # you cannot define attributes conditionally: # # # INVALID: do not do this. # unless RUBY_ENGINE == "jruby" # s.extensions << "ext/example/extconf.rb" # end # class Gem::Specification < Gem::BasicSpecification # REFACTOR: Consider breaking out this version stuff into a separate # module. There's enough special stuff around it that it may justify # a separate class. ## # The version number of a specification that does not specify one # (i.e. RubyGems 0.7 or earlier). NONEXISTENT_SPECIFICATION_VERSION = -1 ## # The specification version applied to any new Specification instances # created. This should be bumped whenever something in the spec format # changes. # # Specification Version History: # # spec ruby # ver ver yyyy-mm-dd description # -1 <0.8.0 pre-spec-version-history # 1 0.8.0 2004-08-01 Deprecated "test_suite_file" for "test_files" # "test_file=x" is a shortcut for "test_files=[x]" # 2 0.9.5 2007-10-01 Added "required_rubygems_version" # Now forward-compatible with future versions # 3 1.3.2 2009-01-03 Added Fixnum validation to specification_version # 4 1.9.0 2011-06-07 Added metadata #-- # When updating this number, be sure to also update #to_ruby. # # NOTE RubyGems < 1.2 cannot load specification versions > 2. CURRENT_SPECIFICATION_VERSION = 4 # :nodoc: ## # An informal list of changes to the specification. The highest-valued # key should be equal to the CURRENT_SPECIFICATION_VERSION. SPECIFICATION_VERSION_HISTORY = { # :nodoc: -1 => ["(RubyGems versions up to and including 0.7 did not have versioned specifications)"], 1 => [ 'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"', '"test_file=x" is a shortcut for "test_files=[x]"', ], 2 => [ 'Added "required_rubygems_version"', "Now forward-compatible with future versions", ], 3 => [ "Added Fixnum validation to the specification_version", ], 4 => [ "Added sandboxed freeform metadata to the specification version.", ], }.freeze MARSHAL_FIELDS = { # :nodoc: -1 => 16, 1 => 16, 2 => 16, 3 => 17, 4 => 18, }.freeze today = Time.now.utc TODAY = Time.utc(today.year, today.month, today.day) # :nodoc: @load_cache = {} # :nodoc: @load_cache_mutex = Thread::Mutex.new VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc: # :startdoc: ## # List of attribute names: [:name, :version, ...] @@required_attributes = [:rubygems_version, :specification_version, :name, :version, :date, :summary, :require_paths] ## # Map of attribute names to default values. @@default_value = { authors: [], autorequire: nil, bindir: "bin", cert_chain: [], date: nil, dependencies: [], description: nil, email: nil, executables: [], extensions: [], extra_rdoc_files: [], files: [], homepage: nil, licenses: [], metadata: {}, name: nil, platform: Gem::Platform::RUBY, post_install_message: nil, rdoc_options: [], require_paths: ["lib"], required_ruby_version: Gem::Requirement.default, required_rubygems_version: Gem::Requirement.default, requirements: [], rubygems_version: Gem::VERSION, signing_key: nil, specification_version: CURRENT_SPECIFICATION_VERSION, summary: nil, test_files: [], version: nil, }.freeze # rubocop:disable Style/MutableConstant INITIALIZE_CODE_FOR_DEFAULTS = {} # :nodoc: # rubocop:enable Style/MutableConstant @@default_value.each do |k,v| INITIALIZE_CODE_FOR_DEFAULTS[k] = case v when [], {}, true, false, nil, Numeric, Symbol v.inspect when String v.dump else "default_value(:#{k}).dup" end end @@attributes = @@default_value.keys.sort_by(&:to_s) @@array_attributes = @@default_value.select {|_k,v| v.is_a?(Array) }.keys @@nil_attributes, @@non_nil_attributes = @@default_value.keys.partition do |k| @@default_value[k].nil? end # Sentinel object to represent "not found" stubs NOT_FOUND = Struct.new(:to_spec, :this).new # :nodoc: deprecate_constant :NOT_FOUND # Tracking removed method calls to warn users during build time. REMOVED_METHODS = [:rubyforge_project=, :mark_version].freeze # :nodoc: def removed_method_calls @removed_method_calls ||= [] end ###################################################################### # :section: Required gemspec attributes ## # This gem's name. # # Usage: # # spec.name = 'rake' attr_accessor :name ## # This gem's version. # # The version string can contain numbers and periods, such as +1.0.0+. # A gem is a 'prerelease' gem if the version has a letter in it, such as # +1.0.0.pre+. # # Usage: # # spec.version = '0.4.1' attr_reader :version ## # A short summary of this gem's description. Displayed in gem list -d. # # The #description should be more detailed than the summary. # # Usage: # # spec.summary = "This is a small summary of my gem" attr_reader :summary ## # Files included in this gem. You cannot append to this accessor, you must # assign to it. # # Only add files you can require to this list, not directories, etc. # # Directories are automatically stripped from this list when building a gem, # other non-files cause an error. # # Usage: # # require 'rake' # spec.files = FileList['lib/**/*.rb', # 'bin/*', # '[A-Z]*'].to_a # # # or without Rake... # spec.files = Dir['lib/**/*.rb'] + Dir['bin/*'] # spec.files += Dir['[A-Z]*'] # spec.files.reject! { |fn| fn.include? "CVS" } def files # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks) # DOC: Why isn't it normal? Why does it suck? How can we fix this? @files = [@files, @test_files, add_bindir(@executables), @extra_rdoc_files, @extensions].flatten.compact.uniq.sort end ## # A list of authors for this gem. # # Alternatively, a single author can be specified by assigning a string to # +spec.author+ # # Usage: # # spec.authors = ['John Jones', 'Mary Smith'] def authors=(value) @authors = Array(value).flatten.grep(String) end ###################################################################### # :section: Recommended gemspec attributes ## # The version of Ruby required by this gem # # Usage: # # spec.required_ruby_version = '>= 2.7.0' attr_reader :required_ruby_version ## # A long description of this gem # # The description should be more detailed than the summary but not # excessively long. A few paragraphs is a recommended length with no # examples or formatting. # # Usage: # # spec.description = <<~EOF # Rake is a Make-like program implemented in Ruby. Tasks and # dependencies are specified in standard Ruby syntax. # EOF attr_reader :description ## # A contact email address (or addresses) for this gem # # Usage: # # spec.email = 'john.jones@example.com' # spec.email = ['jack@example.com', 'jill@example.com'] attr_accessor :email ## # The URL of this gem's home page # # Usage: # # spec.homepage = 'https://github.com/ruby/rake' attr_accessor :homepage ## # The license for this gem. # # The license must be no more than 64 characters. # # This should just be the name of your license. The full text of the license # should be inside of the gem (at the top level) when you build it. # # The simplest way is to specify the standard SPDX ID # https://spdx.org/licenses/ for the license. # Ideally, you should pick one that is OSI (Open Source Initiative) # https://opensource.org/licenses/ approved. # # The most commonly used OSI-approved licenses are MIT and Apache-2.0. # GitHub also provides a license picker at https://choosealicense.com/. # # You can also use a custom license file along with your gemspec and specify # a LicenseRef-, where idstring is the name of the file containing # the license text. # # You should specify a license for your gem so that people know how they are # permitted to use it and any restrictions you're placing on it. Not # specifying a license means all rights are reserved; others have no right # to use the code for any purpose. # # You can set multiple licenses with #licenses= # # Usage: # spec.license = 'MIT' def license=(o) self.licenses = [o] end ## # The license(s) for the library. # # Each license must be a short name, no more than 64 characters. # # This should just be the name of your license. The full # text of the license should be inside of the gem when you build it. # # See #license= for more discussion # # Usage: # spec.licenses = ['MIT', 'GPL-2.0'] def licenses=(licenses) @licenses = Array licenses end ## # The metadata holds extra data for this gem that may be useful to other # consumers and is settable by gem authors. # # Metadata items have the following restrictions: # # * The metadata must be a Hash object # * All keys and values must be Strings # * Keys can be a maximum of 128 bytes and values can be a maximum of 1024 # bytes # * All strings must be UTF-8, no binary data is allowed # # You can use metadata to specify links to your gem's homepage, codebase, # documentation, wiki, mailing list, issue tracker and changelog. # # s.metadata = { # "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", # "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", # "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", # "homepage_uri" => "https://bestgemever.example.io", # "mailing_list_uri" => "https://groups.example.com/bestgemever", # "source_code_uri" => "https://example.com/user/bestgemever", # "wiki_uri" => "https://example.com/user/bestgemever/wiki", # "funding_uri" => "https://example.com/donate" # } # # These links will be used on your gem's page on rubygems.org and must pass # validation against following regex. # # %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} attr_accessor :metadata ###################################################################### # :section: Optional gemspec attributes ## # Singular (alternative) writer for #authors # # Usage: # # spec.author = 'John Jones' def author=(o) self.authors = [o] end ## # The path in the gem for executable scripts. Usually 'exe' # # Usage: # # spec.bindir = 'exe' attr_accessor :bindir ## # The certificate chain used to sign this gem. See Gem::Security for # details. attr_accessor :cert_chain ## # A message that gets displayed after the gem is installed. # # Usage: # # spec.post_install_message = "Thanks for installing!" attr_accessor :post_install_message ## # The platform this gem runs on. # # This is usually Gem::Platform::RUBY or Gem::Platform::CURRENT. # # Most gems contain pure Ruby code; they should simply leave the default # value in place. Some gems contain C (or other) code to be compiled into a # Ruby "extension". The gem should leave the default value in place unless # the code will only compile on a certain type of system. Some gems consist # of pre-compiled code ("binary gems"). It's especially important that they # set the platform attribute appropriately. A shortcut is to set the # platform to Gem::Platform::CURRENT, which will cause the gem builder to set # the platform to the appropriate value for the system on which the build is # being performed. # # If this attribute is set to a non-default value, it will be included in # the filename of the gem when it is built such as: # nokogiri-1.6.0-x86-mingw32.gem # # Usage: # # spec.platform = Gem::Platform.local def platform=(platform) @original_platform = platform case platform when Gem::Platform::CURRENT then @new_platform = Gem::Platform.local @original_platform = @new_platform.to_s when Gem::Platform then @new_platform = platform # legacy constants when nil, Gem::Platform::RUBY then @new_platform = Gem::Platform::RUBY when "mswin32" then # was Gem::Platform::WIN32 @new_platform = Gem::Platform.new "x86-mswin32" when "i586-linux" then # was Gem::Platform::LINUX_586 @new_platform = Gem::Platform.new "x86-linux" when "powerpc-darwin" then # was Gem::Platform::DARWIN @new_platform = Gem::Platform.new "ppc-darwin" else @new_platform = Gem::Platform.new platform end @platform = @new_platform.to_s end ## # Paths in the gem to add to $LOAD_PATH when this gem is # activated. #-- # See also #require_paths #++ # If you have an extension you do not need to add "ext" to the # require path, the extension build process will copy the extension files # into "lib" for you. # # The default value is "lib" # # Usage: # # # If all library files are in the root directory... # spec.require_paths = ['.'] def require_paths=(val) @require_paths = Array(val) end ## # The RubyGems version required by this gem attr_reader :required_rubygems_version ## # The key used to sign this gem. See Gem::Security for details. attr_accessor :signing_key ## # Adds a development dependency named +gem+ with +requirements+ to this # gem. # # Usage: # # spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4' # # Development dependencies aren't installed by default and aren't # activated when a gem is required. def add_development_dependency(gem, *requirements) add_dependency_with_type(gem, :development, requirements) end ## # Adds a runtime dependency named +gem+ with +requirements+ to this gem. # # Usage: # # spec.add_dependency 'example', '~> 1.1', '>= 1.1.4' def add_dependency(gem, *requirements) if requirements.uniq.size != requirements.size warn "WARNING: duplicated #{gem} dependency #{requirements}" end add_dependency_with_type(gem, :runtime, requirements) end ## # Executables included in the gem. # # For example, the rake gem has rake as an executable. You don't specify the # full path (as in bin/rake); all application-style files are expected to be # found in bindir. These files must be executable Ruby files. Files that # use bash or other interpreters will not work. # # Executables included may only be ruby scripts, not scripts for other # languages or compiled binaries. # # Usage: # # spec.executables << 'rake' def executables @executables ||= [] end ## # Extensions to build when installing the gem, specifically the paths to # extconf.rb-style files used to compile extensions. # # These files will be run when the gem is installed, causing the C (or # whatever) code to be compiled on the user's machine. # # Usage: # # spec.extensions << 'ext/rmagic/extconf.rb' # # See Gem::Ext::Builder for information about writing extensions for gems. def extensions @extensions ||= [] end ## # Extra files to add to RDoc such as README or doc/examples.txt # # When the user elects to generate the RDoc documentation for a gem (typically # at install time), all the library files are sent to RDoc for processing. # This option allows you to have some non-code files included for a more # complete set of documentation. # # Usage: # # spec.extra_rdoc_files = ['README', 'doc/user-guide.txt'] def extra_rdoc_files @extra_rdoc_files ||= [] end ## # The version of RubyGems that installed this gem. Returns # Gem::Version.new(0) for gems installed by versions earlier # than RubyGems 2.2.0. def installed_by_version # :nodoc: @installed_by_version ||= Gem::Version.new(0) end ## # Sets the version of RubyGems that installed this gem. See also # #installed_by_version. def installed_by_version=(version) # :nodoc: @installed_by_version = Gem::Version.new version end ## # Specifies the rdoc options to be used when generating API documentation. # # Usage: # # spec.rdoc_options << '--title' << 'Rake -- Ruby Make' << # '--main' << 'README' << # '--line-numbers' def rdoc_options @rdoc_options ||= [] end LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1") ## # The version of Ruby required by this gem. The ruby version can be # specified to the patch-level: # # $ ruby -v -e 'p Gem.ruby_version' # ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0] # # # # Prereleases can also be specified. # # Usage: # # # This gem will work with 1.8.6 or greater... # spec.required_ruby_version = '>= 1.8.6' # # # Only with final releases of major version 2 where minor version is at least 3 # spec.required_ruby_version = '~> 2.3' # # # Only prereleases or final releases after 2.6.0.preview2 # spec.required_ruby_version = '> 2.6.0.preview2' # # # This gem will work with 2.3.0 or greater, including major version 3, but lesser than 4.0.0 # spec.required_ruby_version = '>= 2.3', '< 4' def required_ruby_version=(req) @required_ruby_version = Gem::Requirement.create req @required_ruby_version.requirements.map! do |op, v| if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4 [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))] else [op, v] end end end ## # The RubyGems version required by this gem def required_rubygems_version=(req) @required_rubygems_version = Gem::Requirement.create req end ## # Lists the external (to RubyGems) requirements that must be met for this gem # to work. It's simply information for the user. # # Usage: # # spec.requirements << 'libmagick, v6.0' # spec.requirements << 'A good graphics card' def requirements @requirements ||= [] end ## # A collection of unit test files. They will be loaded as unit tests when # the user requests a gem to be unit tested. # # Usage: # spec.test_files = Dir.glob('test/tc_*.rb') # spec.test_files = ['tests/test-suite.rb'] def test_files=(files) # :nodoc: @test_files = Array files end ###################################################################### # :section: Read-only attributes ## # The version of RubyGems used to create this gem. attr_accessor :rubygems_version ## # The path where this gem installs its extensions. def extensions_dir @extensions_dir ||= super end ###################################################################### # :section: Specification internals ## # True when this gemspec has been activated. This attribute is not persisted. attr_accessor :activated alias_method :activated?, :activated ## # Autorequire was used by old RubyGems to automatically require a file. # # Deprecated: It is neither supported nor functional. attr_accessor :autorequire # :nodoc: ## # Allows deinstallation of gems with legacy platforms. attr_writer :original_platform # :nodoc: ## # The Gem::Specification version of this gemspec. # # Do not set this, it is set automatically when the gem is packaged. attr_accessor :specification_version def self._all # :nodoc: specification_record.all end def self.clear_load_cache # :nodoc: @load_cache_mutex.synchronize do @load_cache.clear end end private_class_method :clear_load_cache def self.gem_path # :nodoc: Gem.path end private_class_method :gem_path def self.each_gemspec(dirs) # :nodoc: dirs.each do |dir| Gem::Util.glob_files_in_dir("*.gemspec", dir).each do |path| yield path end end end def self.gemspec_stubs_in(dir, pattern) # :nodoc: Gem::Util.glob_files_in_dir(pattern, dir).map {|path| yield path }.select(&:valid?) end def self.each_spec(dirs) # :nodoc: each_gemspec(dirs) do |path| spec = self.load path yield spec if spec end end ## # Returns a Gem::StubSpecification for every installed gem def self.stubs specification_record.stubs end ## # Returns a Gem::StubSpecification for default gems def self.default_stubs(pattern = "*.gemspec") base_dir = Gem.default_dir gems_dir = File.join base_dir, "gems" gemspec_stubs_in(Gem.default_specifications_dir, pattern) do |path| Gem::StubSpecification.default_gemspec_stub(path, base_dir, gems_dir) end end ## # Returns a Gem::StubSpecification for installed gem named +name+ # only returns stubs that match Gem.platforms def self.stubs_for(name) specification_record.stubs_for(name) end ## # Finds stub specifications matching a pattern from the standard locations, # optionally filtering out specs not matching the current platform # def self.stubs_for_pattern(pattern, match_platform = true) # :nodoc: specification_record.stubs_for_pattern(pattern, match_platform) end def self._resort!(specs) # :nodoc: specs.sort! do |a, b| names = a.name <=> b.name next names if names.nonzero? versions = b.version <=> a.version next versions if versions.nonzero? platforms = Gem::Platform.sort_priority(b.platform) <=> Gem::Platform.sort_priority(a.platform) next platforms if platforms.nonzero? default_gem = a.default_gem_priority <=> b.default_gem_priority next default_gem if default_gem.nonzero? a.base_dir_priority(gem_path) <=> b.base_dir_priority(gem_path) end end ## # Loads the default specifications. It should be called only once. def self.load_defaults each_spec([Gem.default_specifications_dir]) do |spec| # #load returns nil if the spec is bad, so we just ignore # it at this stage Gem.register_default_spec(spec) end end ## # Adds +spec+ to the known specifications, keeping the collection # properly sorted. def self.add_spec(spec) specification_record.add_spec(spec) end ## # Removes +spec+ from the known specs. def self.remove_spec(spec) specification_record.remove_spec(spec) end ## # Returns all specifications. This method is discouraged from use. # You probably want to use one of the Enumerable methods instead. def self.all warn "NOTE: Specification.all called from #{caller(1, 1).first}" unless Gem::Deprecate.skip _all end ## # Sets the known specs to +specs+. def self.all=(specs) specification_record.all = specs end ## # Return full names of all specs in sorted order. def self.all_names specification_record.all_names end ## # Return the list of all array-oriented instance variables. #-- # Not sure why we need to use so much stupid reflection in here... def self.array_attributes @@array_attributes.dup end ## # Return the list of all instance variables. #-- # Not sure why we need to use so much stupid reflection in here... def self.attribute_names @@attributes.dup end ## # Return the directories that Specification uses to find specs. def self.dirs @@dirs ||= Gem::SpecificationRecord.dirs_from(gem_path) end ## # Set the directories that Specification uses to find specs. Setting # this resets the list of known specs. def self.dirs=(dirs) reset @@dirs = Gem::SpecificationRecord.dirs_from(Array(dirs)) end extend Enumerable ## # Enumerate every known spec. See ::dirs= and ::add_spec to set the list of # specs. def self.each(&block) specification_record.each(&block) end ## # Returns every spec that matches +name+ and optional +requirements+. def self.find_all_by_name(name, *requirements) specification_record.find_all_by_name(name, *requirements) end ## # Returns every spec that has the given +full_name+ def self.find_all_by_full_name(full_name) stubs.select {|s| s.full_name == full_name }.map(&:to_spec) end ## # Find the best specification matching a +name+ and +requirements+. Raises # if the dependency doesn't resolve to a valid specification. def self.find_by_name(name, *requirements) requirements = Gem::Requirement.default if requirements.empty? Gem::Dependency.new(name, *requirements).to_spec end ## # Find the best specification matching a +full_name+. def self.find_by_full_name(full_name) stubs.find {|s| s.full_name == full_name }&.to_spec end ## # Return the best specification that contains the file matching +path+. def self.find_by_path(path) specification_record.find_by_path(path) end ## # Return the best specification that contains the file matching +path+ # amongst the specs that are not loaded. This method is different than # +find_inactive_by_path+ as it will filter out loaded specs by their name. def self.find_unloaded_by_path(path) specification_record.find_unloaded_by_path(path) end ## # Return the best specification that contains the file matching +path+ # amongst the specs that are not activated. def self.find_inactive_by_path(path) specification_record.find_inactive_by_path(path) end ## # Return the best specification that contains the file matching +path+, among # those already activated. def self.find_active_stub_by_path(path) specification_record.find_active_stub_by_path(path) end ## # Return currently unresolved specs that contain the file matching +path+. def self.find_in_unresolved(path) unresolved_specs.find_all {|spec| spec.contains_requirable_file? path } end ## # Search through all unresolved deps and sub-dependencies and return # specs that contain the file matching +path+. def self.find_in_unresolved_tree(path) unresolved_specs.each do |spec| spec.traverse do |_from_spec, _dep, to_spec, trail| if to_spec.has_conflicts? || to_spec.conflicts_when_loaded_with?(trail) :next else return trail.reverse if to_spec.contains_requirable_file? path end end end [] end def self.unresolved_specs unresolved_deps.values.flat_map(&:to_specs) end private_class_method :unresolved_specs ## # Special loader for YAML files. When a Specification object is loaded # from a YAML file, it bypasses the normal Ruby object initialization # routine (#initialize). This method makes up for that and deals with # gems of different ages. # # +input+ can be anything that YAML.load() accepts: String or IO. def self.from_yaml(input) Gem.load_yaml input = normalize_yaml_input input spec = Gem::SafeYAML.safe_load input if spec && spec.class == FalseClass raise Gem::EndOfYAMLException end unless Gem::Specification === spec raise Gem::Exception, "YAML data doesn't evaluate to gem specification" end spec.specification_version ||= NONEXISTENT_SPECIFICATION_VERSION spec.reset_nil_attributes_to_default spec.flatten_require_paths spec end ## # Return the latest specs, optionally including prerelease specs if # +prerelease+ is true. def self.latest_specs(prerelease = false) specification_record.latest_specs(prerelease) end ## # Return the latest installed spec for gem +name+. def self.latest_spec_for(name) specification_record.latest_spec_for(name) end def self._latest_specs(specs, prerelease = false) # :nodoc: result = {} specs.reverse_each do |spec| unless prerelease next if spec.version.prerelease? end result[spec.name] = spec end result.flat_map(&:last).sort_by(&:name) end ## # Loads Ruby format gemspec from +file+. def self.load(file) return unless file spec = @load_cache_mutex.synchronize { @load_cache[file] } return spec if spec return unless File.file?(file) code = Gem.open_file(file, "r:UTF-8:-", &:read) begin spec = eval code, binding, file if Gem::Specification === spec spec.loaded_from = File.expand_path file.to_s @load_cache_mutex.synchronize do prev = @load_cache[file] if prev spec = prev else @load_cache[file] = spec end end return spec end warn "[#{file}] isn't a Gem::Specification (#{spec.class} instead)." rescue SignalException, SystemExit raise rescue SyntaxError, StandardError => e warn "Invalid gemspec in [#{file}]: #{e}" end nil end ## # Specification attributes that must be non-nil def self.non_nil_attributes @@non_nil_attributes.dup end ## # Make sure the YAML specification is properly formatted with dashes def self.normalize_yaml_input(input) result = input.respond_to?(:read) ? input.read : input result = "--- " + result unless result.start_with?("--- ") result = result.dup result.gsub!(/ !!null \n/, " \n") # date: 2011-04-26 00:00:00.000000000Z # date: 2011-04-26 00:00:00.000000000 Z result.gsub!(/^(date: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+?)Z/, '\1 Z') result end ## # Return a list of all outdated local gem names. This method is HEAVY # as it must go fetch specifications from the server. # # Use outdated_and_latest_version if you wish to retrieve the latest remote # version as well. def self.outdated outdated_and_latest_version.map {|local, _| local.name } end ## # Enumerates the outdated local gems yielding the local specification and # the latest remote version. # # This method may take some time to return as it must check each local gem # against the server's index. def self.outdated_and_latest_version return enum_for __method__ unless block_given? # TODO: maybe we should switch to rubygems' version service? fetcher = Gem::SpecFetcher.fetcher latest_specs(true).each do |local_spec| dependency = Gem::Dependency.new local_spec.name, ">= #{local_spec.version}" remotes, = fetcher.search_for_dependency dependency remotes = remotes.map {|n, _| n.version } latest_remote = remotes.sort.last yield [local_spec, latest_remote] if latest_remote && local_spec.version < latest_remote end nil end ## # Is +name+ a required attribute? def self.required_attribute?(name) @@required_attributes.include? name.to_sym end ## # Required specification attributes def self.required_attributes @@required_attributes.dup end ## # Reset the list of known specs, running pre and post reset hooks # registered in Gem. def self.reset @@dirs = nil Gem.pre_reset_hooks.each(&:call) @specification_record = nil clear_load_cache unless unresolved_deps.empty? unresolved = unresolved_deps.filter_map do |name, dep| matching_versions = find_all_by_name(name) next if dep.latest_version? && matching_versions.any?(&:default_gem?) [dep, matching_versions.uniq(&:full_name)] end.to_h unless unresolved.empty? warn "WARN: Unresolved or ambiguous specs during Gem::Specification.reset:" unresolved.each do |dep, versions| warn " #{dep}" unless versions.empty? warn " Available/installed versions of this gem:" versions.each {|s| warn " - #{s.version}" } end end warn "WARN: Clearing out unresolved specs. Try 'gem cleanup '" warn "Please report a bug if this causes problems." end unresolved_deps.clear end Gem.post_reset_hooks.each(&:call) end ## # Keeps track of all currently known specifications def self.specification_record @specification_record ||= Gem::SpecificationRecord.new(dirs) end # DOC: This method needs documented or nodoc'd def self.unresolved_deps @unresolved_deps ||= Hash.new {|h, n| h[n] = Gem::Dependency.new n } end ## # Load custom marshal format, re-initializing defaults as needed def self._load(str) Gem.load_yaml Gem.load_safe_marshal yaml_set = false retry_count = 0 array = begin Gem::SafeMarshal.safe_load str rescue ArgumentError => e # Avoid an infinite retry loop when the argument error has nothing to do # with the classes not being defined. # 1 retry each allowed in case all 3 of # - YAML # - YAML::Syck::DefaultKey # - YAML::PrivateType # need to be defined raise if retry_count >= 3 # # Some very old marshaled specs included references to `YAML::PrivateType` # and `YAML::Syck::DefaultKey` constants due to bugs in the old emitter # that generated them. Workaround the issue by defining the necessary # constants and retrying. # message = e.message raise unless message.include?("YAML::") unless Object.const_defined?(:YAML) Object.const_set "YAML", Psych yaml_set = true end if message.include?("YAML::Syck::") YAML.const_set "Syck", YAML unless YAML.const_defined?(:Syck) YAML::Syck.const_set "DefaultKey", Class.new if message.include?("YAML::Syck::DefaultKey") && !YAML::Syck.const_defined?(:DefaultKey) elsif message.include?("YAML::PrivateType") && !YAML.const_defined?(:PrivateType) YAML.const_set "PrivateType", Class.new end retry_count += 1 retry ensure Object.__send__(:remove_const, "YAML") if yaml_set end spec = Gem::Specification.new spec.instance_variable_set :@specification_version, array[1] current_version = CURRENT_SPECIFICATION_VERSION field_count = if spec.specification_version > current_version spec.instance_variable_set :@specification_version, current_version MARSHAL_FIELDS[current_version] else MARSHAL_FIELDS[spec.specification_version] end if array.size < field_count raise TypeError, "invalid Gem::Specification format #{array.inspect}" end spec.instance_variable_set :@rubygems_version, array[0] # spec version spec.instance_variable_set :@name, array[2] spec.instance_variable_set :@version, array[3] spec.date = array[4] spec.instance_variable_set :@summary, array[5] spec.instance_variable_set :@required_ruby_version, array[6] spec.instance_variable_set :@required_rubygems_version, array[7] spec.platform = array[8] spec.instance_variable_set :@dependencies, array[9] # offset due to rubyforge_project removal spec.instance_variable_set :@email, array[11] spec.instance_variable_set :@authors, array[12] spec.instance_variable_set :@description, array[13] spec.instance_variable_set :@homepage, array[14] # offset due to has_rdoc removal spec.instance_variable_set :@licenses, array[17] spec.instance_variable_set :@metadata, array[18] spec.instance_variable_set :@loaded, false spec.instance_variable_set :@activated, false spec end def <=>(other) # :nodoc: sort_obj <=> other.sort_obj end def ==(other) # :nodoc: self.class === other && name == other.name && version == other.version && platform == other.platform end ## # Dump only crucial instance variables. #-- # MAINTAIN ORDER! # (down with the man) def _dump(limit) Marshal.dump [ @rubygems_version, @specification_version, @name, @version, date, @summary, @required_ruby_version, @required_rubygems_version, @original_platform, @dependencies, "", # rubyforge_project @email, @authors, @description, @homepage, true, # has_rdoc @new_platform, @licenses, @metadata, ] end ## # Activate this spec, registering it as a loaded spec and adding # it's lib paths to $LOAD_PATH. Returns true if the spec was # activated, false if it was previously activated. Freaks out if # there are conflicts upon activation. def activate other = Gem.loaded_specs[name] if other check_version_conflict other return false end raise_if_conflicts activate_dependencies add_self_to_load_path Gem.loaded_specs[name] = self @activated = true @loaded = true true end ## # Activate all unambiguously resolved runtime dependencies of this # spec. Add any ambiguous dependencies to the unresolved list to be # resolved later, as needed. def activate_dependencies unresolved = Gem::Specification.unresolved_deps runtime_dependencies.each do |spec_dep| if loaded = Gem.loaded_specs[spec_dep.name] next if spec_dep.matches_spec? loaded msg = "can't satisfy '#{spec_dep}', already activated '#{loaded.full_name}'" e = Gem::LoadError.new msg e.name = spec_dep.name raise e end specs = spec_dep.matching_specs(true).uniq(&:full_name) if specs.size == 0 raise Gem::MissingSpecError.new(spec_dep.name, spec_dep.requirement, "at: #{spec_file}") elsif specs.size == 1 specs.first.activate else name = spec_dep.name unresolved[name] = unresolved[name].merge spec_dep end end unresolved.delete self.name end ## # Abbreviate the spec for downloading. Abbreviated specs are only used for # searching, downloading and related activities and do not need deployment # specific information (e.g. list of files). So we abbreviate the spec, # making it much smaller for quicker downloads. def abbreviate self.files = [] self.test_files = [] self.rdoc_options = [] self.extra_rdoc_files = [] self.cert_chain = [] end ## # Sanitize the descriptive fields in the spec. Sometimes non-ASCII # characters will garble the site index. Non-ASCII characters will # be replaced by their XML entity equivalent. def sanitize self.summary = sanitize_string(summary) self.description = sanitize_string(description) self.post_install_message = sanitize_string(post_install_message) self.authors = authors.collect {|a| sanitize_string(a) } end ## # Sanitize a single string. def sanitize_string(string) return string unless string # HACK: the #to_s is in here because RSpec has an Array of Arrays of # Strings for authors. Need a way to disallow bad values on gemspec # generation. (Probably won't happen.) string.to_s end ## # Returns an array with bindir attached to each executable in the # +executables+ list def add_bindir(executables) return nil if executables.nil? if @bindir Array(executables).map {|e| File.join(@bindir, e) } else executables end rescue StandardError nil end ## # Adds a dependency on gem +dependency+ with type +type+ that requires # +requirements+. Valid types are currently :runtime and # :development. def add_dependency_with_type(dependency, type, requirements) requirements = if requirements.empty? Gem::Requirement.default else requirements.flatten end unless dependency.respond_to?(:name) && dependency.respond_to?(:requirement) dependency = Gem::Dependency.new(dependency.to_s, requirements, type) end dependencies << dependency end private :add_dependency_with_type alias_method :add_runtime_dependency, :add_dependency ## # Adds this spec's require paths to LOAD_PATH, in the proper location. def add_self_to_load_path return if default_gem? paths = full_require_paths Gem.add_to_load_path(*paths) end ## # Singular reader for #authors. Returns the first author in the list def author (val = authors) && val.first end ## # The list of author names who wrote this gem. # # spec.authors = ['Chad Fowler', 'Jim Weirich', 'Rich Kilmer'] def authors @authors ||= [] end ## # Returns the full path to installed gem's bin directory. # # NOTE: do not confuse this with +bindir+, which is just 'bin', not # a full path. def bin_dir @bin_dir ||= File.join gem_dir, bindir end ## # Returns the full path to an executable named +name+ in this gem. def bin_file(name) File.join bin_dir, name end ## # Returns the build_args used to install the gem def build_args if File.exist? build_info_file build_info = File.readlines build_info_file build_info = build_info.map(&:strip) build_info.delete "" build_info else [] end end ## # Builds extensions for this platform if the gem has extensions listed and # the gem.build_complete file is missing. def build_extensions # :nodoc: return if extensions.empty? return if default_gem? # we need to fresh build when same name and version of default gems return if self.class.find_by_full_name(full_name)&.default_gem? return if File.exist? gem_build_complete_path return unless File.writable?(base_dir) return unless File.exist?(File.join(base_dir, "extensions")) begin # We need to require things in $LOAD_PATH without looking for the # extension we are about to build. unresolved_deps = Gem::Specification.unresolved_deps.dup Gem::Specification.unresolved_deps.clear require_relative "config_file" require_relative "ext" require_relative "user_interaction" ui = Gem::SilentUI.new Gem::DefaultUserInteraction.use_ui ui do builder = Gem::Ext::Builder.new self builder.build_extensions end ensure ui&.close Gem::Specification.unresolved_deps.replace unresolved_deps end end ## # Returns the full path to the build info directory def build_info_dir File.join base_dir, "build_info" end ## # Returns the full path to the file containing the build # information generated when the gem was installed def build_info_file File.join build_info_dir, "#{full_name}.info" end ## # Returns the full path to the cache directory containing this # spec's cached gem. def cache_dir File.join base_dir, "cache" end ## # Returns the full path to the cached gem for this spec. def cache_file File.join cache_dir, "#{full_name}.gem" end ## # Return any possible conflicts against the currently loaded specs. def conflicts conflicts = {} runtime_dependencies.each do |dep| spec = Gem.loaded_specs[dep.name] if spec && !spec.satisfies_requirement?(dep) (conflicts[spec] ||= []) << dep end end env_req = Gem.env_requirement(name) (conflicts[self] ||= []) << env_req unless env_req.satisfied_by? version conflicts end ## # return true if there will be conflict when spec if loaded together with the list of specs. def conflicts_when_loaded_with?(list_of_specs) # :nodoc: result = list_of_specs.any? do |spec| spec.runtime_dependencies.any? {|dep| (dep.name == name) && !satisfies_requirement?(dep) } end result end ## # Return true if there are possible conflicts against the currently loaded specs. def has_conflicts? return true unless Gem.env_requirement(name).satisfied_by?(version) runtime_dependencies.any? do |dep| spec = Gem.loaded_specs[dep.name] spec && !spec.satisfies_requirement?(dep) end rescue ArgumentError => e raise e, "#{name} #{version}: #{e.message}" end # The date this gem was created. # # If SOURCE_DATE_EPOCH is set as an environment variable, use that to support # reproducible builds; otherwise, default to the current UTC date. # # Details on SOURCE_DATE_EPOCH: # https://reproducible-builds.org/specs/source-date-epoch/ def date @date ||= Time.utc(*Gem.source_date_epoch.utc.to_a[3..5].reverse) end DateLike = Object.new # :nodoc: def DateLike.===(obj) # :nodoc: defined?(::Date) && Date === obj end DateTimeFormat = # :nodoc: /\A (\d{4})-(\d{2})-(\d{2}) (\s+ \d{2}:\d{2}:\d{2}\.\d+ \s* (Z | [-+]\d\d:\d\d) )? \Z/x ## # The date this gem was created # # DO NOT set this, it is set automatically when the gem is packaged. def date=(date) # We want to end up with a Time object with one-day resolution. # This is the cleanest, most-readable, faster-than-using-Date # way to do it. @date = case date when String then if DateTimeFormat =~ date Time.utc($1.to_i, $2.to_i, $3.to_i) else raise(Gem::InvalidSpecificationException, "invalid date format in specification: #{date.inspect}") end when Time, DateLike then Time.utc(date.year, date.month, date.day) else TODAY end end ## # The default value for specification attribute +name+ def default_value(name) @@default_value[name] end ## # A list of Gem::Dependency objects this gem depends on. # # Use #add_dependency or #add_development_dependency to add dependencies to # a gem. def dependencies @dependencies ||= [] end ## # Return a list of all gems that have a dependency on this gemspec. The # list is structured with entries that conform to: # # [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]] def dependent_gems(check_dev = true) out = [] Gem::Specification.each do |spec| deps = check_dev ? spec.dependencies : spec.runtime_dependencies deps.each do |dep| next unless satisfies_requirement?(dep) sats = [] find_all_satisfiers(dep) do |sat| sats << sat end out << [spec, dep, sats] end end out end ## # Returns all specs that matches this spec's runtime dependencies. def dependent_specs runtime_dependencies.flat_map(&:to_specs) end ## # A detailed description of this gem. See also #summary def description=(str) @description = str.to_s end ## # List of dependencies that are used for development def development_dependencies dependencies.select {|d| d.type == :development } end ## # Returns the full path to this spec's documentation directory. If +type+ # is given it will be appended to the end. For example: # # spec.doc_dir # => "/path/to/gem_repo/doc/a-1" # # spec.doc_dir 'ri' # => "/path/to/gem_repo/doc/a-1/ri" def doc_dir(type = nil) @doc_dir ||= File.join base_dir, "doc", full_name if type File.join @doc_dir, type else @doc_dir end end def encode_with(coder) # :nodoc: coder.add "name", @name coder.add "version", @version coder.add "platform", platform.to_s coder.add "original_platform", original_platform.to_s if platform.to_s != original_platform.to_s attributes = @@attributes.map(&:to_s) - %w[name version platform] attributes.each do |name| value = instance_variable_get("@#{name}") coder.add name, value unless value.nil? end end def eql?(other) # :nodoc: self.class === other && same_attributes?(other) end ## # Singular accessor for #executables def executable (val = executables) && val.first end ## # Singular accessor for #executables def executable=(o) self.executables = [o] end ## # Sets executables to +value+, ensuring it is an array. def executables=(value) @executables = Array(value) end ## # Sets extensions to +extensions+, ensuring it is an array. def extensions=(extensions) @extensions = Array extensions end ## # Sets extra_rdoc_files to +files+, ensuring it is an array. def extra_rdoc_files=(files) @extra_rdoc_files = Array files end ## # The default (generated) file name of the gem. See also #spec_name. # # spec.file_name # => "example-1.0.gem" def file_name "#{full_name}.gem" end ## # Sets files to +files+, ensuring it is an array. def files=(files) @files = Array files end ## # Finds all gems that satisfy +dep+ def find_all_satisfiers(dep) Gem::Specification.each do |spec| yield spec if spec.satisfies_requirement? dep end end private :find_all_satisfiers ## # Creates a duplicate spec without large blobs that aren't used at runtime. def for_cache spec = dup spec.files = nil spec.test_files = nil spec end ## # Work around old bundler versions removing my methods # Can be removed once RubyGems can no longer install Bundler 2.5 def gem_dir # :nodoc: super end def gems_dir @gems_dir ||= File.join(base_dir, "gems") end ## # True if this gem has files in test_files def has_unit_tests? # :nodoc: !test_files.empty? end # :stopdoc: alias_method :has_test_suite?, :has_unit_tests? # :startdoc: def hash # :nodoc: name.hash ^ version.hash end def init_with(coder) # :nodoc: @installed_by_version ||= nil yaml_initialize coder.tag, coder.map end eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # frozen_string_literal: true def set_nil_attributes_to_nil #{@@nil_attributes.map {|key| "@#{key} = nil" }.join "; "} end private :set_nil_attributes_to_nil def set_not_nil_attributes_to_default_values #{@@non_nil_attributes.map {|key| "@#{key} = #{INITIALIZE_CODE_FOR_DEFAULTS[key]}" }.join ";"} end private :set_not_nil_attributes_to_default_values RUBY ## # Specification constructor. Assigns the default values to the attributes # and yields itself for further initialization. Optionally takes +name+ and # +version+. def initialize(name = nil, version = nil) super() @gems_dir = nil @base_dir = nil @loaded = false @activated = false @loaded_from = nil @original_platform = nil @installed_by_version = nil set_nil_attributes_to_nil set_not_nil_attributes_to_default_values @new_platform = Gem::Platform::RUBY self.name = name if name self.version = version if version if (platform = Gem.platforms.last) && platform != Gem::Platform::RUBY && platform != Gem::Platform.local self.platform = platform end yield self if block_given? end ## # Duplicates Array and Gem::Requirement attributes from +other_spec+ so state isn't shared. # def initialize_copy(other_spec) self.class.array_attributes.each do |name| name = :"@#{name}" next unless other_spec.instance_variable_defined? name begin val = other_spec.instance_variable_get(name) if val instance_variable_set name, val.dup elsif Gem.configuration.really_verbose warn "WARNING: #{full_name} has an invalid nil value for #{name}" end rescue TypeError e = Gem::FormatException.new \ "#{full_name} has an invalid value for #{name}" e.file_path = loaded_from raise e end end @required_ruby_version = other_spec.required_ruby_version.dup @required_rubygems_version = other_spec.required_rubygems_version.dup end def base_dir return Gem.dir unless loaded_from @base_dir ||= if default_gem? File.dirname File.dirname File.dirname loaded_from else File.dirname File.dirname loaded_from end end def inspect # :nodoc: if $DEBUG super else "#{super[0..-2]} #{full_name}>" end end ## # Files in the Gem under one of the require_paths def lib_files @files.select do |file| require_paths.any? do |path| file.start_with? path end end end ## # Singular accessor for #licenses def license licenses.first end ## # Plural accessor for setting licenses # # See #license= for details def licenses @licenses ||= [] end def internal_init # :nodoc: super @bin_dir = nil @doc_dir = nil @ri_dir = nil @spec_dir = nil @spec_file = nil end ## # Track removed method calls to warn about during build time. # Warn about unknown attributes while loading a spec. def method_missing(sym, *a, &b) # :nodoc: if REMOVED_METHODS.include?(sym) removed_method_calls << sym return end if @specification_version > CURRENT_SPECIFICATION_VERSION && sym.to_s.end_with?("=") warn "ignoring #{sym} loading #{full_name}" if $DEBUG else super end end ## # Is this specification missing its extensions? When this returns true you # probably want to build_extensions def missing_extensions? return false if RUBY_ENGINE == "jruby" return false if extensions.empty? return false if default_gem? return false if File.exist? gem_build_complete_path true end ## # Normalize the list of files so that: # * All file lists have redundancies removed. # * Files referenced in the extra_rdoc_files are included in the package # file list. def normalize if defined?(@extra_rdoc_files) && @extra_rdoc_files @extra_rdoc_files.uniq! @files ||= [] @files.concat(@extra_rdoc_files) end @files = @files.uniq.sort if @files @extensions = @extensions.uniq.sort if @extensions @test_files = @test_files.uniq.sort if @test_files @executables = @executables.uniq.sort if @executables @extra_rdoc_files = @extra_rdoc_files.uniq.sort if @extra_rdoc_files end ## # Return a NameTuple that represents this Specification def name_tuple Gem::NameTuple.new name, version, original_platform end ## # Returns the full name (name-version) of this gemspec using the original # platform. For use with legacy gems. def original_name # :nodoc: if platform == Gem::Platform::RUBY || platform.nil? "#{@name}-#{@version}" else "#{@name}-#{@version}-#{@original_platform}" end end ## # Cruft. Use +platform+. def original_platform # :nodoc: @original_platform ||= platform end ## # The platform this gem runs on. See Gem::Platform for details. def platform @new_platform ||= Gem::Platform::RUBY # rubocop:disable Naming/MemoizedInstanceVariableName end def pretty_print(q) # :nodoc: q.group 2, "Gem::Specification.new do |s|", "end" do q.breakable attributes = @@attributes - [:name, :version] attributes.unshift :installed_by_version attributes.unshift :version attributes.unshift :name attributes.each do |attr_name| current_value = send attr_name current_value = current_value.sort if [:files, :test_files].include? attr_name next unless current_value != default_value(attr_name) || self.class.required_attribute?(attr_name) q.text "s.#{attr_name} = " if attr_name == :date current_value = current_value.utc q.text "Time.utc(#{current_value.year}, #{current_value.month}, #{current_value.day})" else q.pp current_value end q.breakable end end end ## # Raise an exception if the version of this spec conflicts with the one # that is already loaded (+other+) def check_version_conflict(other) # :nodoc: return if version == other.version # This gem is already loaded. If the currently loaded gem is not in the # list of candidate gems, then we have a version conflict. msg = "can't activate #{full_name}, already activated #{other.full_name}" e = Gem::LoadError.new msg e.name = name raise e end private :check_version_conflict ## # Check the spec for possible conflicts and freak out if there are any. def raise_if_conflicts # :nodoc: if has_conflicts? raise Gem::ConflictError.new self, conflicts end end ## # Sets rdoc_options to +value+, ensuring it is an array. def rdoc_options=(options) @rdoc_options = Array options end ## # Singular accessor for #require_paths def require_path (val = require_paths) && val.first end ## # Singular accessor for #require_paths def require_path=(path) self.require_paths = Array(path) end ## # Set requirements to +req+, ensuring it is an array. def requirements=(req) @requirements = Array req end def respond_to_missing?(m, include_private = false) # :nodoc: false end ## # Returns the full path to this spec's ri directory. def ri_dir @ri_dir ||= File.join base_dir, "ri", full_name end ## # Return a string containing a Ruby code representation of the given # object. def ruby_code(obj) case obj when String then obj.dump + ".freeze" when Array then "[" + obj.map {|x| ruby_code x }.join(", ") + "]" when Hash then seg = obj.keys.sort.map {|k| "#{k.to_s.dump} => #{obj[k].to_s.dump}" } "{ #{seg.join(", ")} }" when Gem::Version then ruby_code(obj.to_s) when DateLike then obj.strftime("%Y-%m-%d").dump when Time then obj.strftime("%Y-%m-%d").dump when Numeric then obj.inspect when true, false, nil then obj.inspect when Gem::Platform then "Gem::Platform.new(#{ruby_code obj.to_a})" when Gem::Requirement then list = obj.as_list "Gem::Requirement.new(#{ruby_code(list.size == 1 ? obj.to_s : list)})" else raise Gem::Exception, "ruby_code case not handled: #{obj.class}" end end private :ruby_code ## # List of dependencies that will automatically be activated at runtime. def runtime_dependencies dependencies.select(&:runtime?) end ## # True if this gem has the same attributes as +other+. def same_attributes?(spec) @@attributes.all? {|name, _default| send(name) == spec.send(name) } end private :same_attributes? ## # Checks if this specification meets the requirement of +dependency+. def satisfies_requirement?(dependency) @name == dependency.name && dependency.requirement.satisfied_by?(@version) end ## # Returns an object you can use to sort specifications in #sort_by. def sort_obj [@name, @version, Gem::Platform.sort_priority(@new_platform)] end ## # Used by Gem::Resolver to order Gem::Specification objects def source # :nodoc: Gem::Source::Installed.new end ## # Returns the full path to the directory containing this spec's # gemspec file. eg: /usr/local/lib/ruby/gems/1.8/specifications def spec_dir @spec_dir ||= File.join base_dir, "specifications" end ## # Returns the full path to this spec's gemspec file. # eg: /usr/local/lib/ruby/gems/1.8/specifications/mygem-1.0.gemspec def spec_file @spec_file ||= File.join spec_dir, "#{full_name}.gemspec" end ## # The default name of the gemspec. See also #file_name # # spec.spec_name # => "example-1.0.gemspec" def spec_name "#{full_name}.gemspec" end ## # A short summary of this gem's description. def summary=(str) @summary = str.to_s.strip. gsub(/(\w-)\n[ \t]*(\w)/, '\1\2').gsub(/\n[ \t]*/, " ") # so. weird. end ## # Singular accessor for #test_files def test_file # :nodoc: (val = test_files) && val.first end ## # Singular mutator for #test_files def test_file=(file) # :nodoc: self.test_files = [file] end ## # Test files included in this gem. You cannot append to this accessor, you # must assign to it. def test_files # :nodoc: # Handle the possibility that we have @test_suite_file but not # @test_files. This will happen when an old gem is loaded via # YAML. if defined? @test_suite_file @test_files = [@test_suite_file].flatten @test_suite_file = nil end if defined?(@test_files) && @test_files @test_files else @test_files = [] end end ## # Returns a Ruby code representation of this specification, such that it can # be eval'ed and reconstruct the same specification later. Attributes that # still have their default values are omitted. def to_ruby result = [] result << "# -*- encoding: utf-8 -*-" result << "#{Gem::StubSpecification::PREFIX}#{name} #{version} #{platform} #{raw_require_paths.join("\0")}" result << "#{Gem::StubSpecification::PREFIX}#{extensions.join "\0"}" unless extensions.empty? result << nil result << "Gem::Specification.new do |s|" result << " s.name = #{ruby_code name}" result << " s.version = #{ruby_code version}" unless platform.nil? || platform == Gem::Platform::RUBY result << " s.platform = #{ruby_code original_platform}" end result << "" result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version=" if metadata && !metadata.empty? result << " s.metadata = #{ruby_code metadata} if s.respond_to? :metadata=" end result << " s.require_paths = #{ruby_code raw_require_paths}" handled = [ :dependencies, :name, :platform, :require_paths, :required_rubygems_version, :specification_version, :version, :metadata, :signing_key, ] @@attributes.each do |attr_name| next if handled.include? attr_name current_value = send(attr_name) if current_value != default_value(attr_name) || self.class.required_attribute?(attr_name) result << " s.#{attr_name} = #{ruby_code current_value}" end end if String === signing_key result << " s.signing_key = #{ruby_code signing_key}" end if @installed_by_version result << nil result << " s.installed_by_version = #{ruby_code Gem::VERSION}" end unless dependencies.empty? result << nil result << " s.specification_version = #{specification_version}" result << nil dependencies.each do |dep| dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>.freeze, #{ruby_code dep.requirements_list})" end end result << "end" result << nil result.join "\n" end ## # Returns a Ruby lighter-weight code representation of this specification, # used for indexing only. # # See #to_ruby. def to_ruby_for_cache for_cache.to_ruby end def to_s # :nodoc: "#" end ## # Returns self def to_spec self end def to_yaml(opts = {}) # :nodoc: Gem.load_yaml # Because the user can switch the YAML engine behind our # back, we have to check again here to make sure that our # psych code was properly loaded, and load it if not. unless Gem.const_defined?(:NoAliasYAMLTree) require_relative "psych_tree" end builder = Gem::NoAliasYAMLTree.create builder << self ast = builder.tree require "stringio" io = StringIO.new io.set_encoding Encoding::UTF_8 Psych::Visitors::Emitter.new(io).accept(ast) io.string.gsub(/ !!null \n/, " \n") end ## # Recursively walk dependencies of this spec, executing the +block+ for each # hop. def traverse(trail = [], visited = {}, &block) trail.push(self) begin runtime_dependencies.each do |dep| dep.matching_specs(true).each do |dep_spec| next if visited.key?(dep_spec) visited[dep_spec] = true trail.push(dep_spec) begin result = block[self, dep, dep_spec, trail] ensure trail.pop end next if result == :next spec_name = dep_spec.name dep_spec.traverse(trail, visited, &block) unless trail.any? {|s| s.name == spec_name } end end ensure trail.pop end end ## # Checks that the specification contains all required fields, and does a # very basic sanity check. # # Raises InvalidSpecificationException if the spec does not pass the # checks.. def validate(packaging = true, strict = false) normalize validation_policy = Gem::SpecificationPolicy.new(self) validation_policy.packaging = packaging validation_policy.validate(strict) end def keep_only_files_and_directories @executables.delete_if {|x| File.directory?(File.join(@bindir, x)) } @extensions.delete_if {|x| File.directory?(x) && !File.symlink?(x) } @extra_rdoc_files.delete_if {|x| File.directory?(x) && !File.symlink?(x) } @files.delete_if {|x| File.directory?(x) && !File.symlink?(x) } @test_files.delete_if {|x| File.directory?(x) && !File.symlink?(x) } end def validate_for_resolution Gem::SpecificationPolicy.new(self).validate_for_resolution end ## # Set the version to +version+. def version=(version) @version = version.nil? ? version : Gem::Version.create(version) end def stubbed? false end def yaml_initialize(tag, vals) # :nodoc: vals.each do |ivar, val| case ivar when "date" # Force Date to go through the extra coerce logic in date= self.date = val when "platform" self.platform = val else instance_variable_set "@#{ivar}", val end end end ## # Reset nil attributes to their default values to make the spec valid def reset_nil_attributes_to_default nil_attributes = self.class.non_nil_attributes.find_all do |name| !instance_variable_defined?("@#{name}") || instance_variable_get("@#{name}").nil? end nil_attributes.each do |attribute| default = default_value attribute value = case default when Time, Numeric, Symbol, true, false, nil then default else default.dup end instance_variable_set "@#{attribute}", value end @installed_by_version ||= nil nil end def flatten_require_paths # :nodoc: return unless raw_require_paths.first.is_a?(Array) warn "#{name} #{version} includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this" raw_require_paths.flatten! end def raw_require_paths # :nodoc: @require_paths end end PK!7jj psych_tree.rbnu[# frozen_string_literal: true module Gem if defined? ::Psych::Visitors class NoAliasYAMLTree < Psych::Visitors::YAMLTree def self.create new({}) end unless respond_to? :create def visit_String(str) return super unless str == "=" # or whatever you want quote = Psych::Nodes::Scalar::SINGLE_QUOTED @emitter.scalar str, nil, nil, false, true, quote end def visit_Hash(o) super(o.compact) end # Noop this out so there are no anchors def register(target, obj) end # This is ported over from the YAMLTree implementation in Ruby 1.9.3 def format_time(time) if time.utc? time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") else time.strftime("%Y-%m-%d %H:%M:%S.%9N %:z") end end private :format_time end end end PK!(++ safe_yaml.rbnu[# frozen_string_literal: true module Gem ### # This module is used for safely loading YAML specs from a gem. The # `safe_load` method defined on this module is specifically designed for # loading Gem specifications. For loading other YAML safely, please see # Psych.safe_load module SafeYAML PERMITTED_CLASSES = %w[ Symbol Time Date Gem::Dependency Gem::Platform Gem::Requirement Gem::Specification Gem::Version Gem::Version::Requirement ].freeze PERMITTED_SYMBOLS = %w[ development runtime ].freeze @aliases_enabled = true def self.aliases_enabled=(value) # :nodoc: @aliases_enabled = !!value end def self.aliases_enabled? # :nodoc: @aliases_enabled end def self.safe_load(input) ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled) end def self.load(input) ::Psych.safe_load(input, permitted_classes: [::Symbol]) end end end PK!巹compatibility.rbnu[# :stopdoc: #-- # This file contains all sorts of little compatibility hacks that we've # had to introduce over the years. Quarantining them into one file helps # us know when we can get rid of them. # # Ruby 1.9.x has introduced some things that are awkward, and we need to # support them, so we define some constants to use later. #++ module Gem # Only MRI 1.9.2 has the custom prelude. GEM_PRELUDE_SUCKAGE = RUBY_VERSION =~ /^1\.9\.2/ and RUBY_ENGINE == "ruby" end # Gem::QuickLoader exists in the gem prelude code in ruby 1.9.2 itself. # We gotta get rid of it if it's there, before we do anything else. if Gem::GEM_PRELUDE_SUCKAGE and defined?(Gem::QuickLoader) then Gem::QuickLoader.remove $LOADED_FEATURES.delete Gem::QuickLoader.path_to_full_rubygems_library if path = $LOADED_FEATURES.find {|n| n.end_with? '/rubygems.rb'} then raise LoadError, "another rubygems is already loaded from #{path}" end class << Gem remove_method :try_activate if Gem.respond_to?(:try_activate, true) end end module Gem RubyGemsVersion = VERSION # TODO remove at RubyGems 3 RbConfigPriorities = %w[ MAJOR MINOR TEENY EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir rubylibdir ] unless defined?(ConfigMap) ## # Configuration settings from ::RbConfig ConfigMap = Hash.new do |cm, key| # TODO remove at RubyGems 3 cm[key] = RbConfig::CONFIG[key.to_s] end else RbConfigPriorities.each do |key| ConfigMap[key.to_sym] = RbConfig::CONFIG[key] end end RubyGemsPackageVersion = VERSION end PK!,4  name_tuple.rbnu[# frozen_string_literal: true ## # # Represents a gem of name +name+ at +version+ of +platform+. These # wrap the data returned from the indexes. class Gem::NameTuple def initialize(name, version, platform = Gem::Platform::RUBY) @name = name @version = version platform &&= platform.to_s platform = Gem::Platform::RUBY if !platform || platform.empty? @platform = platform end attr_reader :name, :version, :platform ## # Turn an array of [name, version, platform] into an array of # NameTuple objects. def self.from_list(list) list.map {|t| new(*t) } end ## # Turn an array of NameTuple objects back into an array of # [name, version, platform] tuples. def self.to_basic(list) list.map(&:to_a) end ## # A null NameTuple, ie name=nil, version=0 def self.null new nil, Gem::Version.new(0), nil end ## # Returns the full name (name-version) of this Gem. Platform information is # included if it is not the default Ruby platform. This mimics the behavior # of Gem::Specification#full_name. def full_name case @platform when nil, "", Gem::Platform::RUBY "#{@name}-#{@version}" else "#{@name}-#{@version}-#{@platform}" end end ## # Indicate if this NameTuple matches the current platform. def match_platform? Gem::Platform.match_gem? @platform, @name end ## # Indicate if this NameTuple is for a prerelease version. def prerelease? @version.prerelease? end ## # Return the name that the gemspec file would be def spec_name "#{full_name}.gemspec" end ## # Convert back to the [name, version, platform] tuple def to_a [@name, @version, @platform] end alias_method :deconstruct, :to_a def deconstruct_keys(keys) { name: @name, version: @version, platform: @platform } end def inspect # :nodoc: "#" end alias_method :to_s, :inspect # :nodoc: def <=>(other) [@name, @version, Gem::Platform.sort_priority(@platform)] <=> [other.name, other.version, Gem::Platform.sort_priority(other.platform)] end include Comparable ## # Compare with +other+. Supports another NameTuple or an Array # in the [name, version, platform] format. def ==(other) case other when self.class @name == other.name && @version == other.version && @platform == other.platform when Array to_a == other else false end end alias_method :eql?, :== def hash to_a.hash end end PK!S7?7? command.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "vendored_optparse" require_relative "requirement" require_relative "user_interaction" ## # Base class for all Gem commands. When creating a new gem command, define # #initialize, #execute, #arguments, #defaults_str, #description and #usage # (as appropriate). See the above mentioned methods for details. # # A very good example to look at is Gem::Commands::ContentsCommand class Gem::Command include Gem::UserInteraction Gem::OptionParser.accept Symbol, &:to_sym ## # The name of the command. attr_reader :command ## # The options for the command. attr_reader :options ## # The default options for the command. attr_accessor :defaults ## # The name of the command for command-line invocation. attr_accessor :program_name ## # A short description of the command. attr_accessor :summary ## # Arguments used when building gems def self.build_args @build_args ||= [] end def self.build_args=(value) @build_args = value end def self.common_options @common_options ||= [] end def self.add_common_option(*args, &handler) Gem::Command.common_options << [args, handler] end def self.extra_args @extra_args ||= [] end def self.extra_args=(value) case value when Array @extra_args = value when String @extra_args = value.split(" ") end end ## # Return an array of extra arguments for the command. The extra arguments # come from the gem configuration file read at program startup. def self.specific_extra_args(cmd) specific_extra_args_hash[cmd] end ## # Add a list of extra arguments for the given command. +args+ may be an # array or a string to be split on white space. def self.add_specific_extra_args(cmd,args) args = args.split(/\s+/) if args.is_a? String specific_extra_args_hash[cmd] = args end ## # Accessor for the specific extra args hash (self initializing). def self.specific_extra_args_hash @specific_extra_args_hash ||= Hash.new do |h,k| h[k] = Array.new end end ## # Initializes a generic gem command named +command+. +summary+ is a short # description displayed in `gem help commands`. +defaults+ are the default # options. Defaults should be mirrored in #defaults_str, unless there are # none. # # When defining a new command subclass, use add_option to add command-line # switches. # # Unhandled arguments (gem names, files, etc.) are left in # options[:args]. def initialize(command, summary = nil, defaults = {}) @command = command @summary = summary @program_name = "gem #{command}" @defaults = defaults @options = defaults.dup @option_groups = Hash.new {|h,k| h[k] = [] } @deprecated_options = { command => {} } @parser = nil @when_invoked = nil end ## # True if +long+ begins with the characters from +short+. def begins?(long, short) return false if short.nil? long[0, short.length] == short end ## # Override to provide command handling. # # #options will be filled in with your parsed options, unparsed options will # be left in options[:args]. # # See also: #get_all_gem_names, #get_one_gem_name, # #get_one_optional_argument def execute raise Gem::Exception, "generic command has no actions" end ## # Display to the user that a gem couldn't be found and reasons why #-- def show_lookup_failure(gem_name, version, errors, suppress_suggestions = false, required_by = nil) gem = "'#{gem_name}' (#{version})" msg = String.new "Could not find a valid gem #{gem}" if errors && !errors.empty? msg << ", here is why:\n" errors.each {|x| msg << " #{x.wordy}\n" } else if required_by && gem != required_by msg << " (required by #{required_by}) in any repository" else msg << " in any repository" end end alert_error msg unless suppress_suggestions suggestions = Gem::SpecFetcher.fetcher.suggest_gems_from_name(gem_name, :latest, 10) unless suggestions.empty? alert_error "Possible alternatives: #{suggestions.join(", ")}" end end end ## # Get all gem names from the command line. def get_all_gem_names args = options[:args] if args.nil? || args.empty? raise Gem::CommandLineError, "Please specify at least one gem name (e.g. gem build GEMNAME)" end args.reject {|arg| arg.start_with?("-") } end ## # Get all [gem, version] from the command line. # # An argument in the form gem:ver is pull apart into the gen name and version, # respectively. def get_all_gem_names_and_versions get_all_gem_names.map do |name| extract_gem_name_and_version(name) end end def extract_gem_name_and_version(name) # :nodoc: if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name [$1, $2] else [name] end end ## # Get a single gem name from the command line. Fail if there is no gem name # or if there is more than one gem name given. def get_one_gem_name args = options[:args] if args.nil? || args.empty? raise Gem::CommandLineError, "Please specify a gem name on the command line (e.g. gem build GEMNAME)" end if args.size > 1 raise Gem::CommandLineError, "Too many gem names (#{args.join(", ")}); please specify only one" end args.first end ## # Get a single optional argument from the command line. If more than one # argument is given, return only the first. Return nil if none are given. def get_one_optional_argument args = options[:args] || [] args.first end ## # Override to provide details of the arguments a command takes. It should # return a left-justified string, one argument per line. # # For example: # # def usage # "#{program_name} FILE [FILE ...]" # end # # def arguments # "FILE name of file to find" # end def arguments "" end ## # Override to display the default values of the command options. (similar to # +arguments+, but displays the default values). # # For example: # # def defaults_str # --no-gems-first --no-all # end def defaults_str "" end ## # Override to display a longer description of what this command does. def description nil end ## # Override to display the usage for an individual gem command. # # The text "[options]" is automatically appended to the usage text. def usage program_name end ## # Display the help message for the command. def show_help parser.program_name = usage say parser end ## # Invoke the command with the given list of arguments. def invoke(*args) invoke_with_build_args args, nil end ## # Invoke the command with the given list of normal arguments # and additional build arguments. def invoke_with_build_args(args, build_args) handle_options args options[:build_args] = build_args if options[:silent] old_ui = ui self.ui = ui = Gem::SilentUI.new end if options[:help] show_help elsif @when_invoked @when_invoked.call options else execute end ensure if ui self.ui = old_ui ui.close end end ## # Call the given block when invoked. # # Normal command invocations just executes the +execute+ method of the # command. Specifying an invocation block allows the test methods to # override the normal action of a command to determine that it has been # invoked correctly. def when_invoked(&block) @when_invoked = block end ## # Add a command-line option and handler to the command. # # See Gem::OptionParser#make_switch for an explanation of +opts+. # # +handler+ will be called with two values, the value of the argument and # the options hash. # # If the first argument of add_option is a Symbol, it's used to group # options in output. See `gem help list` for an example. def add_option(*opts, &handler) # :yields: value, options group_name = Symbol === opts.first ? opts.shift : :options raise "Do not pass an empty string in opts" if opts.include?("") @option_groups[group_name] << [opts, handler] end ## # Remove previously defined command-line argument +name+. def remove_option(name) @option_groups.each do |_, option_list| option_list.reject! {|args, _| args.any? {|x| x.is_a?(String) && x =~ /^#{name}/ } } end end ## # Mark a command-line option as deprecated, and optionally specify a # deprecation horizon. # # Note that with the current implementation, every version of the option needs # to be explicitly deprecated, so to deprecate an option defined as # # add_option('-t', '--[no-]test', 'Set test mode') do |value, options| # # ... stuff ... # end # # you would need to explicitly add a call to `deprecate_option` for every # version of the option you want to deprecate, like # # deprecate_option('-t') # deprecate_option('--test') # deprecate_option('--no-test') def deprecate_option(name, version: nil, extra_msg: nil) @deprecated_options[command].merge!({ name => { "rg_version_to_expire" => version, "extra_msg" => extra_msg } }) end def check_deprecated_options(options) options.each do |option| next unless option_is_deprecated?(option) deprecation = @deprecated_options[command][option] version_to_expire = deprecation["rg_version_to_expire"] deprecate_option_msg = if version_to_expire "The \"#{option}\" option has been deprecated and will be removed in Rubygems #{version_to_expire}." else "The \"#{option}\" option has been deprecated and will be removed in future versions of Rubygems." end extra_msg = deprecation["extra_msg"] deprecate_option_msg += " #{extra_msg}" if extra_msg alert_warning(deprecate_option_msg) end end ## # Merge a set of command options with the set of default options (without # modifying the default option hash). def merge_options(new_options) @options = @defaults.clone new_options.each {|k,v| @options[k] = v } end ## # True if the command handles the given argument list. def handles?(args) parser.parse!(args.dup) true rescue StandardError false end ## # Handle the given list of arguments by parsing them and recording the # results. def handle_options(args) args = add_extra_args(args) check_deprecated_options(args) @options = Marshal.load Marshal.dump @defaults # deep copy parser.parse!(args) @options[:args] = args end ## # Adds extra args from ~/.gemrc def add_extra_args(args) result = [] s_extra = Gem::Command.specific_extra_args(@command) extra = Gem::Command.extra_args + s_extra until extra.empty? do ex = [] ex << extra.shift ex << extra.shift if /^[^-]/.match?(extra.first.to_s) result << ex if handles?(ex) end result.flatten! result.concat(args) result end def deprecated? false end private def option_is_deprecated?(option) @deprecated_options[command].key?(option) end def add_parser_description # :nodoc: return unless description formatted = description.split("\n\n").map do |chunk| wrap chunk, 80 - 4 end.join "\n" @parser.separator nil @parser.separator " Description:" formatted.each_line do |line| @parser.separator " #{line.rstrip}" end end def add_parser_options # :nodoc: @parser.separator nil regular_options = @option_groups.delete :options configure_options "", regular_options @option_groups.sort_by {|n,_| n.to_s }.each do |group_name, option_list| @parser.separator nil configure_options group_name, option_list end end ## # Adds a section with +title+ and +content+ to the parser help view. Used # for adding command arguments and default arguments. def add_parser_run_info(title, content) return if content.empty? @parser.separator nil @parser.separator " #{title}:" content.each_line do |line| @parser.separator " #{line.rstrip}" end end def add_parser_summary # :nodoc: return unless @summary @parser.separator nil @parser.separator " Summary:" wrap(@summary, 80 - 4).each_line do |line| @parser.separator " #{line.strip}" end end ## # Create on demand parser. def parser create_option_parser if @parser.nil? @parser end ## # Creates an option parser and fills it in with the help info for the # command. def create_option_parser @parser = Gem::OptionParser.new add_parser_options @parser.separator nil configure_options "Common", Gem::Command.common_options add_parser_run_info "Arguments", arguments add_parser_summary add_parser_description add_parser_run_info "Defaults", defaults_str end def configure_options(header, option_list) return if option_list.nil? || option_list.empty? header = header.to_s.empty? ? "" : "#{header} " @parser.separator " #{header}Options:" option_list.each do |args, handler| @parser.on(*args) do |value| handler.call(value, @options) end end @parser.separator "" end ## # Wraps +text+ to +width+ def wrap(text, width) # :doc: text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n") end # ---------------------------------------------------------------- # Add the options common to all commands. add_common_option("-h", "--help", "Get help on this command") do |_value, options| options[:help] = true end add_common_option("-V", "--[no-]verbose", "Set the verbose level of output") do |value, _options| # Set us to "really verbose" so the progress meter works if Gem.configuration.verbose && value Gem.configuration.verbose = 1 else Gem.configuration.verbose = value end end add_common_option("-q", "--quiet", "Silence command progress meter") do |_value, _options| Gem.configuration.verbose = false end add_common_option("--silent", "Silence RubyGems output") do |_value, options| options[:silent] = true end # Backtrace and config-file are added so they show up in the help # commands. Both options are actually handled before the other # options get parsed. add_common_option("--config-file FILE", "Use this config file instead of default") do end add_common_option("--backtrace", "Show stack backtrace on errors") do end add_common_option("--debug", "Turn on Ruby debugging") do end add_common_option("--norc", "Avoid loading any .gemrc file") do end # :stopdoc: HELP = <<-HELP RubyGems is a package manager for Ruby. Usage: gem -h/--help gem -v/--version gem [global options...] command [arguments...] [options...] Global options: -C PATH run as if gem was started in instead of the current working directory Examples: gem install rake gem list --local gem build package.gemspec gem push package-0.0.1.gem gem help install Further help: gem help commands list all 'gem' commands gem help examples show some examples of usage gem help gem_dependencies gem dependencies file guide gem help platforms gem platforms guide gem help show help on COMMAND (e.g. 'gem help install') Further information: https://guides.rubygems.org HELP # :startdoc: end ## # \Commands will be placed in this namespace module Gem::Commands end PK!`.Rs3_uri_signer.rbnu[# frozen_string_literal: true require_relative "openssl" require_relative "user_interaction" ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems # More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html class Gem::S3URISigner include Gem::UserInteraction class ConfigurationError < Gem::Exception def initialize(message) super message end def to_s # :nodoc: super.to_s end end class InstanceProfileError < Gem::Exception def initialize(message) super message end def to_s # :nodoc: super.to_s end end attr_accessor :uri attr_accessor :method def initialize(uri, method) @uri = uri @method = method end ## # Signs S3 URI using query-params according to the reference: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html def sign(expiration = 86_400) s3_config = fetch_s3_config current_time = Time.now.utc date_time = current_time.strftime("%Y%m%dT%H%M%SZ") date = date_time[0,8] credential_info = "#{date}/#{s3_config.region}/s3/aws4_request" canonical_host = "#{uri.host}.s3.#{s3_config.region}.amazonaws.com" query_params = generate_canonical_query_params(s3_config, date_time, credential_info, expiration) canonical_request = generate_canonical_request(canonical_host, query_params) string_to_sign = generate_string_to_sign(date_time, credential_info, canonical_request) signature = generate_signature(s3_config, date, string_to_sign) Gem::URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}") end private S3Config = Struct.new :access_key_id, :secret_access_key, :security_token, :region def generate_canonical_query_params(s3_config, date_time, credential_info, expiration) canonical_params = {} canonical_params["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256" canonical_params["X-Amz-Credential"] = "#{s3_config.access_key_id}/#{credential_info}" canonical_params["X-Amz-Date"] = date_time canonical_params["X-Amz-Expires"] = expiration.to_s canonical_params["X-Amz-SignedHeaders"] = "host" canonical_params["X-Amz-Security-Token"] = s3_config.security_token if s3_config.security_token # Sorting is required to generate proper signature canonical_params.sort.to_h.map do |key, value| "#{base64_uri_escape(key)}=#{base64_uri_escape(value)}" end.join("&") end def generate_canonical_request(canonical_host, query_params) [ method.upcase, uri.path, query_params, "host:#{canonical_host}", "", # empty params "host", "UNSIGNED-PAYLOAD", ].join("\n") end def generate_string_to_sign(date_time, credential_info, canonical_request) [ "AWS4-HMAC-SHA256", date_time, credential_info, OpenSSL::Digest::SHA256.hexdigest(canonical_request), ].join("\n") end def generate_signature(s3_config, date, string_to_sign) date_key = OpenSSL::HMAC.digest("sha256", "AWS4" + s3_config.secret_access_key, date) date_region_key = OpenSSL::HMAC.digest("sha256", date_key, s3_config.region) date_region_service_key = OpenSSL::HMAC.digest("sha256", date_region_key, "s3") signing_key = OpenSSL::HMAC.digest("sha256", date_region_service_key, "aws4_request") OpenSSL::HMAC.hexdigest("sha256", signing_key, string_to_sign) end ## # Extracts S3 configuration for S3 bucket def fetch_s3_config return S3Config.new(uri.user, uri.password, nil, "us-east-1") if uri.user && uri.password s3_source = Gem.configuration[:s3_source] || Gem.configuration["s3_source"] host = uri.host raise ConfigurationError.new("no s3_source key exists in .gemrc") unless s3_source auth = s3_source[host] || s3_source[host.to_sym] raise ConfigurationError.new("no key for host #{host} in s3_source in .gemrc") unless auth provider = auth[:provider] || auth["provider"] case provider when "env" id = ENV["AWS_ACCESS_KEY_ID"] secret = ENV["AWS_SECRET_ACCESS_KEY"] security_token = ENV["AWS_SESSION_TOKEN"] when "instance_profile" credentials = ec2_metadata_credentials_json id = credentials["AccessKeyId"] secret = credentials["SecretAccessKey"] security_token = credentials["Token"] else id = auth[:id] || auth["id"] secret = auth[:secret] || auth["secret"] security_token = auth[:security_token] || auth["security_token"] end raise ConfigurationError.new("s3_source for #{host} missing id or secret") unless id && secret region = auth[:region] || auth["region"] || "us-east-1" S3Config.new(id, secret, security_token, region) end def base64_uri_escape(str) str.gsub(%r{[\+/=\n]}, BASE64_URI_TRANSLATE) end def ec2_metadata_credentials_json require_relative "vendored_net_http" require_relative "request" require_relative "request/connection_pools" require "json" # First try V2 fallback to V1 res = nil begin res = ec2_metadata_credentials_imds_v2 rescue InstanceProfileError alert_warning "Unable to access ec2 credentials via IMDSv2, falling back to IMDSv1" res = ec2_metadata_credentials_imds_v1 end res end def ec2_metadata_credentials_imds_v2 token = ec2_metadata_token iam_info = ec2_metadata_request(EC2_IAM_INFO, token:) # Expected format: arn:aws:iam:::instance-profile/ role_name = iam_info["InstanceProfileArn"].split("/").last ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token:) end def ec2_metadata_credentials_imds_v1 iam_info = ec2_metadata_request(EC2_IAM_INFO, token: nil) # Expected format: arn:aws:iam:::instance-profile/ role_name = iam_info["InstanceProfileArn"].split("/").last ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token: nil) end def ec2_metadata_request(url, token:) request = ec2_iam_request(Gem::URI(url), Gem::Net::HTTP::Get) response = request.fetch do |req| if token req.add_field "X-aws-ec2-metadata-token", token end end case response when Gem::Net::HTTPOK then JSON.parse(response.body) else raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}") end end def ec2_metadata_token request = ec2_iam_request(Gem::URI(EC2_IAM_TOKEN), Gem::Net::HTTP::Put) response = request.fetch do |req| req.add_field "X-aws-ec2-metadata-token-ttl-seconds", 60 end case response when Gem::Net::HTTPOK then response.body else raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}") end end def ec2_iam_request(uri, verb) @request_pool ||= create_request_pool(uri) Gem::Request.new(uri, verb, nil, @request_pool) end def create_request_pool(uri) proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme)) certs = Gem::Request.get_cert_files Gem::Request::ConnectionPools.new(proxy_uri, certs).pool_for(uri) end BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze EC2_IAM_TOKEN = "http://169.254.169.254/latest/api/token" EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info" EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" end PK!vendor/timeout/lib/timeout.rbnu[# frozen_string_literal: true # Timeout long-running blocks # # == Synopsis # # require 'rubygems/vendor/timeout/lib/timeout' # status = Gem::Timeout.timeout(5) { # # Something that should be interrupted if it takes more than 5 seconds... # } # # == Description # # Gem::Timeout provides a way to auto-terminate a potentially long-running # operation if it hasn't finished in a fixed amount of time. # # == Copyright # # Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc. # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Gem::Timeout # The version VERSION = "0.4.4" # Internal error raised to when a timeout is triggered. class ExitException < Exception def exception(*) # :nodoc: self end end # Raised by Gem::Timeout.timeout when the block times out. class Error < RuntimeError def self.handle_timeout(message) # :nodoc: exc = ExitException.new(message) begin yield exc rescue ExitException => e raise new(message) if exc.equal?(e) raise end end end # :stopdoc: CONDVAR = ConditionVariable.new QUEUE = Queue.new QUEUE_MUTEX = Mutex.new TIMEOUT_THREAD_MUTEX = Mutex.new @timeout_thread = nil private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX class Request attr_reader :deadline def initialize(thread, timeout, exception_class, message) @thread = thread @deadline = GET_TIME.call(Process::CLOCK_MONOTONIC) + timeout @exception_class = exception_class @message = message @mutex = Mutex.new @done = false # protected by @mutex end def done? @mutex.synchronize do @done end end def expired?(now) now >= @deadline end def interrupt @mutex.synchronize do unless @done @thread.raise @exception_class, @message @done = true end end end def finished @mutex.synchronize do @done = true end end end private_constant :Request def self.create_timeout_thread watcher = Thread.new do requests = [] while true until QUEUE.empty? and !requests.empty? # wait to have at least one request req = QUEUE.pop requests << req unless req.done? end closest_deadline = requests.min_by(&:deadline).deadline now = 0.0 QUEUE_MUTEX.synchronize do while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty? CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now) end end requests.each do |req| req.interrupt if req.expired?(now) end requests.reject!(&:done?) end end ThreadGroup::Default.add(watcher) unless watcher.group.enclosed? watcher.name = "Gem::Timeout stdlib thread" watcher.thread_variable_set(:"\0__detached_thread__", true) watcher end private_class_method :create_timeout_thread def self.ensure_timeout_thread_created unless @timeout_thread and @timeout_thread.alive? # If the Mutex is already owned we are in a signal handler. # In that case, just return and let the main thread create the @timeout_thread. return if TIMEOUT_THREAD_MUTEX.owned? TIMEOUT_THREAD_MUTEX.synchronize do unless @timeout_thread and @timeout_thread.alive? @timeout_thread = create_timeout_thread end end end end # We keep a private reference so that time mocking libraries won't break # Gem::Timeout. GET_TIME = Process.method(:clock_gettime) private_constant :GET_TIME # :startdoc: # Perform an operation in a block, raising an error if it takes longer than # +sec+ seconds to complete. # # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number # or nil may be used, including Floats to specify fractional seconds. A # value of 0 or +nil+ will execute the block without any timeout. # Any negative number will raise an ArgumentError. # +klass+:: Exception Class to raise if the block fails to terminate # in +sec+ seconds. Omitting will use the default, Gem::Timeout::Error # +message+:: Error message to raise with Exception Class. # Omitting will use the default, "execution expired" # # Returns the result of the block *if* the block completed before # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. # # The exception thrown to terminate the given block cannot be rescued inside # the block unless +klass+ is given explicitly. However, the block can use # ensure to prevent the handling of the exception. For that reason, this # method cannot be relied on to enforce timeouts for untrusted blocks. # # If a scheduler is defined, it will be used to handle the timeout by invoking # Scheduler#timeout_after. # # Note that this is both a method of module Gem::Timeout, so you can include # Gem::Timeout into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Gem::Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec message ||= "execution expired" if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after) return scheduler.timeout_after(sec, klass || Error, message, &block) end Gem::Timeout.ensure_timeout_thread_created perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) QUEUE_MUTEX.synchronize do QUEUE << request CONDVAR.signal end begin return yield(sec) ensure request.finished end end if klass perform.call(klass) else Error.handle_timeout(message, &perform) end end module_function :timeout end PK!L;;#vendor/optparse/lib/optionparser.rbnu[# frozen_string_literal: false require_relative 'optparse' PK!S?'vendor/optparse/lib/optparse/version.rbnu[# frozen_string_literal: false # Gem::OptionParser internal utility class << Gem::OptionParser # # Shows version string in packages if Version is defined. # # +pkgs+:: package list # def show_version(*pkgs) progname = ARGV.options.program_name result = false show = proc do |klass, cname, version| str = "#{progname}" unless klass == ::Object and cname == :VERSION version = version.join(".") if Array === version str << ": #{klass}" unless klass == Object str << " version #{version}" end [:Release, :RELEASE].find do |rel| if klass.const_defined?(rel) str << " (#{klass.const_get(rel)})" end end puts str result = true end if pkgs.size == 1 and pkgs[0] == "all" self.search_const(::Object, /\AV(?:ERSION|ersion)\z/) do |klass, cname, version| unless cname[1] == ?e and klass.const_defined?(:Version) show.call(klass, cname.intern, version) end end else pkgs.each do |pkg| begin pkg = pkg.split(/::|\//).inject(::Object) {|m, c| m.const_get(c)} v = case when pkg.const_defined?(:Version) pkg.const_get(n = :Version) when pkg.const_defined?(:VERSION) pkg.const_get(n = :VERSION) else n = nil "unknown" end show.call(pkg, n, v) rescue NameError end end end result end # :stopdoc: def each_const(path, base = ::Object) path.split(/::|\//).inject(base) do |klass, name| raise NameError, path unless Module === klass klass.constants.grep(/#{name}/i) do |c| klass.const_defined?(c) or next klass.const_get(c) end end end def search_const(klass, name) klasses = [klass] while klass = klasses.shift klass.constants.each do |cname| klass.const_defined?(cname) or next const = klass.const_get(cname) yield klass, cname, const if name === cname klasses << const if Module === const and const != ::Object end end end # :startdoc: end PK!7&vendor/optparse/lib/optparse/kwargs.rbnu[# frozen_string_literal: true require_relative '../optparse' class Gem::OptionParser # :call-seq: # define_by_keywords(options, method, **params) # # :include: ../../doc/optparse/creates_option.rdoc # # Defines options which set in to _options_ for keyword parameters # of _method_. # # Parameters for each keywords are given as elements of _params_. # def define_by_keywords(options, method, **params) method.parameters.each do |type, name| case type when :key, :keyreq op, cl = *(type == :key ? %w"[ ]" : ["", ""]) define("--#{name}=#{op}#{name.upcase}#{cl}", *params[name]) do |o| options[name] = o end end end options end end PK!lV#vendor/optparse/lib/optparse/uri.rbnu[# frozen_string_literal: false # -*- ruby -*- require_relative '../optparse' require_relative '../../../uri/lib/uri' Gem::OptionParser.accept(Gem::URI) {|s,| Gem::URI.parse(s) if s} PK!"j*vendor/optparse/lib/optparse/shellwords.rbnu[# frozen_string_literal: false # -*- ruby -*- require 'shellwords' require_relative '../optparse' Gem::OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)} PK!O$vendor/optparse/lib/optparse/date.rbnu[# frozen_string_literal: false require_relative '../optparse' require 'date' Gem::OptionParser.accept(DateTime) do |s,| begin DateTime.parse(s) if s rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end end Gem::OptionParser.accept(Date) do |s,| begin Date.parse(s) if s rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end end PK!.U$vendor/optparse/lib/optparse/time.rbnu[# frozen_string_literal: false require_relative '../optparse' require 'time' Gem::OptionParser.accept(Time) do |s,| begin (Time.httpdate(s) rescue Time.parse(s)) if s rescue raise Gem::OptionParser::InvalidArgument, s end end PK!ua"vendor/optparse/lib/optparse/ac.rbnu[# frozen_string_literal: false require_relative '../optparse' # # autoconf-like options. # class Gem::OptionParser::AC < Gem::OptionParser # :stopdoc: private def _check_ac_args(name, block) unless /\A\w[-\w]*\z/ =~ name raise ArgumentError, name end unless block raise ArgumentError, "no block given", ParseError.filter_backtrace(caller) end end ARG_CONV = proc {|val| val.nil? ? true : val} private_constant :ARG_CONV def _ac_arg_enable(prefix, name, help_string, block) _check_ac_args(name, block) sdesc = [] ldesc = ["--#{prefix}-#{name}"] desc = [help_string] q = name.downcase ac_block = proc {|val| block.call(ARG_CONV.call(val))} enable = Switch::PlacedArgument.new(nil, ARG_CONV, sdesc, ldesc, nil, desc, ac_block) disable = Switch::NoArgument.new(nil, proc {false}, sdesc, ldesc, nil, desc, ac_block) top.append(enable, [], ["enable-" + q], disable, ['disable-' + q]) enable end # :startdoc: public # Define --enable / --disable style option # # Appears as --enable-name in help message. def ac_arg_enable(name, help_string, &block) _ac_arg_enable("enable", name, help_string, block) end # Define --enable / --disable style option # # Appears as --disable-name in help message. def ac_arg_disable(name, help_string, &block) _ac_arg_enable("disable", name, help_string, block) end # Define --with / --without style option # # Appears as --with-name in help message. def ac_arg_with(name, help_string, &block) _check_ac_args(name, block) sdesc = [] ldesc = ["--with-#{name}"] desc = [help_string] q = name.downcase with = Switch::PlacedArgument.new(*search(:atype, String), sdesc, ldesc, nil, desc, block) without = Switch::NoArgument.new(nil, proc {}, sdesc, ldesc, nil, desc, block) top.append(with, [], ["with-" + q], without, ['without-' + q]) with end end PK!B Y??vendor/optparse/lib/optparse.rbnu[# frozen_string_literal: true # # optparse.rb - command-line option analysis with the Gem::OptionParser class. # # Author:: Nobu Nakada # Documentation:: Nobu Nakada and Gavin Sinclair. # # See Gem::OptionParser for documentation. # require 'set' unless defined?(Set) #-- # == Developer Documentation (not for RDoc output) # # === Class tree # # - Gem::OptionParser:: front end # - Gem::OptionParser::Switch:: each switches # - Gem::OptionParser::List:: options list # - Gem::OptionParser::ParseError:: errors on parsing # - Gem::OptionParser::AmbiguousOption # - Gem::OptionParser::NeedlessArgument # - Gem::OptionParser::MissingArgument # - Gem::OptionParser::InvalidOption # - Gem::OptionParser::InvalidArgument # - Gem::OptionParser::AmbiguousArgument # # === Object relationship diagram # # +--------------+ # | Gem::OptionParser |<>-----+ # +--------------+ | +--------+ # | ,-| Switch | # on_head -------->+---------------+ / +--------+ # accept/reject -->| List |<|>- # | |<|>- +----------+ # on ------------->+---------------+ `-| argument | # : : | class | # +---------------+ |==========| # on_tail -------->| | |pattern | # +---------------+ |----------| # Gem::OptionParser.accept ->| DefaultList | |converter | # reject |(shared between| +----------+ # | all instances)| # +---------------+ # #++ # # == Gem::OptionParser # # === New to +Gem::OptionParser+? # # See the {Tutorial}[optparse/tutorial.rdoc]. # # === Introduction # # Gem::OptionParser is a class for command-line option analysis. It is much more # advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented # solution. # # === Features # # 1. The argument specification and the code to handle it are written in the # same place. # 2. It can output an option summary; you don't need to maintain this string # separately. # 3. Optional and mandatory arguments are specified very gracefully. # 4. Arguments can be automatically converted to a specified class. # 5. Arguments can be restricted to a certain set. # # All of these features are demonstrated in the examples below. See # #make_switch for full documentation. # # === Minimal example # # require 'rubygems/vendor/optparse/lib/optparse' # # options = {} # Gem::OptionParser.new do |parser| # parser.banner = "Usage: example.rb [options]" # # parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| # options[:verbose] = v # end # end.parse! # # p options # p ARGV # # === Generating Help # # Gem::OptionParser can be used to automatically generate help for the commands you # write: # # require 'rubygems/vendor/optparse/lib/optparse' # # Options = Struct.new(:name) # # class Parser # def self.parse(options) # args = Options.new("world") # # opt_parser = Gem::OptionParser.new do |parser| # parser.banner = "Usage: example.rb [options]" # # parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| # args.name = n # end # # parser.on("-h", "--help", "Prints this help") do # puts parser # exit # end # end # # opt_parser.parse!(options) # return args # end # end # options = Parser.parse %w[--help] # # #=> # # Usage: example.rb [options] # # -n, --name=NAME Name to say hello to # # -h, --help Prints this help # # === Required Arguments # # For options that require an argument, option specification strings may include an # option name in all caps. If an option is used without the required argument, # an exception will be raised. # # require 'rubygems/vendor/optparse/lib/optparse' # # options = {} # Gem::OptionParser.new do |parser| # parser.on("-r", "--require LIBRARY", # "Require the LIBRARY before executing your script") do |lib| # puts "You required #{lib}!" # end # end.parse! # # Used: # # $ ruby optparse-test.rb -r # optparse-test.rb:9:in '
': missing argument: -r (Gem::OptionParser::MissingArgument) # $ ruby optparse-test.rb -r my-library # You required my-library! # # === Type Coercion # # Gem::OptionParser supports the ability to coerce command line arguments # into objects for us. # # Gem::OptionParser comes with a few ready-to-use kinds of type # coercion. They are: # # - Date -- Anything accepted by +Date.parse+ (need to require +optparse/date+) # - DateTime -- Anything accepted by +DateTime.parse+ (need to require +optparse/date+) # - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+ (need to require +optparse/time+) # - URI -- Anything accepted by +Gem::URI.parse+ (need to require +optparse/uri+) # - Shellwords -- Anything accepted by +Shellwords.shellwords+ (need to require +optparse/shellwords+) # - String -- Any non-empty string # - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040) # - Float -- Any float. (e.g. 10, 3.14, -100E+13) # - Numeric -- Any integer, float, or rational (1, 3.4, 1/3) # - DecimalInteger -- Like +Integer+, but no octal format. # - OctalInteger -- Like +Integer+, but no decimal format. # - DecimalNumeric -- Decimal integer or float. # - TrueClass -- Accepts '+, yes, true, -, no, false' and # defaults as +true+ # - FalseClass -- Same as +TrueClass+, but defaults to +false+ # - Array -- Strings separated by ',' (e.g. 1,2,3) # - Regexp -- Regular expressions. Also includes options. # # We can also add our own coercions, which we will cover below. # # ==== Using Built-in Conversions # # As an example, the built-in +Time+ conversion is used. The other built-in # conversions behave in the same way. # Gem::OptionParser will attempt to parse the argument # as a +Time+. If it succeeds, that time will be passed to the # handler block. Otherwise, an exception will be raised. # # require 'rubygems/vendor/optparse/lib/optparse' # require 'rubygems/vendor/optparse/lib/optparse/time' # Gem::OptionParser.new do |parser| # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| # p time # end # end.parse! # # Used: # # $ ruby optparse-test.rb -t nonsense # ... invalid argument: -t nonsense (Gem::OptionParser::InvalidArgument) # $ ruby optparse-test.rb -t 10-11-12 # 2010-11-12 00:00:00 -0500 # $ ruby optparse-test.rb -t 9:30 # 2014-08-13 09:30:00 -0400 # # ==== Creating Custom Conversions # # The +accept+ method on Gem::OptionParser may be used to create converters. # It specifies which conversion block to call whenever a class is specified. # The example below uses it to fetch a +User+ object before the +on+ handler receives it. # # require 'rubygems/vendor/optparse/lib/optparse' # # User = Struct.new(:id, :name) # # def find_user id # not_found = ->{ raise "No User Found for id #{id}" } # [ User.new(1, "Sam"), # User.new(2, "Gandalf") ].find(not_found) do |u| # u.id == id # end # end # # op = Gem::OptionParser.new # op.accept(User) do |user_id| # find_user user_id.to_i # end # # op.on("--user ID", User) do |user| # puts user # end # # op.parse! # # Used: # # $ ruby optparse-test.rb --user 1 # # # $ ruby optparse-test.rb --user 2 # # # $ ruby optparse-test.rb --user 3 # optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError) # # === Store options to a Hash # # The +into+ option of +order+, +parse+ and so on methods stores command line options into a Hash. # # require 'rubygems/vendor/optparse/lib/optparse' # # options = {} # Gem::OptionParser.new do |parser| # parser.on('-a') # parser.on('-b NUM', Integer) # parser.on('-v', '--verbose') # end.parse!(into: options) # # p options # # Used: # # $ ruby optparse-test.rb -a # {:a=>true} # $ ruby optparse-test.rb -a -v # {:a=>true, :verbose=>true} # $ ruby optparse-test.rb -a -b 100 # {:a=>true, :b=>100} # # === Complete example # # The following example is a complete Ruby program. You can run it and see the # effect of specifying various options. This is probably the best way to learn # the features of +optparse+. # # require 'rubygems/vendor/optparse/lib/optparse' # require 'rubygems/vendor/optparse/lib/optparse/time' # require 'ostruct' # require 'pp' # # class OptparseExample # Version = '1.0.0' # # CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] # CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } # # class ScriptOptions # attr_accessor :library, :inplace, :encoding, :transfer_type, # :verbose, :extension, :delay, :time, :record_separator, # :list # # def initialize # self.library = [] # self.inplace = false # self.encoding = "utf8" # self.transfer_type = :auto # self.verbose = false # end # # def define_options(parser) # parser.banner = "Usage: example.rb [options]" # parser.separator "" # parser.separator "Specific options:" # # # add additional options # perform_inplace_option(parser) # delay_execution_option(parser) # execute_at_time_option(parser) # specify_record_separator_option(parser) # list_example_option(parser) # specify_encoding_option(parser) # optional_option_argument_with_keyword_completion_option(parser) # boolean_verbose_option(parser) # # parser.separator "" # parser.separator "Common options:" # # No argument, shows at tail. This will print an options summary. # # Try it and see! # parser.on_tail("-h", "--help", "Show this message") do # puts parser # exit # end # # Another typical switch to print the version. # parser.on_tail("--version", "Show version") do # puts Version # exit # end # end # # def perform_inplace_option(parser) # # Specifies an optional option argument # parser.on("-i", "--inplace [EXTENSION]", # "Edit ARGV files in place", # "(make backup if EXTENSION supplied)") do |ext| # self.inplace = true # self.extension = ext || '' # self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot. # end # end # # def delay_execution_option(parser) # # Cast 'delay' argument to a Float. # parser.on("--delay N", Float, "Delay N seconds before executing") do |n| # self.delay = n # end # end # # def execute_at_time_option(parser) # # Cast 'time' argument to a Time object. # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| # self.time = time # end # end # # def specify_record_separator_option(parser) # # Cast to octal integer. # parser.on("-F", "--irs [OCTAL]", Gem::OptionParser::OctalInteger, # "Specify record separator (default \\0)") do |rs| # self.record_separator = rs # end # end # # def list_example_option(parser) # # List of arguments. # parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| # self.list = list # end # end # # def specify_encoding_option(parser) # # Keyword completion. We are specifying a specific set of arguments (CODES # # and CODE_ALIASES - notice the latter is a Hash), and the user may provide # # the shortest unambiguous text. # code_list = (CODE_ALIASES.keys + CODES).join(', ') # parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding", # "(#{code_list})") do |encoding| # self.encoding = encoding # end # end # # def optional_option_argument_with_keyword_completion_option(parser) # # Optional '--type' option argument with keyword completion. # parser.on("--type [TYPE]", [:text, :binary, :auto], # "Select transfer type (text, binary, auto)") do |t| # self.transfer_type = t # end # end # # def boolean_verbose_option(parser) # # Boolean switch. # parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| # self.verbose = v # end # end # end # # # # # Return a structure describing the options. # # # def parse(args) # # The options specified on the command line will be collected in # # *options*. # # @options = ScriptOptions.new # @args = Gem::OptionParser.new do |parser| # @options.define_options(parser) # parser.parse!(args) # end # @options # end # # attr_reader :parser, :options # end # class OptparseExample # # example = OptparseExample.new # options = example.parse(ARGV) # pp options # example.options # pp ARGV # # === Shell Completion # # For modern shells (e.g. bash, zsh, etc.), you can use shell # completion for command line options. # # === Further documentation # # The above examples, along with the accompanying # {Tutorial}[optparse/tutorial.rdoc], # should be enough to learn how to use this class. # If you have any questions, file a ticket at http://bugs.ruby-lang.org. # class Gem::OptionParser # The version string VERSION = "0.8.0" Version = VERSION # for compatibility # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze # :startdoc: # # Keyword completion module. This allows partial arguments to be specified # and resolved against a list of acceptable values. # module Completion # :nodoc: def self.regexp(key, icase) Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) end def self.candidate(key, icase = false, pat = nil, &block) pat ||= Completion.regexp(key, icase) candidates = [] block.call do |k, *v| (if Regexp === k kn = "" k === key else kn = defined?(k.id2name) ? k.id2name : k pat === kn end) or next v << k if v.empty? candidates << [k, v, kn] end candidates end def self.completable?(key) String.try_convert(key) or defined?(key.id2name) end def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end public def complete(key, icase = false, pat = nil) candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} if candidates.size == 1 canon, sw, * = candidates[0] elsif candidates.size > 1 canon, sw, cn = candidates.shift candidates.each do |k, v, kn| next if sw == v if String === cn and String === kn if cn.rindex(kn, 0) canon, sw, cn = k, v, kn next elsif kn.rindex(cn, 0) next end end throw :ambiguous, key end end if canon block_given? or return key, *sw yield(key, *sw) end end def convert(opt = nil, val = nil, *) val end end # # Map from option/keyword string to object with completion. # class OptionMap < Hash include Completion end # # Individual switch class. Not important to the user. # # Defined within Switch are several Switch-derived classes: NoArgument, # RequiredArgument, etc. # class Switch # :nodoc: attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block # # Guesses argument style from +arg+. Returns corresponding # Gem::OptionParser::Switch class (OptionalArgument, etc.). # def self.guess(arg) case arg when "" t = self when /\A=?\[/ t = Switch::OptionalArgument when /\A\s+\[/ t = Switch::PlacedArgument else t = Switch::RequiredArgument end self >= t or incompatible_argument_styles(arg, t) t end def self.incompatible_argument_styles(arg, t) raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}", ParseError.filter_backtrace(caller(2))) end def self.pattern NilClass end def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, desc = ([] if short or long), block = nil, values = nil, &_block) raise if Array === pattern block ||= _block @pattern, @conv, @short, @long, @arg, @desc, @block, @values = pattern, conv, short, long, arg, desc, block, values end # # Parses +arg+ and returns rest of +arg+ and matched portion to the # argument pattern. Yields when the pattern doesn't match substring. # def parse_arg(arg) # :nodoc: pattern or return nil, [arg] unless m = pattern.match(arg) yield(InvalidArgument, arg) return arg, [] end if String === m m = [s = m] else m = m.to_a s = m[0] return nil, m unless String === s end raise InvalidArgument, arg unless arg.rindex(s, 0) return nil, m if s.length == arg.length yield(InvalidArgument, arg) # didn't match whole arg return arg[s.length..-1], m end private :parse_arg # # Parses argument, converts and returns +arg+, +block+ and result of # conversion. Yields at semi-error condition instead of raising an # exception. # def conv_arg(arg, val = []) # :nodoc: v, = *val if conv val = conv.call(*val) else val = proc {|v| v}.call(*val) end if @values @values.include?(val) or raise InvalidArgument, v end return arg, block, val end private :conv_arg # # Produces the summary text. Each line of the summary is yielded to the # block (without newline). # # +sdone+:: Already summarized short style options keyed hash. # +ldone+:: Already summarized long style options keyed hash. # +width+:: Width of left side (option part). In other words, the right # side (description part) starts after +width+ columns. # +max+:: Maximum width of left side -> the options are filled within # +max+ columns. # +indent+:: Prefix string indents all summarized lines. # def summarize(sdone = {}, ldone = {}, width = 1, max = width - 1, indent = "") sopts, lopts = [], [], nil @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long return if sopts.empty? and lopts.empty? # completely hidden left = [sopts.join(', ')] right = desc.dup while s = lopts.shift l = left[-1].length + s.length l += arg.length if left.size == 1 && arg l < max or sopts.empty? or left << +'' left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s end if arg left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg) end mlen = left.collect {|ss| ss.length}.max.to_i while mlen > width and l = left.shift mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen if l.length < width and (r = right[0]) and !r.empty? l = l.to_s.ljust(width) + ' ' + r right.shift end yield(indent + l) end while begin l = left.shift; r = right.shift; l or r end l = l.to_s.ljust(width) + ' ' + r if r and !r.empty? yield(indent + l) end self end def add_banner(to) # :nodoc: unless @short or @long s = desc.join to << " [" + s + "]..." unless s.empty? end to end def match_nonswitch?(str) # :nodoc: @pattern =~ str unless @short or @long end # # Main name of the switch. # def switch_name (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '') end def compsys(sdone, ldone) # :nodoc: sopts, lopts = [], [] @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long return if sopts.empty? and lopts.empty? # completely hidden (sopts+lopts).each do |opt| # "(-x -c -r)-l[left justify]" if /\A--\[no-\](.+)$/ =~ opt o = $1 yield("--#{o}", desc.join("")) yield("--no-#{o}", desc.join("")) else yield("#{opt}", desc.join("")) end end end def pretty_print_contents(q) # :nodoc: if @block q.text ":" + @block.source_location.join(":") + ":" first = false else first = true end [@short, @long].each do |list| list.each do |opt| if first q.text ":" first = false end q.breakable q.text opt end end end def pretty_print(q) # :nodoc: q.object_group(self) {pretty_print_contents(q)} end def omitted_argument(val) # :nodoc: val.pop if val.size == 3 and val.last.nil? val end # # Switch that takes no arguments. # class NoArgument < self # # Raises an exception if any arguments given. # def parse(arg, argv) yield(NeedlessArgument, arg) if arg conv_arg(arg) end def self.incompatible_argument_styles(*) # :nodoc: end def self.pattern # :nodoc: Object end def pretty_head # :nodoc: "NoArgument" end end # # Switch that takes an argument. # class RequiredArgument < self # # Raises an exception if argument is not present. # def parse(arg, argv, &_) unless arg raise MissingArgument if argv.empty? arg = argv.shift end conv_arg(*parse_arg(arg, &method(:raise))) end def pretty_head # :nodoc: "Required" end end # # Switch that can omit argument. # class OptionalArgument < self # # Parses argument if given, or uses default value. # def parse(arg, argv, &error) if arg conv_arg(*parse_arg(arg, &error)) else omitted_argument conv_arg(arg) end end def pretty_head # :nodoc: "Optional" end end # # Switch that takes an argument, which does not begin with '-' or is '-'. # class PlacedArgument < self # # Returns nil if argument is not present or begins with '-' and is not '-'. # def parse(arg, argv, &error) if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0])) return nil, block end opt = (val = parse_arg(val, &error))[1] val = conv_arg(*val) if opt and !arg argv.shift else omitted_argument val val[0] = nil end val end def pretty_head # :nodoc: "Placed" end end end # # Simple option list providing mapping from short and/or long option # string to Gem::OptionParser::Switch and mapping from acceptable argument to # matching pattern and converter pair. Also provides summary feature. # class List # :nodoc: # Map from acceptable argument types to pattern and converter pairs. attr_reader :atype # Map from short style option switches to actual switch objects. attr_reader :short # Map from long style option switches to actual switch objects. attr_reader :long # List of all switches and summary string. attr_reader :list # # Just initializes all instance variables. # def initialize @atype = {} @short = OptionMap.new @long = OptionMap.new @list = [] end def pretty_print(q) # :nodoc: q.group(1, "(", ")") do @list.each do |sw| next unless Switch === sw q.group(1, "(" + sw.pretty_head, ")") do sw.pretty_print_contents(q) end end end end # # See Gem::OptionParser.accept. # def accept(t, pat = /.*/m, &block) if pat pat.respond_to?(:match) or raise TypeError, "has no 'match'", ParseError.filter_backtrace(caller(2)) else pat = t if t.respond_to?(:match) end unless block block = pat.method(:convert).to_proc if pat.respond_to?(:convert) end @atype[t] = [pat, block] end # # See Gem::OptionParser.reject. # def reject(t) @atype.delete(t) end # # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+. # # +sw+:: Gem::OptionParser::Switch instance to be added. # +sopts+:: Short style option list. # +lopts+:: Long style option list. # +nlopts+:: Negated long style options list. # def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: sopts.each {|o| @short[o] = sw} if sopts lopts.each {|o| @long[o] = sw} if lopts nlopts.each {|o| @long[o] = nsw} if nsw and nlopts used = @short.invert.update(@long.invert) @list.delete_if {|o| Switch === o and !used[o]} end private :update # # Inserts +switch+ at the head of the list, and associates short, long # and negated long options. Arguments are: # # +switch+:: Gem::OptionParser::Switch instance to be inserted. # +short_opts+:: List of short style options. # +long_opts+:: List of long style options. # +nolong_opts+:: List of long style options with "no-" prefix. # # prepend(switch, short_opts, long_opts, nolong_opts) # def prepend(*args) update(*args) @list.unshift(args[0]) end # # Appends +switch+ at the tail of the list, and associates short, long # and negated long options. Arguments are: # # +switch+:: Gem::OptionParser::Switch instance to be inserted. # +short_opts+:: List of short style options. # +long_opts+:: List of long style options. # +nolong_opts+:: List of long style options with "no-" prefix. # # append(switch, short_opts, long_opts, nolong_opts) # def append(*args) update(*args) @list.push(args[0]) end # # Searches +key+ in +id+ list. The result is returned or yielded if a # block is given. If it isn't found, nil is returned. # def search(id, key) if list = __send__(id) val = list.fetch(key) {return nil} block_given? ? yield(val) : val end end # # Searches list +id+ for +opt+ and the optional patterns for completion # +pat+. If +icase+ is true, the search is case insensitive. The result # is returned or yielded if a block is given. If it isn't found, nil is # returned. # def complete(id, opt, icase = false, *pat, &block) __send__(id).complete(opt, icase, *pat, &block) end def get_candidates(id) yield __send__(id).keys end # # Iterates over each option, passing the option to the +block+. # def each_option(&block) list.each(&block) end # # Creates the summary table, passing each line to the +block+ (without # newline). The arguments +args+ are passed along to the summarize # method which is called on every option. # def summarize(*args, &block) sum = [] list.reverse_each do |opt| if opt.respond_to?(:summarize) # perhaps Gem::OptionParser::Switch s = [] opt.summarize(*args) {|l| s << l} sum.concat(s.reverse) elsif !opt or opt.empty? sum << "" elsif opt.respond_to?(:each_line) sum.concat([*opt.each_line].reverse) else sum.concat([*opt.each].reverse) end end sum.reverse_each(&block) end def add_banner(to) # :nodoc: list.each do |opt| if opt.respond_to?(:add_banner) opt.add_banner(to) end end to end def compsys(*args, &block) # :nodoc: list.each do |opt| if opt.respond_to?(:compsys) opt.compsys(*args, &block) end end end end # # Hash with completion search feature. See Gem::OptionParser::Completion. # class CompletingHash < Hash include Completion # # Completion for hash key. # def match(key) *values = fetch(key) { raise AmbiguousArgument, catch(:ambiguous) {return complete(key)} } return key, *values end end # :stopdoc: # # Enumeration of acceptable argument styles. Possible values are: # # NO_ARGUMENT:: The switch takes no arguments. (:NONE) # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED) # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL) # # Use like --switch=argument (long style) or -Xargument (short style). For # short style, only portion matched to argument pattern is treated as # argument. # ArgumentStyle = {} NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument} RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument} OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument} ArgumentStyle.freeze # # Switches common used such as '--', and also provides default # argument classes # DefaultList = List.new DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} COMPSYS_HEADER = <<'XXX' # :nodoc: typeset -A opt_args local context state line _arguments -s -S \ XXX def compsys(to, name = File.basename($0)) # :nodoc: to << "#compdef #{name}\n" to << COMPSYS_HEADER visit(:compsys, {}, {}) {|o, d| to << %Q[ "#{o}[#{d.gsub(/[\\\"\[\]]/, '\\\\\&')}]" \\\n] } to << " '*:file:_files' && return 0\n" end def help_exit if $stdout.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) less = ENV["LESS"] args = [{"LESS" => "#{less} -Fe"}, pager, "w"] print = proc do |f| f.puts help rescue Errno::EPIPE # pager terminated end if Process.respond_to?(:fork) and false IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)} # unreachable end IO.popen(*args, &print) else puts help end exit end # # Default options for ARGV, which never appear in option summary. # Officious = {} # # --help # Shows option summary. # Officious['help'] = proc do |parser| Switch::NoArgument.new do |arg| parser.help_exit end end # # --*-completion-bash=WORD # Shows candidates for command line completion. # Officious['*-completion-bash'] = proc do |parser| Switch::RequiredArgument.new do |arg| puts parser.candidate(arg) exit end end # # --*-completion-zsh[=NAME:FILE] # Creates zsh completion file. # Officious['*-completion-zsh'] = proc do |parser| Switch::OptionalArgument.new do |arg| parser.compsys($stdout, arg) exit end end # # --version # Shows version string if Version is defined. # Officious['version'] = proc do |parser| Switch::OptionalArgument.new do |pkg| if pkg begin require_relative 'optparse/version' rescue LoadError else show_version(*pkg.split(/,/)) or abort("#{parser.program_name}: no version found in package #{pkg}") exit end end v = parser.ver or abort("#{parser.program_name}: version unknown") puts v exit end end # :startdoc: # # Class methods # # # Initializes a new instance and evaluates the optional block in context # of the instance. Arguments +args+ are passed to #new, see there for # description of parameters. # # This method is *deprecated*, its behavior corresponds to the older #new # method. # def self.with(*args, &block) opts = new(*args) opts.instance_eval(&block) opts end # # Returns an incremented value of +default+ according to +arg+. # def self.inc(arg, default = nil) case arg when Integer arg.nonzero? when nil default.to_i + 1 end end # # See self.inc # def inc(*args) self.class.inc(*args) end # # Initializes the instance and yields itself if called with a block. # # +banner+:: Banner message. # +width+:: Summary width. # +indent+:: Summary indent. # def initialize(banner = nil, width = 32, indent = ' ' * 4) @stack = [DefaultList, List.new, List.new] @program_name = nil @banner = banner @summary_width = width @summary_indent = indent @default_argv = ARGV @require_exact = false @raise_unknown = true add_officious yield self if block_given? end def add_officious # :nodoc: list = base() Officious.each do |opt, block| list.long[opt] ||= block.call(self) end end # # Terminates option parsing. Optional parameter +arg+ is a string pushed # back to be the first non-option argument. # def terminate(arg = nil) self.class.terminate(arg) end # # See #terminate. # def self.terminate(arg = nil) throw :terminate, arg end @stack = [DefaultList] # # Returns the global top option list. # # Do not use directly. # def self.top() DefaultList end # # Directs to accept specified class +t+. The argument string is passed to # the block in which it should be converted to the desired class. # # +t+:: Argument class specifier, any object including Class. # +pat+:: Pattern for argument, defaults to +t+ if it responds to match. # # accept(t, pat, &block) # def accept(*args, &blk) top.accept(*args, &blk) end # # See #accept. # def self.accept(*args, &blk) top.accept(*args, &blk) end # # Directs to reject specified class argument. # # +type+:: Argument class specifier, any object including Class. # # reject(type) # def reject(*args, &blk) top.reject(*args, &blk) end # # See #reject. # def self.reject(*args, &blk) top.reject(*args, &blk) end # # Instance methods # # Heading banner preceding summary. attr_writer :banner # Program name to be emitted in error message and default banner, # defaults to $0. attr_writer :program_name # Width for option list portion of summary. Must be Numeric. attr_accessor :summary_width # Indentation for summary. Must be String (or have + String method). attr_accessor :summary_indent # Strings to be parsed in default. attr_accessor :default_argv # Whether to require that options match exactly (disallows providing # abbreviated long option as short option). attr_accessor :require_exact # Whether to raise at unknown option. attr_accessor :raise_unknown # # Heading banner preceding summary. # def banner unless @banner @banner = +"Usage: #{program_name} [options]" visit(:add_banner, @banner) end @banner end # # Program name to be emitted in error message and default banner, defaults # to $0. # def program_name @program_name || strip_ext(File.basename($0)) end private def strip_ext(name) # :nodoc: exts = /#{ require "rbconfig" Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) }\z/o name.sub(exts, "") end # for experimental cascading :-) alias set_banner banner= alias set_program_name program_name= alias set_summary_width summary_width= alias set_summary_indent summary_indent= # Version attr_writer :version # Release code attr_writer :release # # Version # def version (defined?(@version) && @version) || (defined?(::Version) && ::Version) end # # Release code # def release (defined?(@release) && @release) || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE) end # # Returns version string from program_name, version and release. # def ver if v = version str = +"#{program_name} #{[v].join('.')}" str << " (#{v})" if v = release str end end # # Shows warning message with the program name # # +mesg+:: Message, defaulted to +$!+. # # See Kernel#warn. # def warn(mesg = $!) super("#{program_name}: #{mesg}") end # # Shows message with the program name then aborts. # # +mesg+:: Message, defaulted to +$!+. # # See Kernel#abort. # def abort(mesg = $!) super("#{program_name}: #{mesg}") end # # Subject of #on / #on_head, #accept / #reject # def top @stack[-1] end # # Subject of #on_tail. # def base @stack[1] end # # Pushes a new List. # # If a block is given, yields +self+ and returns the result of the # block, otherwise returns +self+. # def new @stack.push(List.new) if block_given? yield self else self end end # # Removes the last List. # def remove @stack.pop end # # Puts option summary into +to+ and returns +to+. Yields each line if # a block is given. # # +to+:: Output destination, which must have method <<. Defaults to []. # +width+:: Width of left side, defaults to @summary_width. # +max+:: Maximum length allowed for left side, defaults to +width+ - 1. # +indent+:: Indentation, defaults to @summary_indent. # def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) nl = "\n" blk ||= proc {|l| to << (l.index(nl, -1) ? l : l + nl)} visit(:summarize, {}, {}, width, max, indent, &blk) to end # # Returns option summary string. # def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end alias to_s help def pretty_print(q) # :nodoc: q.object_group(self) do first = true if @stack.size > 2 @stack.each_with_index do |s, i| next if i < 2 next if s.list.empty? if first first = false q.text ":" end q.breakable s.pretty_print(q) end end end end def inspect # :nodoc: require 'pp' pretty_print_inspect end # # Returns option summary list. # def to_a; summarize("#{banner}".split(/^/)) end # # Checks if an argument is given twice, in which case an ArgumentError is # raised. Called from Gem::OptionParser#switch only. # # +obj+:: New argument. # +prv+:: Previously specified argument. # +msg+:: Exception message. # def notwice(obj, prv, msg) # :nodoc: unless !prv or prv == obj raise(ArgumentError, "argument #{msg} given twice: #{obj}", ParseError.filter_backtrace(caller(2))) end obj end private :notwice SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: # :call-seq: # make_switch(params, block = nil) # # :include: ../doc/optparse/creates_option.rdoc # def make_switch(opts, block = nil) short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], [] ldesc, sdesc, desc, arg = [], [], [] default_style = Switch::NoArgument default_pattern = nil klass = nil q, a = nil has_arg = false values = nil opts.each do |o| # argument class next if search(:atype, o) do |pat, c| klass = notwice(o, klass, 'type') if not_style and not_style != Switch::NoArgument not_pattern, not_conv = pat, c else default_pattern, conv = pat, c end end # directly specified pattern(any object possible to match) if !Completion.completable?(o) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc else conv = SPLAT_PROC end next end # anything others case o when Proc, Method block = notwice(o, block, 'block') when Array, Hash, Set if Array === o o, v = o.partition {|v,| Completion.completable?(v)} values = notwice(v, values, 'values') unless v.empty? next if o.empty? end case pattern when CompletingHash when nil pattern = CompletingHash.new conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert) else raise ArgumentError, "argument pattern given twice" end o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} when Range values = notwice(o, values, 'values') when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') when /\A--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style not_style = (not_style || default_style).guess(arg = a) if a default_style = Switch::NoArgument default_pattern, conv = search(:atype, FalseClass) unless default_pattern ldesc << "--no-#{q}" (q = q.downcase).tr!('_', '-') long << "no-#{q}" nolong << q when /\A--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--[no-]#{q}" (o = q.downcase).tr!('_', '-') long << o not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << "no-#{o}" when /\A--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--#{q}" (o = q.downcase).tr!('_', '-') long << o when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern else has_arg = true end sdesc << "-#{q}" short << Regexp.new(q) when /\A-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end sdesc << "-#{q}" short << q when /\A=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else desc.push(o) if o && !o.empty? end end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern if Range === values and klass unless (!values.begin or klass === values.begin) and (!values.end or klass === values.end) raise ArgumentError, "range does not match class" end end if !(short.empty? and long.empty?) if has_arg and default_style == Switch::NoArgument default_style = Switch::RequiredArgument end s = (style || default_style).new(pattern || default_pattern, conv, sdesc, ldesc, arg, desc, block, values) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) end s = desc else short << pattern s = (style || default_style).new(pattern, conv, nil, nil, arg, desc, block, values) end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), nolong end # ---- # Option definition phase methods # # These methods are used to define options, or to construct an # Gem::OptionParser instance in other words. # :call-seq: # define(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def define(*opts, &block) top.append(*(sw = make_switch(opts, block))) sw[0] end # :call-seq: # on(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def on(*opts, &block) define(*opts, &block) self end alias def_option define # :call-seq: # define_head(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def define_head(*opts, &block) top.prepend(*(sw = make_switch(opts, block))) sw[0] end # :call-seq: # on_head(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # # The new option is added at the head of the summary. # def on_head(*opts, &block) define_head(*opts, &block) self end alias def_head_option define_head # :call-seq: # define_tail(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # def define_tail(*opts, &block) base.append(*(sw = make_switch(opts, block))) sw[0] end # # :call-seq: # on_tail(*params, &block) # # :include: ../doc/optparse/creates_option.rdoc # # The new option is added at the tail of the summary. # def on_tail(*opts, &block) define_tail(*opts, &block) self end alias def_tail_option define_tail # # Add separator in summary. # def separator(string) top.append(string, nil, nil) end # ---- # Arguments parse phase methods # # These methods parse +argv+, convert, and store the results by # calling handlers. As these methods do not modify +self+, +self+ # can be frozen. # # Parses command line arguments +argv+ in order. When a block is given, # each non-option argument is yielded. When optional +into+ keyword # argument is provided, the parsed option values are stored there via # []= method (so it can be Hash, or OpenStruct, or other # similar object). # # Returns the rest of +argv+ left unparsed. # def order(*argv, **keywords, &nonopt) argv = argv[0].dup if argv.size == 1 and Array === argv[0] order!(argv, **keywords, &nonopt) end # # Same as #order, but removes switches destructively. # Non-option arguments remain in +argv+. # def order!(argv = default_argv, into: nil, **keywords, &nonopt) setter = ->(name, val) {into[name.to_sym] = val} if into parse_in_order(argv, setter, **keywords, &nonopt) end def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: opt, arg, val, rest = nil nonopt ||= proc {|a| throw :terminate, a} argv.unshift(arg) if arg = catch(:terminate) { while arg = argv.shift case arg # long option when /\A--([^=]*)(?:=(.*))?/m opt, rest = $1, $2 opt.tr!('_', '-') begin if exact sw, = search(:long, opt) else sw, = complete(:long, opt, true) end rescue ParseError throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) else unless sw throw :terminate, arg unless raise_unknown raise InvalidOption, arg end end begin opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} val = callback!(cb, 1, val) if cb callback!(setter, 2, sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, rest) end # short option when /\A-(.)((=).*|.+)?/m eq, rest, opt = $3, $2, $1 has_arg, val = eq, rest begin sw, = search(:short, opt) unless sw begin sw, = complete(:short, opt) # short option matched. val = arg.delete_prefix('-') has_arg = true rescue InvalidOption raise if exact # if no short options match, try completion with long # options. sw, = complete(:long, opt) eq ||= !rest end end rescue ParseError throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) end begin opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} rescue ParseError raise $!.set_option(arg, arg.length > 2) else raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" end begin argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') val = callback!(cb, 1, val) if cb callback!(setter, 2, sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, arg.length > 2) end # non-option argument else catch(:prune) do visit(:each_option) do |sw0| sw = sw0 sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg) end nonopt.call(arg) end end end nil } visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern} argv end private :parse_in_order # Calls callback with _val_. def callback!(cb, max_arity, *args) # :nodoc: args.compact! if (size = args.size) < max_arity and cb.to_proc.lambda? (arity = cb.arity) < 0 and arity = (1-arity) arity = max_arity if arity > max_arity args[arity - 1] = nil if arity > size end cb.call(*args) end private :callback! # # Parses command line arguments +argv+ in permutation mode and returns # list of non-option arguments. When optional +into+ keyword # argument is provided, the parsed option values are stored there via # []= method (so it can be Hash, or OpenStruct, or other # similar object). # def permute(*argv, **keywords) argv = argv[0].dup if argv.size == 1 and Array === argv[0] permute!(argv, **keywords) end # # Same as #permute, but removes switches destructively. # Non-option arguments remain in +argv+. # def permute!(argv = default_argv, **keywords) nonopts = [] order!(argv, **keywords) {|nonopt| nonopts << nonopt} argv[0, 0] = nonopts argv end # # Parses command line arguments +argv+ in order when environment variable # POSIXLY_CORRECT is set, and in permutation mode otherwise. # When optional +into+ keyword argument is provided, the parsed option # values are stored there via []= method (so it can be Hash, # or OpenStruct, or other similar object). # def parse(*argv, **keywords) argv = argv[0].dup if argv.size == 1 and Array === argv[0] parse!(argv, **keywords) end # # Same as #parse, but removes switches destructively. # Non-option arguments remain in +argv+. # def parse!(argv = default_argv, **keywords) if ENV.include?('POSIXLY_CORRECT') order!(argv, **keywords) else permute!(argv, **keywords) end end # # Wrapper method for getopts.rb. # # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option") # # params["a"] = true # -a # # params["b"] = "1" # -b1 # # params["foo"] = "1" # --foo # # params["bar"] = "x" # --bar x # # params["zot"] = "z" # --zot Z # # Option +symbolize_names+ (boolean) specifies whether returned Hash keys should be Symbols; defaults to +false+ (use Strings). # # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option", symbolize_names: true) # # params[:a] = true # -a # # params[:b] = "1" # -b1 # # params[:foo] = "1" # --foo # # params[:bar] = "x" # --bar x # # params[:zot] = "z" # --zot Z # def getopts(*args, symbolize_names: false, **keywords) argv = Array === args.first ? args.shift : default_argv single_options, *long_options = *args result = {} setter = (symbolize_names ? ->(name, val) {result[name.to_sym] = val} : ->(name, val) {result[name] = val}) single_options.scan(/(.)(:)?/) do |opt, val| if val setter[opt, nil] define("-#{opt} VAL") else setter[opt, false] define("-#{opt}") end end if single_options long_options.each do |arg| arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val setter[opt, (val unless val.empty?)] define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else setter[opt, false] define("--#{opt}", *[desc].compact) end end parse_in_order(argv, setter, **keywords) result end # # See #getopts. # def self.getopts(*args, symbolize_names: false) new.getopts(*args, symbolize_names: symbolize_names) end # # Traverses @stack, sending each element method +id+ with +args+ and # +block+. # def visit(id, *args, &block) # :nodoc: @stack.reverse_each do |el| el.__send__(id, *args, &block) end nil end private :visit # # Searches +key+ in @stack for +id+ hash and returns or yields the result. # def search(id, key) # :nodoc: block_given = block_given? visit(:search, id, key) do |k| return block_given ? yield(k) : k end end private :search # # Completes shortened long style option switch and returns pair of # canonical switch and switch descriptor Gem::OptionParser::Switch. # # +typ+:: Searching table. # +opt+:: Searching key. # +icase+:: Search case insensitive if true. # +pat+:: Optional pattern for completion. # def complete(typ, opt, icase = false, *pat) # :nodoc: if pat.empty? search(typ, opt) {|sw| return [sw, opt]} # exact match or... end ambiguous = catch(:ambiguous) { visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end private :complete # # Returns additional info. # def additional_message(typ, opt) return unless typ and opt and defined?(DidYouMean::SpellChecker) all_candidates = [] visit(:get_candidates, typ) do |candidates| all_candidates.concat(candidates) end all_candidates.select! {|cand| cand.is_a?(String) } checker = DidYouMean::SpellChecker.new(dictionary: all_candidates) DidYouMean.formatter.message_for(all_candidates & checker.correct(opt)) end # # Return candidates for +word+. # def candidate(word) list = [] case word when '-' long = short = true when /\A--/ word, arg = word.split(/=/, 2) argpat = Completion.regexp(arg, false) if arg and !arg.empty? long = true when /\A-/ short = true end pat = Completion.regexp(word, long) visit(:each_option) do |opt| next unless Switch === opt opts = (long ? opt.long : []) + (short ? opt.short : []) opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat if /\A=/ =~ opt.arg opts.map! {|sw| sw + "="} if arg and CompletingHash === opt.pattern if opts = opt.pattern.candidate(arg, false, argpat) opts.map!(&:last) end end end list.concat(opts) end list end # # Loads options from file names as +filename+. Does nothing when the file # is not present. Returns whether successfully loaded. # # +filename+ defaults to basename of the program without suffix in a # directory ~/.options, then the basename with '.options' suffix # under XDG and Haiku standard places. # # The optional +into+ keyword argument works exactly like that accepted in # method #parse. # def load(filename = nil, **keywords) unless filename basename = File.basename($0, '.*') return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil basename << ".options" if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty? # https://specifications.freedesktop.org/basedir-spec/latest/#variables # # If $XDG_CONFIG_HOME is either not set or empty, a default # equal to $HOME/.config should be used. xdg = ['~/.config', true] end return [ xdg, *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), # Haiku ['~/config/settings', true], ].any? {|dir, expand| next if !dir or dir.empty? filename = File.join(dir, basename) filename = File.expand_path(filename) if expand load(filename, **keywords) rescue nil } end begin parse(*File.readlines(filename, chomp: true), **keywords) true rescue Errno::ENOENT, Errno::ENOTDIR false end end # # Parses environment variable +env+ or its uppercase with splitting like a # shell. # # +env+ defaults to the basename of the program. # def environment(env = File.basename($0, '.*'), **keywords) env = ENV[env] || ENV[env.upcase] or return require 'shellwords' parse(*Shellwords.shellwords(env), **keywords) end # # Acceptable argument classes # # # Any string and no conversion. This is fall-back. # accept(Object) {|s,|s or s.nil?} accept(NilClass) {|s,|s} # # Any non-empty string, and no conversion. # accept(String, /.+/m) {|s,*|s} # # Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal # for 0x, and decimal for others; with optional sign prefix. Converts to # Integer. # decimal = '\d+(?:_\d+)*' binary = 'b[01]+(?:_[01]+)*' hex = 'x[\da-f]+(?:_[\da-f]+)*' octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?" integer = "#{octal}|#{decimal}" accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,| begin Integer(s) rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Float number format, and converts to Float. # float = "(?:#{decimal}(?=(.)?)(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?" floatpat = %r"\A[-+]?#{float}\z"io accept(Float, floatpat) {|s,| s.to_f if s} # # Generic numeric format, converts to Integer for integer format, Float # for float format, and Rational for rational format. # real = "[-+]?(?:#{octal}|#{float})" accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, f, n,| if n Rational(d, n) elsif f Float(s) else Integer(s) end } # # Decimal integer format, to be converted to Integer. # DecimalInteger = /\A[-+]?#{decimal}\z/io accept(DecimalInteger, DecimalInteger) {|s,| begin Integer(s, 10) rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Ruby/C like octal/hexadecimal/binary integer format, to be converted to # Integer. # OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io accept(OctalInteger, OctalInteger) {|s,| begin Integer(s, 8) rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Decimal integer/float number format, to be converted to Integer for # integer format, Float for float format. # DecimalNumeric = floatpat # decimal integer is allowed as float also. accept(DecimalNumeric, floatpat) {|s, f| begin if f Float(s) else Integer(s) end rescue ArgumentError raise Gem::OptionParser::InvalidArgument, s end if s } # # Boolean switch, which means whether it is present or not, whether it is # absent or not with prefix no-, or it takes an argument # yes/no/true/false/+/-. # yesno = CompletingHash.new %w[- no false].each {|el| yesno[el] = false} %w[+ yes true].each {|el| yesno[el] = true} yesno['nil'] = false # should be nil? accept(TrueClass, yesno) {|arg, val| val == nil or val} # # Similar to TrueClass, but defaults to false. # accept(FalseClass, yesno) {|arg, val| val != nil and val} # # List of strings separated by ",". # accept(Array) do |s, | if s s = s.split(',').collect {|ss| ss unless ss.empty?} end s end # # Regular expression with options. # accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o| f = 0 if o f |= Regexp::IGNORECASE if /i/ =~ o f |= Regexp::MULTILINE if /m/ =~ o f |= Regexp::EXTENDED if /x/ =~ o case o = o.delete("imx") when "" when "u" s = s.encode(Encoding::UTF_8) when "e" s = s.encode(Encoding::EUC_JP) when "s" s = s.encode(Encoding::SJIS) when "n" f |= Regexp::NOENCODING else raise Gem::OptionParser::InvalidArgument, "unknown regexp option - #{o}" end else s ||= all end Regexp.new(s, f) end # # Exceptions # # # Base class of exceptions from Gem::OptionParser. # class ParseError < RuntimeError # Reason which caused the error. Reason = 'parse error' # :nodoc: def initialize(*args, additional: nil) @additional = additional @arg0, = args @args = args @reason = nil end attr_reader :args attr_writer :reason attr_accessor :additional # # Pushes back erred argument(s) to +argv+. # def recover(argv) argv[0, 0] = @args argv end DIR = File.join(__dir__, '') def self.filter_backtrace(array) unless $DEBUG array.delete_if {|bt| bt.start_with?(DIR)} end array end def set_backtrace(array) super(self.class.filter_backtrace(array)) end def set_option(opt, eq) if eq @args[0] = opt else @args.unshift(opt) end self end # # Returns error reason. Override this for I18N. # def reason @reason || self.class::Reason end def inspect "#<#{self.class}: #{args.join(' ')}>" end # # Default stringizing method to emit standard error message. # def message "#{reason}: #{args.join(' ')}#{additional[@arg0] if additional}" end alias to_s message end # # Raises when ambiguously completable string is encountered. # class AmbiguousOption < ParseError const_set(:Reason, 'ambiguous option') end # # Raises when there is an argument for a switch which takes no argument. # class NeedlessArgument < ParseError const_set(:Reason, 'needless argument') end # # Raises when a switch with mandatory argument has no argument. # class MissingArgument < ParseError const_set(:Reason, 'missing argument') end # # Raises when switch is undefined. # class InvalidOption < ParseError const_set(:Reason, 'invalid option') end # # Raises when the given argument does not match required format. # class InvalidArgument < ParseError const_set(:Reason, 'invalid argument') end # # Raises when the given argument word can't be completed uniquely. # class AmbiguousArgument < InvalidArgument const_set(:Reason, 'ambiguous argument') end # # Miscellaneous # # # Extends command line arguments array (ARGV) to parse itself. # module Arguable # # Sets Gem::OptionParser object, when +opt+ is +false+ or +nil+, methods # Gem::OptionParser::Arguable#options and Gem::OptionParser::Arguable#options= are # undefined. Thus, there is no ways to access the Gem::OptionParser object # via the receiver object. # def options=(opt) unless @optparse = opt class << self undef_method(:options) undef_method(:options=) end end end # # Actual Gem::OptionParser object, automatically created if nonexistent. # # If called with a block, yields the Gem::OptionParser object and returns the # result of the block. If an Gem::OptionParser::ParseError exception occurs # in the block, it is rescued, a error message printed to STDERR and # +nil+ returned. # def options @optparse ||= Gem::OptionParser.new @optparse.default_argv = self block_given? or return @optparse begin yield @optparse rescue ParseError @optparse.warn $! nil end end # # Parses +self+ destructively in order and returns +self+ containing the # rest arguments left unparsed. # def order!(**keywords, &blk) options.order!(self, **keywords, &blk) end # # Parses +self+ destructively in permutation mode and returns +self+ # containing the rest arguments left unparsed. # def permute!(**keywords) options.permute!(self, **keywords) end # # Parses +self+ destructively and returns +self+ containing the # rest arguments left unparsed. # def parse!(**keywords) options.parse!(self, **keywords) end # # Substitution of getopts is possible as follows. Also see # Gem::OptionParser#getopts. # # def getopts(*args) # ($OPT = ARGV.getopts(*args)).each do |opt, val| # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val" # end # rescue Gem::OptionParser::ParseError # end # def getopts(*args, symbolize_names: false, **keywords) options.getopts(self, *args, symbolize_names: symbolize_names, **keywords) end # # Initializes instance variable. # def self.extend_object(obj) super obj.instance_eval {@optparse = nil} end def initialize(*args) # :nodoc: super @optparse = nil end end # # Acceptable argument classes. Now contains DecimalInteger, OctalInteger # and DecimalNumeric. See Acceptable argument classes (in source code). # module Acceptables const_set(:DecimalInteger, Gem::OptionParser::DecimalInteger) const_set(:OctalInteger, Gem::OptionParser::OctalInteger) const_set(:DecimalNumeric, Gem::OptionParser::DecimalNumeric) end end # ARGV is arguable by Gem::OptionParser ARGV.extend(Gem::OptionParser::Arguable) PK!_k#"" vendor/net-http/lib/net/https.rbnu[# frozen_string_literal: true =begin = net/https -- SSL/TLS enhancement for Gem::Net::HTTP. This file has been merged with net/http. There is no longer any need to require_relative 'https' to use HTTPS. See Gem::Net::HTTP for details on how to make HTTPS connections. == Info 'OpenSSL for Ruby 2' project Copyright (C) 2001 GOTOU Yuuzou All rights reserved. == Licence This program is licensed under the same licence as Ruby. (See the file 'LICENCE'.) =end require_relative 'http' require 'openssl' PK!<. # HTTPS support added by GOTOU Yuuzou . # # This file is derived from "http-access.rb". # # Documented by Minero Aoki; converted to RDoc by William Webber. # # This program is free software. You can re-distribute and/or # modify this program under the same terms of ruby itself --- # Ruby Distribution License or GNU General Public License. # # See Gem::Net::HTTP for an overview and examples. # require_relative '../../../net-protocol/lib/net/protocol' require_relative '../../../uri/lib/uri' require_relative '../../../resolv/lib/resolv' autoload :OpenSSL, 'openssl' module Gem::Net #:nodoc: # :stopdoc: class HTTPBadResponse < StandardError; end class HTTPHeaderSyntaxError < StandardError; end # :startdoc: # \Class \Gem::Net::HTTP provides a rich library that implements the client # in a client-server model that uses the \HTTP request-response protocol. # For information about \HTTP, see: # # - {Hypertext Transfer Protocol}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol]. # - {Technical overview}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technical_overview]. # # == About the Examples # # :include: doc/net-http/examples.rdoc # # == Strategies # # - If you will make only a few GET requests, # consider using {OpenURI}[https://docs.ruby-lang.org/en/master/OpenURI.html]. # - If you will make only a few requests of all kinds, # consider using the various singleton convenience methods in this class. # Each of the following methods automatically starts and finishes # a {session}[rdoc-ref:Gem::Net::HTTP@Sessions] that sends a single request: # # # Return string response body. # Gem::Net::HTTP.get(hostname, path) # Gem::Net::HTTP.get(uri) # # # Write string response body to $stdout. # Gem::Net::HTTP.get_print(hostname, path) # Gem::Net::HTTP.get_print(uri) # # # Return response as Gem::Net::HTTPResponse object. # Gem::Net::HTTP.get_response(hostname, path) # Gem::Net::HTTP.get_response(uri) # data = '{"title": "foo", "body": "bar", "userId": 1}' # Gem::Net::HTTP.post(uri, data) # params = {title: 'foo', body: 'bar', userId: 1} # Gem::Net::HTTP.post_form(uri, params) # data = '{"title": "foo", "body": "bar", "userId": 1}' # Gem::Net::HTTP.put(uri, data) # # - If performance is important, consider using sessions, which lower request overhead. # This {session}[rdoc-ref:Gem::Net::HTTP@Sessions] has multiple requests for # {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods] # and {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: # # Gem::Net::HTTP.start(hostname) do |http| # # Session started automatically before block execution. # http.get(path) # http.head(path) # body = 'Some text' # http.post(path, body) # Can also have a block. # http.put(path, body) # http.delete(path) # http.options(path) # http.trace(path) # http.patch(path, body) # Can also have a block. # http.copy(path) # http.lock(path, body) # http.mkcol(path, body) # http.move(path) # http.propfind(path, body) # http.proppatch(path, body) # http.unlock(path, body) # # Session finished automatically at block exit. # end # # The methods cited above are convenience methods that, via their few arguments, # allow minimal control over the requests. # For greater control, consider using {request objects}[rdoc-ref:Gem::Net::HTTPRequest]. # # == URIs # # On the internet, a Gem::URI # ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier]) # is a string that identifies a particular resource. # It consists of some or all of: scheme, hostname, path, query, and fragment; # see {Gem::URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. # # A Ruby {Gem::URI::Generic}[https://docs.ruby-lang.org/en/master/Gem::URI/Generic.html] object # represents an internet Gem::URI. # It provides, among others, methods # +scheme+, +hostname+, +path+, +query+, and +fragment+. # # === Schemes # # An internet \Gem::URI has # a {scheme}[https://en.wikipedia.org/wiki/List_of_URI_schemes]. # # The two schemes supported in \Gem::Net::HTTP are 'https' and 'http': # # uri.scheme # => "https" # Gem::URI('http://example.com').scheme # => "http" # # === Hostnames # # A hostname identifies a server (host) to which requests may be sent: # # hostname = uri.hostname # => "jsonplaceholder.typicode.com" # Gem::Net::HTTP.start(hostname) do |http| # # Some HTTP stuff. # end # # === Paths # # A host-specific path identifies a resource on the host: # # _uri = uri.dup # _uri.path = '/todos/1' # hostname = _uri.hostname # path = _uri.path # Gem::Net::HTTP.get(hostname, path) # # === Queries # # A host-specific query adds name/value pairs to the Gem::URI: # # _uri = uri.dup # params = {userId: 1, completed: false} # _uri.query = Gem::URI.encode_www_form(params) # _uri # => # # Gem::Net::HTTP.get(_uri) # # === Fragments # # A {Gem::URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect # in \Gem::Net::HTTP; # the same data is returned, regardless of whether a fragment is included. # # == Request Headers # # Request headers may be used to pass additional information to the host, # similar to arguments passed in a method call; # each header is a name/value pair. # # Each of the \Gem::Net::HTTP methods that sends a request to the host # has optional argument +headers+, # where the headers are expressed as a hash of field-name/value pairs: # # headers = {Accept: 'application/json', Connection: 'Keep-Alive'} # Gem::Net::HTTP.get(uri, headers) # # See lists of both standard request fields and common request fields at # {Request Fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. # A host may also accept other custom fields. # # == \HTTP Sessions # # A _session_ is a connection between a server (host) and a client that: # # - Is begun by instance method Gem::Net::HTTP#start. # - May contain any number of requests. # - Is ended by instance method Gem::Net::HTTP#finish. # # See example sessions at {Strategies}[rdoc-ref:Gem::Net::HTTP@Strategies]. # # === Session Using \Gem::Net::HTTP.start # # If you have many requests to make to a single host (and port), # consider using singleton method Gem::Net::HTTP.start with a block; # the method handles the session automatically by: # # - Calling #start before block execution. # - Executing the block. # - Calling #finish after block execution. # # In the block, you can use these instance methods, # each of which that sends a single request: # # - {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]: # # - #get, #request_get: GET. # - #head, #request_head: HEAD. # - #post, #request_post: POST. # - #delete: DELETE. # - #options: OPTIONS. # - #trace: TRACE. # - #patch: PATCH. # # - {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: # # - #copy: COPY. # - #lock: LOCK. # - #mkcol: MKCOL. # - #move: MOVE. # - #propfind: PROPFIND. # - #proppatch: PROPPATCH. # - #unlock: UNLOCK. # # === Session Using \Gem::Net::HTTP.start and \Gem::Net::HTTP.finish # # You can manage a session manually using methods #start and #finish: # # http = Gem::Net::HTTP.new(hostname) # http.start # http.get('/todos/1') # http.get('/todos/2') # http.delete('/posts/1') # http.finish # Needed to free resources. # # === Single-Request Session # # Certain convenience methods automatically handle a session by: # # - Creating an \HTTP object # - Starting a session. # - Sending a single request. # - Finishing the session. # - Destroying the object. # # Such methods that send GET requests: # # - ::get: Returns the string response body. # - ::get_print: Writes the string response body to $stdout. # - ::get_response: Returns a Gem::Net::HTTPResponse object. # # Such methods that send POST requests: # # - ::post: Posts data to the host. # - ::post_form: Posts form data to the host. # # == \HTTP Requests and Responses # # Many of the methods above are convenience methods, # each of which sends a request and returns a string # without directly using \Gem::Net::HTTPRequest and \Gem::Net::HTTPResponse objects. # # You can, however, directly create a request object, send the request, # and retrieve the response object; see: # # - Gem::Net::HTTPRequest. # - Gem::Net::HTTPResponse. # # == Following Redirection # # Each returned response is an instance of a subclass of Gem::Net::HTTPResponse. # See the {response class hierarchy}[rdoc-ref:Gem::Net::HTTPResponse@Response+Subclasses]. # # In particular, class Gem::Net::HTTPRedirection is the parent # of all redirection classes. # This allows you to craft a case statement to handle redirections properly: # # def fetch(uri, limit = 10) # # You should choose a better exception. # raise ArgumentError, 'Too many HTTP redirects' if limit == 0 # # res = Gem::Net::HTTP.get_response(Gem::URI(uri)) # case res # when Gem::Net::HTTPSuccess # Any success class. # res # when Gem::Net::HTTPRedirection # Any redirection class. # location = res['Location'] # warn "Redirected to #{location}" # fetch(location, limit - 1) # else # Any other class. # res.value # end # end # # fetch(uri) # # == Basic Authentication # # Basic authentication is performed according to # {RFC2617}[http://www.ietf.org/rfc/rfc2617.txt]: # # req = Gem::Net::HTTP::Get.new(uri) # req.basic_auth('user', 'pass') # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # == Streaming Response Bodies # # By default \Gem::Net::HTTP reads an entire response into memory. If you are # handling large files or wish to implement a progress bar you can instead # stream the body directly to an IO. # # Gem::Net::HTTP.start(hostname) do |http| # req = Gem::Net::HTTP::Get.new(uri) # http.request(req) do |res| # open('t.tmp', 'w') do |f| # res.read_body do |chunk| # f.write chunk # end # end # end # end # # == HTTPS # # HTTPS is enabled for an \HTTP connection by Gem::Net::HTTP#use_ssl=: # # Gem::Net::HTTP.start(hostname, :use_ssl => true) do |http| # req = Gem::Net::HTTP::Get.new(uri) # res = http.request(req) # end # # Or if you simply want to make a GET request, you may pass in a Gem::URI # object that has an \HTTPS URL. \Gem::Net::HTTP automatically turns on TLS # verification if the Gem::URI object has a 'https' Gem::URI scheme: # # uri # => # # Gem::Net::HTTP.get(uri) # # == Proxy Server # # An \HTTP object can have # a {proxy server}[https://en.wikipedia.org/wiki/Proxy_server]. # # You can create an \HTTP object with a proxy server # using method Gem::Net::HTTP.new or method Gem::Net::HTTP.start. # # The proxy may be defined either by argument +p_addr+ # or by environment variable 'http_proxy'. # # === Proxy Using Argument +p_addr+ as a \String # # When argument +p_addr+ is a string hostname, # the returned +http+ has the given host as its proxy: # # http = Gem::Net::HTTP.new(hostname, nil, 'proxy.example') # http.proxy? # => true # http.proxy_from_env? # => false # http.proxy_address # => "proxy.example" # # These use default values. # http.proxy_port # => 80 # http.proxy_user # => nil # http.proxy_pass # => nil # # The port, username, and password for the proxy may also be given: # # http = Gem::Net::HTTP.new(hostname, nil, 'proxy.example', 8000, 'pname', 'ppass') # # => # # http.proxy? # => true # http.proxy_from_env? # => false # http.proxy_address # => "proxy.example" # http.proxy_port # => 8000 # http.proxy_user # => "pname" # http.proxy_pass # => "ppass" # # === Proxy Using 'ENV['http_proxy']' # # When environment variable 'http_proxy' # is set to a \Gem::URI string, # the returned +http+ will have the server at that Gem::URI as its proxy; # note that the \Gem::URI string must have a protocol # such as 'http' or 'https': # # ENV['http_proxy'] = 'http://example.com' # http = Gem::Net::HTTP.new(hostname) # http.proxy? # => true # http.proxy_from_env? # => true # http.proxy_address # => "example.com" # # These use default values. # http.proxy_port # => 80 # http.proxy_user # => nil # http.proxy_pass # => nil # # The \Gem::URI string may include proxy username, password, and port number: # # ENV['http_proxy'] = 'http://pname:ppass@example.com:8000' # http = Gem::Net::HTTP.new(hostname) # http.proxy? # => true # http.proxy_from_env? # => true # http.proxy_address # => "example.com" # http.proxy_port # => 8000 # http.proxy_user # => "pname" # http.proxy_pass # => "ppass" # # === Filtering Proxies # # With method Gem::Net::HTTP.new (but not Gem::Net::HTTP.start), # you can use argument +p_no_proxy+ to filter proxies: # # - Reject a certain address: # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example') # http.proxy_address # => nil # # - Reject certain domains or subdomains: # # http = Gem::Net::HTTP.new('example.com', nil, 'my.proxy.example', 8000, 'pname', 'ppass', 'proxy.example') # http.proxy_address # => nil # # - Reject certain addresses and port combinations: # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:1234') # http.proxy_address # => "proxy.example" # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:8000') # http.proxy_address # => nil # # - Reject a list of the types above delimited using a comma: # # http = Gem::Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000') # http.proxy_address # => nil # # http = Gem::Net::HTTP.new('example.com', nil, 'my.proxy', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000') # http.proxy_address # => nil # # == Compression and Decompression # # \Gem::Net::HTTP does not compress the body of a request before sending. # # By default, \Gem::Net::HTTP adds header 'Accept-Encoding' # to a new {request object}[rdoc-ref:Gem::Net::HTTPRequest]: # # Gem::Net::HTTP::Get.new(uri)['Accept-Encoding'] # # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" # # This requests the server to zip-encode the response body if there is one; # the server is not required to do so. # # \Gem::Net::HTTP does not automatically decompress a response body # if the response has header 'Content-Range'. # # Otherwise decompression (or not) depends on the value of header # {Content-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-encoding-response-header]: # # - 'deflate', 'gzip', or 'x-gzip': # decompresses the body and deletes the header. # - 'none' or 'identity': # does not decompress the body, but deletes the header. # - Any other value: # leaves the body and header unchanged. # # == What's Here # # First, what's elsewhere. Class Gem::Net::HTTP: # # - Inherits from {class Object}[https://docs.ruby-lang.org/en/master/Object.html#class-Object-label-What-27s+Here]. # # This is a categorized summary of methods and attributes. # # === \Gem::Net::HTTP Objects # # - {::new}[rdoc-ref:Gem::Net::HTTP.new]: # Creates a new instance. # - {#inspect}[rdoc-ref:Gem::Net::HTTP#inspect]: # Returns a string representation of +self+. # # === Sessions # # - {::start}[rdoc-ref:Gem::Net::HTTP.start]: # Begins a new session in a new \Gem::Net::HTTP object. # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?]: # Returns whether in a session. # - {#finish}[rdoc-ref:Gem::Net::HTTP#finish]: # Ends an active session. # - {#start}[rdoc-ref:Gem::Net::HTTP#start]: # Begins a new session in an existing \Gem::Net::HTTP object (+self+). # # === Connections # # - {:continue_timeout}[rdoc-ref:Gem::Net::HTTP#continue_timeout]: # Returns the continue timeout. # - {#continue_timeout=}[rdoc-ref:Gem::Net::HTTP#continue_timeout=]: # Sets the continue timeout seconds. # - {:keep_alive_timeout}[rdoc-ref:Gem::Net::HTTP#keep_alive_timeout]: # Returns the keep-alive timeout. # - {:keep_alive_timeout=}[rdoc-ref:Gem::Net::HTTP#keep_alive_timeout=]: # Sets the keep-alive timeout. # - {:max_retries}[rdoc-ref:Gem::Net::HTTP#max_retries]: # Returns the maximum retries. # - {#max_retries=}[rdoc-ref:Gem::Net::HTTP#max_retries=]: # Sets the maximum retries. # - {:open_timeout}[rdoc-ref:Gem::Net::HTTP#open_timeout]: # Returns the open timeout. # - {:open_timeout=}[rdoc-ref:Gem::Net::HTTP#open_timeout=]: # Sets the open timeout. # - {:read_timeout}[rdoc-ref:Gem::Net::HTTP#read_timeout]: # Returns the open timeout. # - {:read_timeout=}[rdoc-ref:Gem::Net::HTTP#read_timeout=]: # Sets the read timeout. # - {:ssl_timeout}[rdoc-ref:Gem::Net::HTTP#ssl_timeout]: # Returns the ssl timeout. # - {:ssl_timeout=}[rdoc-ref:Gem::Net::HTTP#ssl_timeout=]: # Sets the ssl timeout. # - {:write_timeout}[rdoc-ref:Gem::Net::HTTP#write_timeout]: # Returns the write timeout. # - {write_timeout=}[rdoc-ref:Gem::Net::HTTP#write_timeout=]: # Sets the write timeout. # # === Requests # # - {::get}[rdoc-ref:Gem::Net::HTTP.get]: # Sends a GET request and returns the string response body. # - {::get_print}[rdoc-ref:Gem::Net::HTTP.get_print]: # Sends a GET request and write the string response body to $stdout. # - {::get_response}[rdoc-ref:Gem::Net::HTTP.get_response]: # Sends a GET request and returns a response object. # - {::post_form}[rdoc-ref:Gem::Net::HTTP.post_form]: # Sends a POST request with form data and returns a response object. # - {::post}[rdoc-ref:Gem::Net::HTTP.post]: # Sends a POST request with data and returns a response object. # - {::put}[rdoc-ref:Gem::Net::HTTP.put]: # Sends a PUT request with data and returns a response object. # - {#copy}[rdoc-ref:Gem::Net::HTTP#copy]: # Sends a COPY request and returns a response object. # - {#delete}[rdoc-ref:Gem::Net::HTTP#delete]: # Sends a DELETE request and returns a response object. # - {#get}[rdoc-ref:Gem::Net::HTTP#get]: # Sends a GET request and returns a response object. # - {#head}[rdoc-ref:Gem::Net::HTTP#head]: # Sends a HEAD request and returns a response object. # - {#lock}[rdoc-ref:Gem::Net::HTTP#lock]: # Sends a LOCK request and returns a response object. # - {#mkcol}[rdoc-ref:Gem::Net::HTTP#mkcol]: # Sends a MKCOL request and returns a response object. # - {#move}[rdoc-ref:Gem::Net::HTTP#move]: # Sends a MOVE request and returns a response object. # - {#options}[rdoc-ref:Gem::Net::HTTP#options]: # Sends a OPTIONS request and returns a response object. # - {#patch}[rdoc-ref:Gem::Net::HTTP#patch]: # Sends a PATCH request and returns a response object. # - {#post}[rdoc-ref:Gem::Net::HTTP#post]: # Sends a POST request and returns a response object. # - {#propfind}[rdoc-ref:Gem::Net::HTTP#propfind]: # Sends a PROPFIND request and returns a response object. # - {#proppatch}[rdoc-ref:Gem::Net::HTTP#proppatch]: # Sends a PROPPATCH request and returns a response object. # - {#put}[rdoc-ref:Gem::Net::HTTP#put]: # Sends a PUT request and returns a response object. # - {#request}[rdoc-ref:Gem::Net::HTTP#request]: # Sends a request and returns a response object. # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get]: # Sends a GET request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head]: # Sends a HEAD request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post]: # Sends a POST request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. # - {#send_request}[rdoc-ref:Gem::Net::HTTP#send_request]: # Sends a request and returns a response object. # - {#trace}[rdoc-ref:Gem::Net::HTTP#trace]: # Sends a TRACE request and returns a response object. # - {#unlock}[rdoc-ref:Gem::Net::HTTP#unlock]: # Sends an UNLOCK request and returns a response object. # # === Responses # # - {:close_on_empty_response}[rdoc-ref:Gem::Net::HTTP#close_on_empty_response]: # Returns whether to close connection on empty response. # - {:close_on_empty_response=}[rdoc-ref:Gem::Net::HTTP#close_on_empty_response=]: # Sets whether to close connection on empty response. # - {:ignore_eof}[rdoc-ref:Gem::Net::HTTP#ignore_eof]: # Returns whether to ignore end-of-file when reading a response body # with Content-Length headers. # - {:ignore_eof=}[rdoc-ref:Gem::Net::HTTP#ignore_eof=]: # Sets whether to ignore end-of-file when reading a response body # with Content-Length headers. # - {:response_body_encoding}[rdoc-ref:Gem::Net::HTTP#response_body_encoding]: # Returns the encoding to use for the response body. # - {#response_body_encoding=}[rdoc-ref:Gem::Net::HTTP#response_body_encoding=]: # Sets the response body encoding. # # === Proxies # # - {:proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]: # Returns the proxy address. # - {:proxy_address=}[rdoc-ref:Gem::Net::HTTP#proxy_address=]: # Sets the proxy address. # - {::proxy_class?}[rdoc-ref:Gem::Net::HTTP.proxy_class?]: # Returns whether +self+ is a proxy class. # - {#proxy?}[rdoc-ref:Gem::Net::HTTP#proxy?]: # Returns whether +self+ has a proxy. # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]: # Returns the proxy address. # - {#proxy_from_env?}[rdoc-ref:Gem::Net::HTTP#proxy_from_env?]: # Returns whether the proxy is taken from an environment variable. # - {:proxy_from_env=}[rdoc-ref:Gem::Net::HTTP#proxy_from_env=]: # Sets whether the proxy is to be taken from an environment variable. # - {:proxy_pass}[rdoc-ref:Gem::Net::HTTP#proxy_pass]: # Returns the proxy password. # - {:proxy_pass=}[rdoc-ref:Gem::Net::HTTP#proxy_pass=]: # Sets the proxy password. # - {:proxy_port}[rdoc-ref:Gem::Net::HTTP#proxy_port]: # Returns the proxy port. # - {:proxy_port=}[rdoc-ref:Gem::Net::HTTP#proxy_port=]: # Sets the proxy port. # - {#proxy_user}[rdoc-ref:Gem::Net::HTTP#proxy_user]: # Returns the proxy user name. # - {:proxy_user=}[rdoc-ref:Gem::Net::HTTP#proxy_user=]: # Sets the proxy user. # # === Security # # - {:ca_file}[rdoc-ref:Gem::Net::HTTP#ca_file]: # Returns the path to a CA certification file. # - {:ca_file=}[rdoc-ref:Gem::Net::HTTP#ca_file=]: # Sets the path to a CA certification file. # - {:ca_path}[rdoc-ref:Gem::Net::HTTP#ca_path]: # Returns the path of to CA directory containing certification files. # - {:ca_path=}[rdoc-ref:Gem::Net::HTTP#ca_path=]: # Sets the path of to CA directory containing certification files. # - {:cert}[rdoc-ref:Gem::Net::HTTP#cert]: # Returns the OpenSSL::X509::Certificate object to be used for client certification. # - {:cert=}[rdoc-ref:Gem::Net::HTTP#cert=]: # Sets the OpenSSL::X509::Certificate object to be used for client certification. # - {:cert_store}[rdoc-ref:Gem::Net::HTTP#cert_store]: # Returns the X509::Store to be used for verifying peer certificate. # - {:cert_store=}[rdoc-ref:Gem::Net::HTTP#cert_store=]: # Sets the X509::Store to be used for verifying peer certificate. # - {:ciphers}[rdoc-ref:Gem::Net::HTTP#ciphers]: # Returns the available SSL ciphers. # - {:ciphers=}[rdoc-ref:Gem::Net::HTTP#ciphers=]: # Sets the available SSL ciphers. # - {:extra_chain_cert}[rdoc-ref:Gem::Net::HTTP#extra_chain_cert]: # Returns the extra X509 certificates to be added to the certificate chain. # - {:extra_chain_cert=}[rdoc-ref:Gem::Net::HTTP#extra_chain_cert=]: # Sets the extra X509 certificates to be added to the certificate chain. # - {:key}[rdoc-ref:Gem::Net::HTTP#key]: # Returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. # - {:key=}[rdoc-ref:Gem::Net::HTTP#key=]: # Sets the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. # - {:max_version}[rdoc-ref:Gem::Net::HTTP#max_version]: # Returns the maximum SSL version. # - {:max_version=}[rdoc-ref:Gem::Net::HTTP#max_version=]: # Sets the maximum SSL version. # - {:min_version}[rdoc-ref:Gem::Net::HTTP#min_version]: # Returns the minimum SSL version. # - {:min_version=}[rdoc-ref:Gem::Net::HTTP#min_version=]: # Sets the minimum SSL version. # - {#peer_cert}[rdoc-ref:Gem::Net::HTTP#peer_cert]: # Returns the X509 certificate chain for the session's socket peer. # - {:ssl_version}[rdoc-ref:Gem::Net::HTTP#ssl_version]: # Returns the SSL version. # - {:ssl_version=}[rdoc-ref:Gem::Net::HTTP#ssl_version=]: # Sets the SSL version. # - {#use_ssl=}[rdoc-ref:Gem::Net::HTTP#use_ssl=]: # Sets whether a new session is to use Transport Layer Security. # - {#use_ssl?}[rdoc-ref:Gem::Net::HTTP#use_ssl?]: # Returns whether +self+ uses SSL. # - {:verify_callback}[rdoc-ref:Gem::Net::HTTP#verify_callback]: # Returns the callback for the server certification verification. # - {:verify_callback=}[rdoc-ref:Gem::Net::HTTP#verify_callback=]: # Sets the callback for the server certification verification. # - {:verify_depth}[rdoc-ref:Gem::Net::HTTP#verify_depth]: # Returns the maximum depth for the certificate chain verification. # - {:verify_depth=}[rdoc-ref:Gem::Net::HTTP#verify_depth=]: # Sets the maximum depth for the certificate chain verification. # - {:verify_hostname}[rdoc-ref:Gem::Net::HTTP#verify_hostname]: # Returns the flags for server the certification verification at the beginning of the SSL/TLS session. # - {:verify_hostname=}[rdoc-ref:Gem::Net::HTTP#verify_hostname=]: # Sets he flags for server the certification verification at the beginning of the SSL/TLS session. # - {:verify_mode}[rdoc-ref:Gem::Net::HTTP#verify_mode]: # Returns the flags for server the certification verification at the beginning of the SSL/TLS session. # - {:verify_mode=}[rdoc-ref:Gem::Net::HTTP#verify_mode=]: # Sets the flags for server the certification verification at the beginning of the SSL/TLS session. # # === Addresses and Ports # # - {:address}[rdoc-ref:Gem::Net::HTTP#address]: # Returns the string host name or host IP. # - {::default_port}[rdoc-ref:Gem::Net::HTTP.default_port]: # Returns integer 80, the default port to use for HTTP requests. # - {::http_default_port}[rdoc-ref:Gem::Net::HTTP.http_default_port]: # Returns integer 80, the default port to use for HTTP requests. # - {::https_default_port}[rdoc-ref:Gem::Net::HTTP.https_default_port]: # Returns integer 443, the default port to use for HTTPS requests. # - {#ipaddr}[rdoc-ref:Gem::Net::HTTP#ipaddr]: # Returns the IP address for the connection. # - {#ipaddr=}[rdoc-ref:Gem::Net::HTTP#ipaddr=]: # Sets the IP address for the connection. # - {:local_host}[rdoc-ref:Gem::Net::HTTP#local_host]: # Returns the string local host used to establish the connection. # - {:local_host=}[rdoc-ref:Gem::Net::HTTP#local_host=]: # Sets the string local host used to establish the connection. # - {:local_port}[rdoc-ref:Gem::Net::HTTP#local_port]: # Returns the integer local port used to establish the connection. # - {:local_port=}[rdoc-ref:Gem::Net::HTTP#local_port=]: # Sets the integer local port used to establish the connection. # - {:port}[rdoc-ref:Gem::Net::HTTP#port]: # Returns the integer port number. # # === \HTTP Version # # - {::version_1_2?}[rdoc-ref:Gem::Net::HTTP.version_1_2?] # (aliased as {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]): # Returns true; retained for compatibility. # # === Debugging # # - {#set_debug_output}[rdoc-ref:Gem::Net::HTTP#set_debug_output]: # Sets the output stream for debugging. # class HTTP < Protocol # :stopdoc: VERSION = "0.7.0" HTTPVersion = '1.1' begin require 'zlib' HAVE_ZLIB=true rescue LoadError HAVE_ZLIB=false end # :startdoc: # Returns +true+; retained for compatibility. def HTTP.version_1_2 true end # Returns +true+; retained for compatibility. def HTTP.version_1_2? true end # Returns +false+; retained for compatibility. def HTTP.version_1_1? #:nodoc: false end class << HTTP alias is_version_1_1? version_1_1? #:nodoc: alias is_version_1_2? version_1_2? #:nodoc: end # :call-seq: # Gem::Net::HTTP.get_print(hostname, path, port = 80) -> nil # Gem::Net::HTTP:get_print(uri, headers = {}, port = uri.port) -> nil # # Like Gem::Net::HTTP.get, but writes the returned body to $stdout; # returns +nil+. def HTTP.get_print(uri_or_host, path_or_headers = nil, port = nil) get_response(uri_or_host, path_or_headers, port) {|res| res.read_body do |chunk| $stdout.print chunk end } nil end # :call-seq: # Gem::Net::HTTP.get(hostname, path, port = 80) -> body # Gem::Net::HTTP:get(uri, headers = {}, port = uri.port) -> body # # Sends a GET request and returns the \HTTP response body as a string. # # With string arguments +hostname+ and +path+: # # hostname = 'jsonplaceholder.typicode.com' # path = '/todos/1' # puts Gem::Net::HTTP.get(hostname, path) # # Output: # # { # "userId": 1, # "id": 1, # "title": "delectus aut autem", # "completed": false # } # # With Gem::URI object +uri+ and optional hash argument +headers+: # # uri = Gem::URI('https://jsonplaceholder.typicode.com/todos/1') # headers = {'Content-type' => 'application/json; charset=UTF-8'} # Gem::Net::HTTP.get(uri, headers) # # Related: # # - Gem::Net::HTTP::Get: request class for \HTTP method +GET+. # - Gem::Net::HTTP#get: convenience method for \HTTP method +GET+. # def HTTP.get(uri_or_host, path_or_headers = nil, port = nil) get_response(uri_or_host, path_or_headers, port).body end # :call-seq: # Gem::Net::HTTP.get_response(hostname, path, port = 80) -> http_response # Gem::Net::HTTP:get_response(uri, headers = {}, port = uri.port) -> http_response # # Like Gem::Net::HTTP.get, but returns a Gem::Net::HTTPResponse object # instead of the body string. def HTTP.get_response(uri_or_host, path_or_headers = nil, port = nil, &block) if path_or_headers && !path_or_headers.is_a?(Hash) host = uri_or_host path = path_or_headers new(host, port || HTTP.default_port).start {|http| return http.request_get(path, &block) } else uri = uri_or_host headers = path_or_headers start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') {|http| return http.request_get(uri, headers, &block) } end end # Posts data to a host; returns a Gem::Net::HTTPResponse object. # # Argument +url+ must be a URL; # argument +data+ must be a string: # # _uri = uri.dup # _uri.path = '/posts' # data = '{"title": "foo", "body": "bar", "userId": 1}' # headers = {'content-type': 'application/json'} # res = Gem::Net::HTTP.post(_uri, data, headers) # => # # puts res.body # # Output: # # { # "title": "foo", # "body": "bar", # "userId": 1, # "id": 101 # } # # Related: # # - Gem::Net::HTTP::Post: request class for \HTTP method +POST+. # - Gem::Net::HTTP#post: convenience method for \HTTP method +POST+. # def HTTP.post(url, data, header = nil) start(url.hostname, url.port, :use_ssl => url.scheme == 'https' ) {|http| http.post(url, data, header) } end # Posts data to a host; returns a Gem::Net::HTTPResponse object. # # Argument +url+ must be a Gem::URI; # argument +data+ must be a hash: # # _uri = uri.dup # _uri.path = '/posts' # data = {title: 'foo', body: 'bar', userId: 1} # res = Gem::Net::HTTP.post_form(_uri, data) # => # # puts res.body # # Output: # # { # "title": "foo", # "body": "bar", # "userId": "1", # "id": 101 # } # def HTTP.post_form(url, params) req = Post.new(url) req.form_data = params req.basic_auth url.user, url.password if url.user start(url.hostname, url.port, :use_ssl => url.scheme == 'https' ) {|http| http.request(req) } end # Sends a PUT request to the server; returns a Gem::Net::HTTPResponse object. # # Argument +url+ must be a URL; # argument +data+ must be a string: # # _uri = uri.dup # _uri.path = '/posts' # data = '{"title": "foo", "body": "bar", "userId": 1}' # headers = {'content-type': 'application/json'} # res = Gem::Net::HTTP.put(_uri, data, headers) # => # # puts res.body # # Output: # # { # "title": "foo", # "body": "bar", # "userId": 1, # "id": 101 # } # # Related: # # - Gem::Net::HTTP::Put: request class for \HTTP method +PUT+. # - Gem::Net::HTTP#put: convenience method for \HTTP method +PUT+. # def HTTP.put(url, data, header = nil) start(url.hostname, url.port, :use_ssl => url.scheme == 'https' ) {|http| http.put(url, data, header) } end # # \HTTP session management # # Returns integer +80+, the default port to use for \HTTP requests: # # Gem::Net::HTTP.default_port # => 80 # def HTTP.default_port http_default_port() end # Returns integer +80+, the default port to use for \HTTP requests: # # Gem::Net::HTTP.http_default_port # => 80 # def HTTP.http_default_port 80 end # Returns integer +443+, the default port to use for HTTPS requests: # # Gem::Net::HTTP.https_default_port # => 443 # def HTTP.https_default_port 443 end def HTTP.socket_type #:nodoc: obsolete BufferedIO end # :call-seq: # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) -> http # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) {|http| ... } -> object # # Creates a new \Gem::Net::HTTP object, +http+, via \Gem::Net::HTTP.new: # # - For arguments +address+ and +port+, see Gem::Net::HTTP.new. # - For proxy-defining arguments +p_addr+ through +p_pass+, # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. # - For argument +opts+, see below. # # With no block given: # # - Calls http.start with no block (see #start), # which opens a TCP connection and \HTTP session. # - Returns +http+. # - The caller should call #finish to close the session: # # http = Gem::Net::HTTP.start(hostname) # http.started? # => true # http.finish # http.started? # => false # # With a block given: # # - Calls http.start with the block (see #start), which: # # - Opens a TCP connection and \HTTP session. # - Calls the block, # which may make any number of requests to the host. # - Closes the \HTTP session and TCP connection on block exit. # - Returns the block's value +object+. # # - Returns +object+. # # Example: # # hostname = 'jsonplaceholder.typicode.com' # Gem::Net::HTTP.start(hostname) do |http| # puts http.get('/todos/1').body # puts http.get('/todos/2').body # end # # Output: # # { # "userId": 1, # "id": 1, # "title": "delectus aut autem", # "completed": false # } # { # "userId": 1, # "id": 2, # "title": "quis ut nam facilis et officia qui", # "completed": false # } # # If the last argument given is a hash, it is the +opts+ hash, # where each key is a method or accessor to be called, # and its value is the value to be set. # # The keys may include: # # - #ca_file # - #ca_path # - #cert # - #cert_store # - #ciphers # - #close_on_empty_response # - +ipaddr+ (calls #ipaddr=) # - #keep_alive_timeout # - #key # - #open_timeout # - #read_timeout # - #ssl_timeout # - #ssl_version # - +use_ssl+ (calls #use_ssl=) # - #verify_callback # - #verify_depth # - #verify_mode # - #write_timeout # # Note: If +port+ is +nil+ and opts[:use_ssl] is a truthy value, # the value passed to +new+ is Gem::Net::HTTP.https_default_port, not +port+. # def HTTP.start(address, *arg, &block) # :yield: +http+ arg.pop if opt = Hash.try_convert(arg[-1]) port, p_addr, p_port, p_user, p_pass = *arg p_addr = :ENV if arg.size < 2 port = https_default_port if !port && opt && opt[:use_ssl] http = new(address, port, p_addr, p_port, p_user, p_pass) http.ipaddr = opt[:ipaddr] if opt && opt[:ipaddr] if opt if opt[:use_ssl] opt = {verify_mode: OpenSSL::SSL::VERIFY_PEER}.update(opt) end http.methods.grep(/\A(\w+)=\z/) do |meth| key = $1.to_sym opt.key?(key) or next http.__send__(meth, opt[key]) end end http.start(&block) end class << HTTP alias newobj new # :nodoc: end # Returns a new \Gem::Net::HTTP object +http+ # (but does not open a TCP connection or \HTTP session). # # With only string argument +address+ given # (and ENV['http_proxy'] undefined or +nil+), # the returned +http+: # # - Has the given address. # - Has the default port number, Gem::Net::HTTP.default_port (80). # - Has no proxy. # # Example: # # http = Gem::Net::HTTP.new(hostname) # # => # # http.address # => "jsonplaceholder.typicode.com" # http.port # => 80 # http.proxy? # => false # # With integer argument +port+ also given, # the returned +http+ has the given port: # # http = Gem::Net::HTTP.new(hostname, 8000) # # => # # http.port # => 8000 # # For proxy-defining arguments +p_addr+ through +p_no_proxy+, # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. # def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil, p_use_ssl = nil) http = super address, port if proxy_class? then # from Gem::Net::HTTP::Proxy() http.proxy_from_env = @proxy_from_env http.proxy_address = @proxy_address http.proxy_port = @proxy_port http.proxy_user = @proxy_user http.proxy_pass = @proxy_pass http.proxy_use_ssl = @proxy_use_ssl elsif p_addr == :ENV then http.proxy_from_env = true else if p_addr && p_no_proxy && !Gem::URI::Generic.use_proxy?(address, address, port, p_no_proxy) p_addr = nil p_port = nil end http.proxy_address = p_addr http.proxy_port = p_port || default_port http.proxy_user = p_user http.proxy_pass = p_pass http.proxy_use_ssl = p_use_ssl end http end class << HTTP # Allows to set the default configuration that will be used # when creating a new connection. # # Example: # # Gem::Net::HTTP.default_configuration = { # read_timeout: 1, # write_timeout: 1 # } # http = Gem::Net::HTTP.new(hostname) # http.open_timeout # => 60 # http.read_timeout # => 1 # http.write_timeout # => 1 # attr_accessor :default_configuration end # Creates a new \Gem::Net::HTTP object for the specified server address, # without opening the TCP connection or initializing the \HTTP session. # The +address+ should be a DNS hostname or IP address. def initialize(address, port = nil) # :nodoc: defaults = { keep_alive_timeout: 2, close_on_empty_response: false, open_timeout: 60, read_timeout: 60, write_timeout: 60, continue_timeout: nil, max_retries: 1, debug_output: nil, response_body_encoding: false, ignore_eof: true } options = defaults.merge(self.class.default_configuration || {}) @address = address @port = (port || HTTP.default_port) @ipaddr = nil @local_host = nil @local_port = nil @curr_http_version = HTTPVersion @keep_alive_timeout = options[:keep_alive_timeout] @last_communicated = nil @close_on_empty_response = options[:close_on_empty_response] @socket = nil @started = false @open_timeout = options[:open_timeout] @read_timeout = options[:read_timeout] @write_timeout = options[:write_timeout] @continue_timeout = options[:continue_timeout] @max_retries = options[:max_retries] @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] @proxy_from_env = false @proxy_uri = nil @proxy_address = nil @proxy_port = nil @proxy_user = nil @proxy_pass = nil @proxy_use_ssl = nil @use_ssl = false @ssl_context = nil @ssl_session = nil @sspi_enabled = false SSL_IVNAMES.each do |ivname| instance_variable_set ivname, nil end end # Returns a string representation of +self+: # # Gem::Net::HTTP.new(hostname).inspect # # => "#" # def inspect "#<#{self.class} #{@address}:#{@port} open=#{started?}>" end # *WARNING* This method opens a serious security hole. # Never use this method in production code. # # Sets the output stream for debugging: # # http = Gem::Net::HTTP.new(hostname) # File.open('t.tmp', 'w') do |file| # http.set_debug_output(file) # http.start # http.get('/nosuch/1') # http.finish # end # puts File.read('t.tmp') # # Output: # # opening connection to jsonplaceholder.typicode.com:80... # opened # <- "GET /nosuch/1 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: jsonplaceholder.typicode.com\r\n\r\n" # -> "HTTP/1.1 404 Not Found\r\n" # -> "Date: Mon, 12 Dec 2022 21:14:11 GMT\r\n" # -> "Content-Type: application/json; charset=utf-8\r\n" # -> "Content-Length: 2\r\n" # -> "Connection: keep-alive\r\n" # -> "X-Powered-By: Express\r\n" # -> "X-Ratelimit-Limit: 1000\r\n" # -> "X-Ratelimit-Remaining: 999\r\n" # -> "X-Ratelimit-Reset: 1670879660\r\n" # -> "Vary: Origin, Accept-Encoding\r\n" # -> "Access-Control-Allow-Credentials: true\r\n" # -> "Cache-Control: max-age=43200\r\n" # -> "Pragma: no-cache\r\n" # -> "Expires: -1\r\n" # -> "X-Content-Type-Options: nosniff\r\n" # -> "Etag: W/\"2-vyGp6PvFo4RvsFtPoIWeCReyIC8\"\r\n" # -> "Via: 1.1 vegur\r\n" # -> "CF-Cache-Status: MISS\r\n" # -> "Server-Timing: cf-q-config;dur=1.3000000762986e-05\r\n" # -> "Report-To: {\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=yOr40jo%2BwS1KHzhTlVpl54beJ5Wx2FcG4gGV0XVrh3X9OlR5q4drUn2dkt5DGO4GDcE%2BVXT7CNgJvGs%2BZleIyMu8CLieFiDIvOviOY3EhHg94m0ZNZgrEdpKD0S85S507l1vsEwEHkoTm%2Ff19SiO\"}],\"group\":\"cf-nel\",\"max_age\":604800}\r\n" # -> "NEL: {\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}\r\n" # -> "Server: cloudflare\r\n" # -> "CF-RAY: 778977dc484ce591-DFW\r\n" # -> "alt-svc: h3=\":443\"; ma=86400, h3-29=\":443\"; ma=86400\r\n" # -> "\r\n" # reading 2 bytes... # -> "{}" # read 2 bytes # Conn keep-alive # def set_debug_output(output) warn 'Gem::Net::HTTP#set_debug_output called after HTTP started', uplevel: 1 if started? @debug_output = output end # Returns the string host name or host IP given as argument +address+ in ::new. attr_reader :address # Returns the integer port number given as argument +port+ in ::new. attr_reader :port # Sets or returns the string local host used to establish the connection; # initially +nil+. attr_accessor :local_host # Sets or returns the integer local port used to establish the connection; # initially +nil+. attr_accessor :local_port # Returns the encoding to use for the response body; # see #response_body_encoding=. attr_reader :response_body_encoding # Sets the encoding to be used for the response body; # returns the encoding. # # The given +value+ may be: # # - An Encoding object. # - The name of an encoding. # - An alias for an encoding name. # # See {Encoding}[https://docs.ruby-lang.org/en/master/Encoding.html]. # # Examples: # # http = Gem::Net::HTTP.new(hostname) # http.response_body_encoding = Encoding::US_ASCII # => # # http.response_body_encoding = 'US-ASCII' # => "US-ASCII" # http.response_body_encoding = 'ASCII' # => "ASCII" # def response_body_encoding=(value) value = Encoding.find(value) if value.is_a?(String) @response_body_encoding = value end # Sets whether to determine the proxy from environment variable # 'ENV['http_proxy']'; # see {Proxy Using ENV['http_proxy']}[rdoc-ref:Gem::Net::HTTP@Proxy+Using+-27ENV-5B-27http_proxy-27-5D-27]. attr_writer :proxy_from_env # Sets the proxy address; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_address # Sets the proxy port; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_port # Sets the proxy user; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_user # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_pass attr_writer :proxy_use_ssl # Returns the IP address for the connection. # # If the session has not been started, # returns the value set by #ipaddr=, # or +nil+ if it has not been set: # # http = Gem::Net::HTTP.new(hostname) # http.ipaddr # => nil # http.ipaddr = '172.67.155.76' # http.ipaddr # => "172.67.155.76" # # If the session has been started, # returns the IP address from the socket: # # http = Gem::Net::HTTP.new(hostname) # http.start # http.ipaddr # => "172.67.155.76" # http.finish # def ipaddr started? ? @socket.io.peeraddr[3] : @ipaddr end # Sets the IP address for the connection: # # http = Gem::Net::HTTP.new(hostname) # http.ipaddr # => nil # http.ipaddr = '172.67.155.76' # http.ipaddr # => "172.67.155.76" # # The IP address may not be set if the session has been started. def ipaddr=(addr) raise IOError, "ipaddr value changed, but session already started" if started? @ipaddr = addr end # Sets or returns the numeric (\Integer or \Float) number of seconds # to wait for a connection to open; # initially 60. # If the connection is not made in the given interval, # an exception is raised. attr_accessor :open_timeout # Returns the numeric (\Integer or \Float) number of seconds # to wait for one block to be read (via one read(2) call); # see #read_timeout=. attr_reader :read_timeout # Returns the numeric (\Integer or \Float) number of seconds # to wait for one block to be written (via one write(2) call); # see #write_timeout=. attr_reader :write_timeout # Sets the maximum number of times to retry an idempotent request in case of # \Gem::Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET, # Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError, # Gem::Timeout::Error. # The initial value is 1. # # Argument +retries+ must be a non-negative numeric value: # # http = Gem::Net::HTTP.new(hostname) # http.max_retries = 2 # => 2 # http.max_retries # => 2 # def max_retries=(retries) retries = retries.to_int if retries < 0 raise ArgumentError, 'max_retries should be non-negative integer number' end @max_retries = retries end # Returns the maximum number of times to retry an idempotent request; # see #max_retries=. attr_reader :max_retries # Sets the read timeout, in seconds, for +self+ to integer +sec+; # the initial value is 60. # # Argument +sec+ must be a non-negative numeric value: # # http = Gem::Net::HTTP.new(hostname) # http.read_timeout # => 60 # http.get('/todos/1') # => # # http.read_timeout = 0 # http.get('/todos/1') # Raises Gem::Net::ReadTimeout. # def read_timeout=(sec) @socket.read_timeout = sec if @socket @read_timeout = sec end # Sets the write timeout, in seconds, for +self+ to integer +sec+; # the initial value is 60. # # Argument +sec+ must be a non-negative numeric value: # # _uri = uri.dup # _uri.path = '/posts' # body = 'bar' * 200000 # data = < 60 # http.post(_uri.path, data, headers) # # => # # http.write_timeout = 0 # http.post(_uri.path, data, headers) # Raises Gem::Net::WriteTimeout. # def write_timeout=(sec) @socket.write_timeout = sec if @socket @write_timeout = sec end # Returns the continue timeout value; # see continue_timeout=. attr_reader :continue_timeout # Sets the continue timeout value, # which is the number of seconds to wait for an expected 100 Continue response. # If the \HTTP object does not receive a response in this many seconds # it sends the request body. def continue_timeout=(sec) @socket.continue_timeout = sec if @socket @continue_timeout = sec end # Sets or returns the numeric (\Integer or \Float) number of seconds # to keep the connection open after a request is sent; # initially 2. # If a new request is made during the given interval, # the still-open connection is used; # otherwise the connection will have been closed # and a new connection is opened. attr_accessor :keep_alive_timeout # Sets or returns whether to ignore end-of-file when reading a response body # with Content-Length headers; # initially +true+. attr_accessor :ignore_eof # Returns +true+ if the \HTTP session has been started: # # http = Gem::Net::HTTP.new(hostname) # http.started? # => false # http.start # http.started? # => true # http.finish # => nil # http.started? # => false # # Gem::Net::HTTP.start(hostname) do |http| # http.started? # end # => true # http.started? # => false # def started? @started end alias active? started? #:nodoc: obsolete # Sets or returns whether to close the connection when the response is empty; # initially +false+. attr_accessor :close_on_empty_response # Returns +true+ if +self+ uses SSL, +false+ otherwise. # See Gem::Net::HTTP#use_ssl=. def use_ssl? @use_ssl end # Sets whether a new session is to use # {Transport Layer Security}[https://en.wikipedia.org/wiki/Transport_Layer_Security]: # # Raises IOError if attempting to change during a session. # # Raises OpenSSL::SSL::SSLError if the port is not an HTTPS port. def use_ssl=(flag) flag = flag ? true : false if started? and @use_ssl != flag raise IOError, "use_ssl value changed, but session already started" end @use_ssl = flag end SSL_ATTRIBUTES = [ :ca_file, :ca_path, :cert, :cert_store, :ciphers, :extra_chain_cert, :key, :ssl_timeout, :ssl_version, :min_version, :max_version, :verify_callback, :verify_depth, :verify_mode, :verify_hostname, ] # :nodoc: SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file # Sets or returns the path of to CA directory # containing certification files in PEM format. attr_accessor :ca_path # Sets or returns the OpenSSL::X509::Certificate object # to be used for client certification. attr_accessor :cert # Sets or returns the X509::Store to be used for verifying peer certificate. attr_accessor :cert_store # Sets or returns the available SSL ciphers. # See {OpenSSL::SSL::SSLContext#ciphers=}[OpenSSL::SSL::SSL::Context#ciphers=]. attr_accessor :ciphers # Sets or returns the extra X509 certificates to be added to the certificate chain. # See {OpenSSL::SSL::SSLContext#add_certificate}[OpenSSL::SSL::SSL::Context#add_certificate]. attr_accessor :extra_chain_cert # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. attr_accessor :key # Sets or returns the SSL timeout seconds. attr_accessor :ssl_timeout # Sets or returns the SSL version. # See {OpenSSL::SSL::SSLContext#ssl_version=}[OpenSSL::SSL::SSL::Context#ssl_version=]. attr_accessor :ssl_version # Sets or returns the minimum SSL version. # See {OpenSSL::SSL::SSLContext#min_version=}[OpenSSL::SSL::SSL::Context#min_version=]. attr_accessor :min_version # Sets or returns the maximum SSL version. # See {OpenSSL::SSL::SSLContext#max_version=}[OpenSSL::SSL::SSL::Context#max_version=]. attr_accessor :max_version # Sets or returns the callback for the server certification verification. attr_accessor :verify_callback # Sets or returns the maximum depth for the certificate chain verification. attr_accessor :verify_depth # Sets or returns the flags for server the certification verification # at the beginning of the SSL/TLS session. # OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable. attr_accessor :verify_mode # Sets or returns whether to verify that the server certificate is valid # for the hostname. # See {OpenSSL::SSL::SSLContext#verify_hostname=}[OpenSSL::SSL::SSL::Context#verify_hostname=]. attr_accessor :verify_hostname # Returns the X509 certificate chain (an array of strings) # for the session's socket peer, # or +nil+ if none. def peer_cert if not use_ssl? or not @socket return nil end @socket.io.peer_cert end # Starts an \HTTP session. # # Without a block, returns +self+: # # http = Gem::Net::HTTP.new(hostname) # # => # # http.start # # => # # http.started? # => true # http.finish # # With a block, calls the block with +self+, # finishes the session when the block exits, # and returns the block's value: # # http.start do |http| # http # end # # => # # http.started? # => false # def start # :yield: http raise IOError, 'HTTP session already opened' if @started if block_given? begin do_start return yield(self) ensure do_finish end end do_start self end def do_start connect @started = true end private :do_start def connect if use_ssl? # reference early to load OpenSSL before connecting, # as OpenSSL may take time to load. @ssl_context = OpenSSL::SSL::SSLContext.new end if proxy? then conn_addr = proxy_address conn_port = proxy_port else conn_addr = conn_address conn_port = port end debug "opening connection to #{conn_addr}:#{conn_port}..." s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { begin TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) rescue => e raise e, "Failed to open TCP connection to " + "#{conn_addr}:#{conn_port} (#{e.message})" end } s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? if proxy? if @proxy_use_ssl proxy_sock = OpenSSL::SSL::SSLSocket.new(s) ssl_socket_connect(proxy_sock, @open_timeout) else proxy_sock = s end proxy_sock = BufferedIO.new(proxy_sock, read_timeout: @read_timeout, write_timeout: @write_timeout, continue_timeout: @continue_timeout, debug_output: @debug_output) buf = +"CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n" \ "Host: #{@address}:#{@port}\r\n" if proxy_user credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0') buf << "Proxy-Authorization: Basic #{credential}\r\n" end buf << "\r\n" proxy_sock.write(buf) HTTPResponse.read_new(proxy_sock).value # assuming nothing left in buffers after successful CONNECT response end ssl_parameters = Hash.new iv_list = instance_variables SSL_IVNAMES.each_with_index do |ivname, i| if iv_list.include?(ivname) value = instance_variable_get(ivname) unless value.nil? ssl_parameters[SSL_ATTRIBUTES[i]] = value end end end @ssl_context.set_params(ssl_parameters) unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby @ssl_context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE end if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } end # Still do the post_connection_check below even if connecting # to IP address verify_hostname = @ssl_context.verify_hostname # Server Name Indication (SNI) RFC 3546/6066 case @address when Gem::Resolv::IPv4::Regex, Gem::Resolv::IPv6::Regex # don't set SNI, as IP addresses in SNI is not valid # per RFC 6066, section 3. # Avoid openssl warning @ssl_context.verify_hostname = false else ssl_host_address = @address end debug "starting SSL for #{conn_addr}:#{conn_port}..." s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) s.sync_close = true s.hostname = ssl_host_address if s.respond_to?(:hostname=) && ssl_host_address if @ssl_session and Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout s.session = @ssl_session end ssl_socket_connect(s, @open_timeout) if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && verify_hostname s.post_connection_check(@address) end debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}" end @socket = BufferedIO.new(s, read_timeout: @read_timeout, write_timeout: @write_timeout, continue_timeout: @continue_timeout, debug_output: @debug_output) @last_communicated = nil on_connect rescue => exception if s debug "Conn close because of connect error #{exception}" s.close end raise end private :connect def on_connect end private :on_connect # Finishes the \HTTP session: # # http = Gem::Net::HTTP.new(hostname) # http.start # http.started? # => true # http.finish # => nil # http.started? # => false # # Raises IOError if not in a session. def finish raise IOError, 'HTTP session not yet started' unless started? do_finish end def do_finish @started = false @socket.close if @socket @socket = nil end private :do_finish # # proxy # public # no proxy @is_proxy_class = false @proxy_from_env = false @proxy_addr = nil @proxy_port = nil @proxy_user = nil @proxy_pass = nil @proxy_use_ssl = nil # Creates an \HTTP proxy class which behaves like \Gem::Net::HTTP, but # performs all access via the specified proxy. # # This class is obsolete. You may pass these same parameters directly to # \Gem::Net::HTTP.new. See Gem::Net::HTTP.new for details of the arguments. def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ssl = nil) #:nodoc: return self unless p_addr Class.new(self) { @is_proxy_class = true if p_addr == :ENV then @proxy_from_env = true @proxy_address = nil @proxy_port = nil else @proxy_from_env = false @proxy_address = p_addr @proxy_port = p_port || default_port end @proxy_user = p_user @proxy_pass = p_pass @proxy_use_ssl = p_use_ssl } end class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? defined?(@is_proxy_class) ? @is_proxy_class : false end # Returns the address of the proxy host, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_address # Returns the port number of the proxy host, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_port # Returns the user name for accessing the proxy, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_user # Returns the password for accessing the proxy, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_pass # Use SSL when talking to the proxy. If Gem::Net::HTTP does not use a proxy, nil. attr_reader :proxy_use_ssl end # Returns +true+ if a proxy server is defined, +false+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy? !!(@proxy_from_env ? proxy_uri : @proxy_address) end # Returns +true+ if the proxy server is defined in the environment, # +false+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_from_env? @proxy_from_env end # The proxy Gem::URI determined from the environment for this connection. def proxy_uri # :nodoc: return if @proxy_uri == false @proxy_uri ||= Gem::URI::HTTP.new( "http", nil, address, port, nil, nil, nil, nil, nil ).find_proxy || false @proxy_uri || nil end # Returns the address of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_address if @proxy_from_env then proxy_uri&.hostname else @proxy_address end end # Returns the port number of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_port if @proxy_from_env then proxy_uri&.port else @proxy_port end end # Returns the user name of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_user if @proxy_from_env user = proxy_uri&.user unescape(user) if user else @proxy_user end end # Returns the password of the proxy server, if defined, +nil+ otherwise; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. def proxy_pass if @proxy_from_env pass = proxy_uri&.password unescape(pass) if pass else @proxy_pass end end alias proxyaddr proxy_address #:nodoc: obsolete alias proxyport proxy_port #:nodoc: obsolete private def unescape(value) require 'cgi/escape' require 'cgi/util' unless defined?(CGI::EscapeExt) CGI.unescape(value) end # without proxy, obsolete def conn_address # :nodoc: @ipaddr || address() end def conn_port # :nodoc: port() end def edit_path(path) if proxy? if path.start_with?("ftp://") || use_ssl? path else "http://#{addr_port}#{path}" end else path end end # # HTTP operations # public # :call-seq: # get(path, initheader = nil) {|res| ... } # # Sends a GET request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Get object # created from string +path+ and initial headers hash +initheader+. # # With a block given, calls the block with the response body: # # http = Gem::Net::HTTP.new(hostname) # http.get('/todos/1') do |res| # p res # end # => # # # Output: # # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}" # # With no block given, simply returns the response object: # # http.get('/') # => # # # Related: # # - Gem::Net::HTTP::Get: request class for \HTTP method GET. # - Gem::Net::HTTP.get: sends GET request, returns response body. # def get(path, initheader = nil, dest = nil, &block) # :yield: +body_segment+ res = nil request(Get.new(path, initheader)) {|r| r.read_body dest, &block res = r } res end # Sends a HEAD request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Head object # created from string +path+ and initial headers hash +initheader+: # # res = http.head('/todos/1') # => # # res.body # => nil # res.to_hash.take(3) # # => # [["date", ["Wed, 15 Feb 2023 15:25:42 GMT"]], # ["content-type", ["application/json; charset=utf-8"]], # ["connection", ["close"]]] # def head(path, initheader = nil) request(Head.new(path, initheader)) end # :call-seq: # post(path, data, initheader = nil) {|res| ... } # # Sends a POST request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Post object # created from string +path+, string +data+, and initial headers hash +initheader+. # # With a block given, calls the block with the response body: # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.post('/todos', data) do |res| # p res # end # => # # # Output: # # "{\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\",\n \"id\": 201\n}" # # With no block given, simply returns the response object: # # http.post('/todos', data) # => # # # Related: # # - Gem::Net::HTTP::Post: request class for \HTTP method POST. # - Gem::Net::HTTP.post: sends POST request, returns response body. # def post(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+ send_entity(path, data, initheader, dest, Post, &block) end # :call-seq: # patch(path, data, initheader = nil) {|res| ... } # # Sends a PATCH request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Patch object # created from string +path+, string +data+, and initial headers hash +initheader+. # # With a block given, calls the block with the response body: # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.patch('/todos/1', data) do |res| # p res # end # => # # # Output: # # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false,\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\"\n}" # # With no block given, simply returns the response object: # # http.patch('/todos/1', data) # => # # def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+ send_entity(path, data, initheader, dest, Patch, &block) end # Sends a PUT request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Put object # created from string +path+, string +data+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.put('/todos/1', data) # => # # # Related: # # - Gem::Net::HTTP::Put: request class for \HTTP method PUT. # - Gem::Net::HTTP.put: sends PUT request, returns response body. # def put(path, data, initheader = nil) request(Put.new(path, initheader), data) end # Sends a PROPPATCH request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Proppatch object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.proppatch('/todos/1', data) # def proppatch(path, body, initheader = nil) request(Proppatch.new(path, initheader), body) end # Sends a LOCK request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Lock object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.lock('/todos/1', data) # def lock(path, body, initheader = nil) request(Lock.new(path, initheader), body) end # Sends an UNLOCK request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Unlock object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.unlock('/todos/1', data) # def unlock(path, body, initheader = nil) request(Unlock.new(path, initheader), body) end # Sends an Options request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Options object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.options('/') # def options(path, initheader = nil) request(Options.new(path, initheader)) end # Sends a PROPFIND request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Propfind object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http = Gem::Net::HTTP.new(hostname) # http.propfind('/todos/1', data) # def propfind(path, body = nil, initheader = {'Depth' => '0'}) request(Propfind.new(path, initheader), body) end # Sends a DELETE request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Delete object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.delete('/todos/1') # def delete(path, initheader = {'Depth' => 'Infinity'}) request(Delete.new(path, initheader)) end # Sends a MOVE request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Move object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.move('/todos/1') # def move(path, initheader = nil) request(Move.new(path, initheader)) end # Sends a COPY request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Copy object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.copy('/todos/1') # def copy(path, initheader = nil) request(Copy.new(path, initheader)) end # Sends a MKCOL request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Mkcol object # created from string +path+, string +body+, and initial headers hash +initheader+. # # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' # http.mkcol('/todos/1', data) # http = Gem::Net::HTTP.new(hostname) # def mkcol(path, body = nil, initheader = nil) request(Mkcol.new(path, initheader), body) end # Sends a TRACE request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Trace object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.trace('/todos/1') # def trace(path, initheader = nil) request(Trace.new(path, initheader)) end # Sends a GET request to the server; # forms the response into a Gem::Net::HTTPResponse object. # # The request is based on the Gem::Net::HTTP::Get object # created from string +path+ and initial headers hash +initheader+. # # With no block given, returns the response object: # # http = Gem::Net::HTTP.new(hostname) # http.request_get('/todos') # => # # # With a block given, calls the block with the response object # and returns the response object: # # http.request_get('/todos') do |res| # p res # end # => # # # Output: # # # # def request_get(path, initheader = nil, &block) # :yield: +response+ request(Get.new(path, initheader), &block) end # Sends a HEAD request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Head object # created from string +path+ and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.head('/todos/1') # => # # def request_head(path, initheader = nil, &block) request(Head.new(path, initheader), &block) end # Sends a POST request to the server; # forms the response into a Gem::Net::HTTPResponse object. # # The request is based on the Gem::Net::HTTP::Post object # created from string +path+, string +data+, and initial headers hash +initheader+. # # With no block given, returns the response object: # # http = Gem::Net::HTTP.new(hostname) # http.post('/todos', 'xyzzy') # # => # # # With a block given, calls the block with the response body # and returns the response object: # # http.post('/todos', 'xyzzy') do |res| # p res # end # => # # # Output: # # "{\n \"xyzzy\": \"\",\n \"id\": 201\n}" # def request_post(path, data, initheader = nil, &block) # :yield: +response+ request Post.new(path, initheader), data, &block end # Sends a PUT request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTP::Put object # created from string +path+, string +data+, and initial headers hash +initheader+. # # http = Gem::Net::HTTP.new(hostname) # http.put('/todos/1', 'xyzzy') # # => # # def request_put(path, data, initheader = nil, &block) #:nodoc: request Put.new(path, initheader), data, &block end alias get2 request_get #:nodoc: obsolete alias head2 request_head #:nodoc: obsolete alias post2 request_post #:nodoc: obsolete alias put2 request_put #:nodoc: obsolete # Sends an \HTTP request to the server; # returns an instance of a subclass of Gem::Net::HTTPResponse. # # The request is based on the Gem::Net::HTTPRequest object # created from string +path+, string +data+, and initial headers hash +header+. # That object is an instance of the # {subclass of Gem::Net::HTTPRequest}[rdoc-ref:Gem::Net::HTTPRequest@Request+Subclasses], # that corresponds to the given uppercase string +name+, # which must be # an {HTTP request method}[https://en.wikipedia.org/wiki/HTTP#Request_methods] # or a {WebDAV request method}[https://en.wikipedia.org/wiki/WebDAV#Implementation]. # # Examples: # # http = Gem::Net::HTTP.new(hostname) # http.send_request('GET', '/todos/1') # # => # # http.send_request('POST', '/todos', 'xyzzy') # # => # # def send_request(name, path, data = nil, header = nil) has_response_body = name != 'HEAD' r = HTTPGenericRequest.new(name,(data ? true : false),has_response_body,path,header) request r, data end # Sends the given request +req+ to the server; # forms the response into a Gem::Net::HTTPResponse object. # # The given +req+ must be an instance of a # {subclass of Gem::Net::HTTPRequest}[rdoc-ref:Gem::Net::HTTPRequest@Request+Subclasses]. # Argument +body+ should be given only if needed for the request. # # With no block given, returns the response object: # # http = Gem::Net::HTTP.new(hostname) # # req = Gem::Net::HTTP::Get.new('/todos/1') # http.request(req) # # => # # # req = Gem::Net::HTTP::Post.new('/todos') # http.request(req, 'xyzzy') # # => # # # With a block given, calls the block with the response and returns the response: # # req = Gem::Net::HTTP::Get.new('/todos/1') # http.request(req) do |res| # p res # end # => # # # Output: # # # # def request(req, body = nil, &block) # :yield: +response+ unless started? start { req['connection'] ||= 'close' return request(req, body, &block) } end if proxy_user() req.proxy_basic_auth proxy_user(), proxy_pass() unless use_ssl? end req.set_body_internal body res = transport_request(req, &block) if sspi_auth?(res) sspi_auth(req) res = transport_request(req, &block) end res end private # Executes a request which uses a representation # and returns its body. def send_entity(path, data, initheader, dest, type, &block) res = nil request(type.new(path, initheader), data) {|r| r.read_body dest, &block res = r } res end IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: def transport_request(req) count = 0 begin begin_transport req res = catch(:response) { begin req.exec @socket, @curr_http_version, edit_path(req.path) rescue Errno::EPIPE # Failure when writing full request, but we can probably # still read the received response. end begin res = HTTPResponse.read_new(@socket) res.decode_content = req.decode_content res.body_encoding = @response_body_encoding res.ignore_eof = @ignore_eof end while res.kind_of?(HTTPInformation) res.uri = req.uri res } res.reading_body(@socket, req.response_body_permitted?) { if block_given? count = max_retries # Don't restart in the middle of a download yield res end } rescue Gem::Net::OpenTimeout raise rescue Gem::Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, Errno::ETIMEDOUT, # avoid a dependency on OpenSSL defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : IOError, Gem::Timeout::Error => exception if count < max_retries && IDEMPOTENT_METHODS_.include?(req.method) count += 1 @socket.close if @socket debug "Conn close because of error #{exception}, and retry" retry end debug "Conn close because of error #{exception}" @socket.close if @socket raise end end_transport req, res res rescue => exception debug "Conn close because of error #{exception}" @socket.close if @socket raise exception end def begin_transport(req) if @socket.closed? connect elsif @last_communicated if @last_communicated + @keep_alive_timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC) debug 'Conn close because of keep_alive_timeout' @socket.close connect elsif @socket.io.to_io.wait_readable(0) && @socket.eof? debug "Conn close because of EOF" @socket.close connect end end if not req.response_body_permitted? and @close_on_empty_response req['connection'] ||= 'close' end req.update_uri address, port, use_ssl? req['host'] ||= addr_port() end def end_transport(req, res) @curr_http_version = res.http_version @last_communicated = nil if @socket.closed? debug 'Conn socket closed' elsif not res.body and @close_on_empty_response debug 'Conn close' @socket.close elsif keep_alive?(req, res) debug 'Conn keep-alive' @last_communicated = Process.clock_gettime(Process::CLOCK_MONOTONIC) else debug 'Conn close' @socket.close end end def keep_alive?(req, res) return false if req.connection_close? if @curr_http_version <= '1.0' res.connection_keep_alive? else # HTTP/1.1 or later not res.connection_close? end end def sspi_auth?(res) return false unless @sspi_enabled if res.kind_of?(HTTPProxyAuthenticationRequired) and proxy? and res["Proxy-Authenticate"].include?("Negotiate") begin require 'win32/sspi' true rescue LoadError false end else false end end def sspi_auth(req) n = Win32::SSPI::NegotiateAuth.new req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}" # Some versions of ISA will close the connection if this isn't present. req["Connection"] = "Keep-Alive" req["Proxy-Connection"] = "Keep-Alive" res = transport_request(req) authphrase = res["Proxy-Authenticate"] or return res req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication(authphrase)}" rescue => err raise HTTPAuthenticationError.new('HTTP authentication failed', err) end # # utils # private def addr_port addr = address addr = "[#{addr}]" if addr.include?(":") default_port = use_ssl? ? HTTP.https_default_port : HTTP.http_default_port default_port == port ? addr : "#{addr}:#{port}" end # Adds a message to debugging output def debug(msg) return unless @debug_output @debug_output << msg @debug_output << "\n" end alias_method :D, :debug end # for backward compatibility until Ruby 3.5 # https://bugs.ruby-lang.org/issues/20900 # https://github.com/bblimke/webmock/pull/1081 HTTPSession = HTTP deprecate_constant :HTTPSession end require_relative 'http/exceptions' require_relative 'http/header' require_relative 'http/generic_request' require_relative 'http/request' require_relative 'http/requests' require_relative 'http/response' require_relative 'http/responses' require_relative 'http/proxy_delta' PK!  'vendor/net-http/lib/net/http/request.rbnu[# frozen_string_literal: true # This class is the base class for \Gem::Net::HTTP request classes. # The class should not be used directly; # instead you should use its subclasses, listed below. # # == Creating a Request # # An request object may be created with either a Gem::URI or a string hostname: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('https://jsonplaceholder.typicode.com/') # req = Gem::Net::HTTP::Get.new(uri) # => # # req = Gem::Net::HTTP::Get.new(uri.hostname) # => # # # And with any of the subclasses: # # req = Gem::Net::HTTP::Head.new(uri) # => # # req = Gem::Net::HTTP::Post.new(uri) # => # # req = Gem::Net::HTTP::Put.new(uri) # => # # # ... # # The new instance is suitable for use as the argument to Gem::Net::HTTP#request. # # == Request Headers # # A new request object has these header fields by default: # # req.to_hash # # => # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], # "accept"=>["*/*"], # "user-agent"=>["Ruby"], # "host"=>["jsonplaceholder.typicode.com"]} # # See: # # - {Request header Accept-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Accept-Encoding] # and {Compression and Decompression}[rdoc-ref:Gem::Net::HTTP@Compression+and+Decompression]. # - {Request header Accept}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#accept-request-header]. # - {Request header User-Agent}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#user-agent-request-header]. # - {Request header Host}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#host-request-header]. # # You can add headers or override default headers: # # # res = Gem::Net::HTTP::Get.new(uri, {'foo' => '0', 'bar' => '1'}) # # This class (and therefore its subclasses) also includes (indirectly) # module Gem::Net::HTTPHeader, which gives access to its # {methods for setting headers}[rdoc-ref:Gem::Net::HTTPHeader@Setters]. # # == Request Subclasses # # Subclasses for HTTP requests: # # - Gem::Net::HTTP::Get # - Gem::Net::HTTP::Head # - Gem::Net::HTTP::Post # - Gem::Net::HTTP::Put # - Gem::Net::HTTP::Delete # - Gem::Net::HTTP::Options # - Gem::Net::HTTP::Trace # - Gem::Net::HTTP::Patch # # Subclasses for WebDAV requests: # # - Gem::Net::HTTP::Propfind # - Gem::Net::HTTP::Proppatch # - Gem::Net::HTTP::Mkcol # - Gem::Net::HTTP::Copy # - Gem::Net::HTTP::Move # - Gem::Net::HTTP::Lock # - Gem::Net::HTTP::Unlock # class Gem::Net::HTTPRequest < Gem::Net::HTTPGenericRequest # Creates an HTTP request object for +path+. # # +initheader+ are the default headers to use. Gem::Net::HTTP adds # Accept-Encoding to enable compression of the response body unless # Accept-Encoding or Range are supplied in +initheader+. def initialize(path, initheader = nil) super self.class::METHOD, self.class::REQUEST_HAS_BODY, self.class::RESPONSE_HAS_BODY, path, initheader end end PK!?sDII(vendor/net-http/lib/net/http/backward.rbnu[# frozen_string_literal: true # for backward compatibility # :enddoc: class Gem::Net::HTTP ProxyMod = ProxyDelta deprecate_constant :ProxyMod end module Gem::Net::NetPrivate HTTPRequest = ::Gem::Net::HTTPRequest deprecate_constant :HTTPRequest end module Gem::Net HTTPSession = HTTP HTTPInformationCode = HTTPInformation HTTPSuccessCode = HTTPSuccess HTTPRedirectionCode = HTTPRedirection HTTPRetriableCode = HTTPRedirection HTTPClientErrorCode = HTTPClientError HTTPFatalErrorCode = HTTPClientError HTTPServerErrorCode = HTTPServerError HTTPResponseReceiver = HTTPResponse HTTPResponceReceiver = HTTPResponse # Typo since 2001 deprecate_constant :HTTPSession, :HTTPInformationCode, :HTTPSuccessCode, :HTTPRedirectionCode, :HTTPRetriableCode, :HTTPClientErrorCode, :HTTPFatalErrorCode, :HTTPServerErrorCode, :HTTPResponseReceiver, :HTTPResponceReceiver end PK!M?i+vendor/net-http/lib/net/http/proxy_delta.rbnu[# frozen_string_literal: true module Gem::Net::HTTP::ProxyDelta #:nodoc: internal use only private def conn_address proxy_address() end def conn_port proxy_port() end def edit_path(path) use_ssl? ? path : "http://#{addr_port()}#{path}" end end PK!L22/vendor/net-http/lib/net/http/generic_request.rbnu[# frozen_string_literal: true # # \HTTPGenericRequest is the parent of the Gem::Net::HTTPRequest class. # # Do not use this directly; instead, use a subclass of Gem::Net::HTTPRequest. # # == About the Examples # # :include: doc/net-http/examples.rdoc # class Gem::Net::HTTPGenericRequest include Gem::Net::HTTPHeader def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: @method = m @request_has_body = reqbody @response_has_body = resbody if Gem::URI === uri_or_path then raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path hostname = uri_or_path.hostname raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup host = @uri.hostname.dup host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup end @decode_content = false if Gem::Net::HTTP::HAVE_ZLIB then if !initheader || !initheader.keys.any? { |k| %w[accept-encoding range].include? k.downcase } then @decode_content = true if @response_has_body initheader = initheader ? initheader.dup : {} initheader["accept-encoding"] = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" end end initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' self['Host'] ||= host if host @body = nil @body_stream = nil @body_data = nil end # Returns the string method name for the request: # # Gem::Net::HTTP::Get.new(uri).method # => "GET" # Gem::Net::HTTP::Post.new(uri).method # => "POST" # attr_reader :method # Returns the string path for the request: # # Gem::Net::HTTP::Get.new(uri).path # => "/" # Gem::Net::HTTP::Post.new('example.com').path # => "example.com" # attr_reader :path # Returns the Gem::URI object for the request, or +nil+ if none: # # Gem::Net::HTTP::Get.new(uri).uri # # => # # Gem::Net::HTTP::Get.new('example.com').uri # => nil # attr_reader :uri # Returns +false+ if the request's header 'Accept-Encoding' # has been set manually or deleted # (indicating that the user intends to handle encoding in the response), # +true+ otherwise: # # req = Gem::Net::HTTP::Get.new(uri) # => # # req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" # req.decode_content # => true # req['Accept-Encoding'] = 'foo' # req.decode_content # => false # req.delete('Accept-Encoding') # req.decode_content # => false # attr_reader :decode_content # Returns a string representation of the request: # # Gem::Net::HTTP::Post.new(uri).inspect # => "#" # def inspect "\#<#{self.class} #{@method}>" end # Returns a string representation of the request with the details for pp: # # require 'pp' # post = Gem::Net::HTTP::Post.new(uri) # post.inspect # => "#" # post.pretty_inspect # # => # ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], # "accept" => ["*/*"], # "user-agent" => ["Ruby"], # "host" => ["www.ruby-lang.org"]}> # def pretty_print(q) q.object_group(self) { q.breakable q.text @method q.breakable q.text "path="; q.pp @path q.breakable q.text "headers="; q.pp to_hash } end ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. def []=(key, val) # :nodoc: @decode_content = false if key.downcase == 'accept-encoding' super key, val end # Returns whether the request may have a body: # # Gem::Net::HTTP::Post.new(uri).request_body_permitted? # => true # Gem::Net::HTTP::Get.new(uri).request_body_permitted? # => false # def request_body_permitted? @request_has_body end # Returns whether the response may have a body: # # Gem::Net::HTTP::Post.new(uri).response_body_permitted? # => true # Gem::Net::HTTP::Head.new(uri).response_body_permitted? # => false # def response_body_permitted? @response_has_body end def body_exist? # :nodoc: warn "Gem::Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE response_body_permitted? end # Returns the string body for the request, or +nil+ if there is none: # # req = Gem::Net::HTTP::Post.new(uri) # req.body # => nil # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" # attr_reader :body # Sets the body for the request: # # req = Gem::Net::HTTP::Post.new(uri) # req.body # => nil # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" # def body=(str) @body = str @body_stream = nil @body_data = nil str end # Returns the body stream object for the request, or +nil+ if there is none: # # req = Gem::Net::HTTP::Post.new(uri) # => # # req.body_stream # => nil # require 'stringio' # req.body_stream = StringIO.new('xyzzy') # => # # req.body_stream # => # # attr_reader :body_stream # Sets the body stream for the request: # # req = Gem::Net::HTTP::Post.new(uri) # => # # req.body_stream # => nil # require 'stringio' # req.body_stream = StringIO.new('xyzzy') # => # # req.body_stream # => # # def body_stream=(input) @body = nil @body_stream = input @body_data = nil input end def set_body_internal(str) #:nodoc: internal use only raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream) self.body = str if str if @body.nil? && @body_stream.nil? && @body_data.nil? && request_body_permitted? self.body = '' end end # # write # def exec(sock, ver, path) #:nodoc: internal use only if @body send_request_with_body sock, ver, path, @body elsif @body_stream send_request_with_body_stream sock, ver, path, @body_stream elsif @body_data send_request_with_body_data sock, ver, path, @body_data else write_header sock, ver, path end end def update_uri(addr, port, ssl) # :nodoc: internal use only # reflect the connection and @path to @uri return unless @uri if ssl scheme = 'https' klass = Gem::URI::HTTPS else scheme = 'http' klass = Gem::URI::HTTP end if host = self['host'] host.sub!(/:.*/m, '') elsif host = @uri.host else host = addr end # convert the class of the Gem::URI if @uri.is_a?(klass) @uri.host = host @uri.port = port else @uri = klass.new( scheme, @uri.userinfo, host, port, nil, @uri.path, nil, @uri.query, nil) end end private class Chunker #:nodoc: def initialize(sock) @sock = sock @prev = nil end def write(buf) # avoid memcpy() of buf, buf can huge and eat memory bandwidth rv = buf.bytesize @sock.write("#{rv.to_s(16)}\r\n", buf, "\r\n") rv end def finish @sock.write("0\r\n\r\n") end end def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body end def send_request_with_body_stream(sock, ver, path, f) unless content_length() or chunked? raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? chunker = Chunker.new(sock) IO.copy_stream(f, chunker) chunker.finish else IO.copy_stream(f, sock) end end def send_request_with_body_data(sock, ver, path, params) if /\Amultipart\/form-data\z/i !~ self.content_type self.content_type = 'application/x-www-form-urlencoded' return send_request_with_body(sock, ver, path, Gem::URI.encode_www_form(params)) end opt = @form_option.dup require 'securerandom' unless defined?(SecureRandom) opt[:boundary] ||= SecureRandom.urlsafe_base64(40) self.set_content_type(self.content_type, boundary: opt[:boundary]) if chunked? write_header sock, ver, path encode_multipart_form_data(sock, params, opt) else require 'tempfile' file = Tempfile.new('multipart') file.binmode encode_multipart_form_data(file, params, opt) file.rewind self.content_length = file.size write_header sock, ver, path IO.copy_stream(file, sock) file.close(true) end end def encode_multipart_form_data(out, params, opt) charset = opt[:charset] boundary = opt[:boundary] require 'securerandom' unless defined?(SecureRandom) boundary ||= SecureRandom.urlsafe_base64(40) chunked_p = chunked? buf = +'' params.each do |key, value, h={}| key = quote_string(key, charset) filename = h.key?(:filename) ? h[:filename] : value.respond_to?(:to_path) ? File.basename(value.to_path) : nil buf << "--#{boundary}\r\n" if filename filename = quote_string(filename, charset) type = h[:content_type] || 'application/octet-stream' buf << "Content-Disposition: form-data; " \ "name=\"#{key}\"; filename=\"#{filename}\"\r\n" \ "Content-Type: #{type}\r\n\r\n" if !out.respond_to?(:write) || !value.respond_to?(:read) # if +out+ is not an IO or +value+ is not an IO buf << (value.respond_to?(:read) ? value.read : value) elsif value.respond_to?(:size) && chunked_p # if +out+ is an IO and +value+ is a File, use IO.copy_stream flush_buffer(out, buf, chunked_p) out << "%x\r\n" % value.size if chunked_p IO.copy_stream(value, out) out << "\r\n" if chunked_p else # +out+ is an IO, and +value+ is not a File but an IO flush_buffer(out, buf, chunked_p) 1 while flush_buffer(out, value.read(4096), chunked_p) end else # non-file field: # HTML5 says, "The parts of the generated multipart/form-data # resource that correspond to non-file fields must not have a # Content-Type header specified." buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n" buf << (value.respond_to?(:read) ? value.read : value) end buf << "\r\n" end buf << "--#{boundary}--\r\n" flush_buffer(out, buf, chunked_p) out << "0\r\n\r\n" if chunked_p end def quote_string(str, charset) str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset str.gsub(/[\\"]/, '\\\\\&') end def flush_buffer(out, buf, chunked_p) return unless buf out << "%x\r\n"%buf.bytesize if chunked_p out << buf out << "\r\n" if chunked_p buf.clear end ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. def wait_for_continue(sock, ver) if ver >= '1.1' and @header['expect'] and @header['expect'].include?('100-continue') if sock.io.to_io.wait_readable(sock.continue_timeout) res = Gem::Net::HTTPResponse.read_new(sock) unless res.kind_of?(Gem::Net::HTTPContinue) res.decode_content = @decode_content throw :response, res end end end end def write_header(sock, ver, path) reqline = "#{@method} #{path} HTTP/#{ver}" if /[\r\n]/ =~ reqline raise ArgumentError, "A Request-Line must not contain CR or LF" end buf = +'' buf << reqline << "\r\n" each_capitalized do |k,v| buf << "#{k}: #{v}\r\n" end buf << "\r\n" sock.write buf end end PK!q^ 8 8(vendor/net-http/lib/net/http/requests.rbnu[# frozen_string_literal: true # HTTP/1.1 methods --- RFC2616 # \Class for representing # {HTTP method GET}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#GET_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Get.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. # # Related: # # - Gem::Net::HTTP.get: sends +GET+ request, returns response body. # - Gem::Net::HTTP#get: sends +GET+ request, returns response object. # class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method HEAD}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#HEAD_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Head.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: no. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. # # Related: # # - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object. # class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false end # \Class for representing # {HTTP method POST}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#POST_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts' # req = Gem::Net::HTTP::Post.new(uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: yes. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. # # Related: # # - Gem::Net::HTTP.post: sends +POST+ request, returns response object. # - Gem::Net::HTTP#post: sends +POST+ request, returns response object. # class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method PUT}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PUT_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts' # req = Gem::Net::HTTP::Put.new(uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: yes. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP.put: sends +PUT+ request, returns response object. # - Gem::Net::HTTP#put: sends +PUT+ request, returns response object. # class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method DELETE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#DELETE_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts/1' # req = Gem::Net::HTTP::Delete.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method OPTIONS}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#OPTIONS_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Options.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: optional. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method TRACE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#TRACE_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Trace.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: no. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {HTTP method PATCH}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PATCH_method]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # uri.path = '/posts' # req = Gem::Net::HTTP::Patch.new(uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Properties: # # - Request body: yes. # - Response body: yes. # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # # Related: # # - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # # WebDAV methods --- RFC2518 # # \Class for representing # {WebDAV method PROPFIND}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Propfind.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method PROPPATCH}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Proppatch.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method MKCOL}[http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Mkcol.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method COPY}[http://www.webdav.org/specs/rfc4918.html#METHOD_COPY]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Copy.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object. # class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method MOVE}[http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Move.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object. # class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method LOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Lock.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end # \Class for representing # {WebDAV method UNLOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK]: # # require 'rubygems/vendor/net-http/lib/net/http' # uri = Gem::URI('http://example.com') # hostname = uri.hostname # => "example.com" # req = Gem::Net::HTTP::Unlock.new(uri) # => # # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # # See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers]. # # Related: # # - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end PK!`9d &vendor/net-http/lib/net/http/status.rbnu[# frozen_string_literal: true require_relative '../http' if $0 == __FILE__ require 'open-uri' File.foreach(__FILE__) do |line| puts line break if line.start_with?('end') end puts puts "Gem::Net::HTTP::STATUS_CODES = {" url = "https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv" Gem::URI(url).read.each_line do |line| code, mes, = line.split(',') next if ['(Unused)', 'Unassigned', 'Description'].include?(mes) puts " #{code} => '#{mes}'," end puts "} # :nodoc:" end Gem::Net::HTTP::STATUS_CODES = { 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 103 => 'Early Hints', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Content Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 421 => 'Misdirected Request', 422 => 'Unprocessable Content', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Too Early', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended (OBSOLETED)', 511 => 'Network Authentication Required', } # :nodoc: PK! TT*vendor/net-http/lib/net/http/exceptions.rbnu[# frozen_string_literal: true module Gem::Net # Gem::Net::HTTP exception class. # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use # its subclasses. module HTTPExceptions def initialize(msg, res) #:nodoc: super msg @response = res end attr_reader :response alias data response #:nodoc: obsolete end class HTTPError < ProtocolError include HTTPExceptions end class HTTPRetriableError < ProtoRetriableError include HTTPExceptions end class HTTPClientException < ProtoServerError include HTTPExceptions end class HTTPFatalError < ProtoFatalError include HTTPExceptions end # We cannot use the name "HTTPServerError", it is the name of the response. HTTPServerException = HTTPClientException # :nodoc: deprecate_constant(:HTTPServerException) end PK!݌&vendor/net-http/lib/net/http/header.rbnu[# frozen_string_literal: true # # The \HTTPHeader module provides access to \HTTP headers. # # The module is included in: # # - Gem::Net::HTTPGenericRequest (and therefore Gem::Net::HTTPRequest). # - Gem::Net::HTTPResponse. # # The headers are a hash-like collection of key/value pairs called _fields_. # # == Request and Response Fields # # Headers may be included in: # # - A Gem::Net::HTTPRequest object: # the object's headers will be sent with the request. # Any fields may be defined in the request; # see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters]. # - A Gem::Net::HTTPResponse object: # the objects headers are usually those returned from the host. # Fields may be retrieved from the object; # see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters] # and {Iterators}[rdoc-ref:Gem::Net::HTTPHeader@Iterators]. # # Exactly which fields should be sent or expected depends on the host; # see: # # - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. # - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields]. # # == About the Examples # # :include: doc/net-http/examples.rdoc # # == Fields # # A header field is a key/value pair. # # === Field Keys # # A field key may be: # # - A string: Key 'Accept' is treated as if it were # 'Accept'.downcase; i.e., 'accept'. # - A symbol: Key :Accept is treated as if it were # :Accept.to_s.downcase; i.e., 'accept'. # # Examples: # # req = Gem::Net::HTTP::Get.new(uri) # req[:accept] # => "*/*" # req['Accept'] # => "*/*" # req['ACCEPT'] # => "*/*" # # req['accept'] = 'text/html' # req[:accept] = 'text/html' # req['ACCEPT'] = 'text/html' # # === Field Values # # A field value may be returned as an array of strings or as a string: # # - These methods return field values as arrays: # # - #get_fields: Returns the array value for the given key, # or +nil+ if it does not exist. # - #to_hash: Returns a hash of all header fields: # each key is a field name; its value is the array value for the field. # # - These methods return field values as string; # the string value for a field is equivalent to # self[key.downcase.to_s].join(', ')): # # - #[]: Returns the string value for the given key, # or +nil+ if it does not exist. # - #fetch: Like #[], but accepts a default value # to be returned if the key does not exist. # # The field value may be set: # # - #[]=: Sets the value for the given key; # the given value may be a string, a symbol, an array, or a hash. # - #add_field: Adds a given value to a value for the given key # (not overwriting the existing value). # - #delete: Deletes the field for the given key. # # Example field values: # # - \String: # # req['Accept'] = 'text/html' # => "text/html" # req['Accept'] # => "text/html" # req.get_fields('Accept') # => ["text/html"] # # - \Symbol: # # req['Accept'] = :text # => :text # req['Accept'] # => "text" # req.get_fields('Accept') # => ["text"] # # - Simple array: # # req[:foo] = %w[bar baz bat] # req[:foo] # => "bar, baz, bat" # req.get_fields(:foo) # => ["bar", "baz", "bat"] # # - Simple hash: # # req[:foo] = {bar: 0, baz: 1, bat: 2} # req[:foo] # => "bar, 0, baz, 1, bat, 2" # req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"] # # - Nested: # # req[:foo] = [%w[bar baz], {bat: 0, bam: 1}] # req[:foo] # => "bar, baz, bat, 0, bam, 1" # req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"] # # req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}} # req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1" # req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"] # # == Convenience Methods # # Various convenience methods retrieve values, set values, query values, # set form values, or iterate over fields. # # === Setters # # \Method #[]= can set any field, but does little to validate the new value; # some of the other setter methods provide some validation: # # - #[]=: Sets the string or array value for the given key. # - #add_field: Creates or adds to the array value for the given key. # - #basic_auth: Sets the string authorization header for 'Authorization'. # - #content_length=: Sets the integer length for field 'Content-Length. # - #content_type=: Sets the string value for field 'Content-Type'. # - #proxy_basic_auth: Sets the string authorization header for 'Proxy-Authorization'. # - #set_range: Sets the value for field 'Range'. # # === Form Setters # # - #set_form: Sets an HTML form data set. # - #set_form_data: Sets header fields and a body from HTML form data. # # === Getters # # \Method #[] can retrieve the value of any field that exists, # but always as a string; # some of the other getter methods return something different # from the simple string value: # # - #[]: Returns the string field value for the given key. # - #content_length: Returns the integer value of field 'Content-Length'. # - #content_range: Returns the Range value of field 'Content-Range'. # - #content_type: Returns the string value of field 'Content-Type'. # - #fetch: Returns the string field value for the given key. # - #get_fields: Returns the array field value for the given +key+. # - #main_type: Returns first part of the string value of field 'Content-Type'. # - #sub_type: Returns second part of the string value of field 'Content-Type'. # - #range: Returns an array of Range objects of field 'Range', or +nil+. # - #range_length: Returns the integer length of the range given in field 'Content-Range'. # - #type_params: Returns the string parameters for 'Content-Type'. # # === Queries # # - #chunked?: Returns whether field 'Transfer-Encoding' is set to 'chunked'. # - #connection_close?: Returns whether field 'Connection' is set to 'close'. # - #connection_keep_alive?: Returns whether field 'Connection' is set to 'keep-alive'. # - #key?: Returns whether a given key exists. # # === Iterators # # - #each_capitalized: Passes each field capitalized-name/value pair to the block. # - #each_capitalized_name: Passes each capitalized field name to the block. # - #each_header: Passes each field name/value pair to the block. # - #each_name: Passes each field name to the block. # - #each_value: Passes each string field value to the block. # module Gem::Net::HTTPHeader MAX_KEY_LENGTH = 1024 MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @header = {} return unless initheader initheader.each do |key, value| warn "net/http: duplicated HTTP header: #{key}", uplevel: 3 if key?(key) and $VERBOSE if value.nil? warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE else value = value.strip # raise error for invalid byte sequences if key.to_s.bytesize > MAX_KEY_LENGTH raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..." end if value.to_s.bytesize > MAX_FIELD_LENGTH raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}" end if value.count("\r\n") > 0 raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF" end @header[key.downcase.to_s] = [value] end end end def size #:nodoc: obsolete @header.size end alias length size #:nodoc: obsolete # Returns the string field value for the case-insensitive field +key+, # or +nil+ if there is no such key; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Connection'] # => "keep-alive" # res['Nosuch'] # => nil # # Note that some field values may be retrieved via convenience methods; # see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters]. def [](key) a = @header[key.downcase.to_s] or return nil a.join(', ') end # Sets the value for the case-insensitive +key+ to +val+, # overwriting the previous value if the field exists; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # req = Gem::Net::HTTP::Get.new(uri) # req['Accept'] # => "*/*" # req['Accept'] = 'text/html' # req['Accept'] # => "text/html" # # Note that some field values may be set via convenience methods; # see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters]. def []=(key, val) unless val @header.delete key.downcase.to_s return val end set_field(key, val) end # Adds value +val+ to the value array for field +key+ if the field exists; # creates the field with the given +key+ and +val+ if it does not exist. # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # req = Gem::Net::HTTP::Get.new(uri) # req.add_field('Foo', 'bar') # req['Foo'] # => "bar" # req.add_field('Foo', 'baz') # req['Foo'] # => "bar, baz" # req.add_field('Foo', %w[baz bam]) # req['Foo'] # => "bar, baz, baz, bam" # req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"] # def add_field(key, val) stringified_downcased_key = key.downcase.to_s if @header.key?(stringified_downcased_key) append_field_value(@header[stringified_downcased_key], val) else set_field(key, val) end end private def set_field(key, val) case val when Enumerable ary = [] append_field_value(ary, val) @header[key.downcase.to_s] = ary else val = val.to_s # for compatibility use to_s instead of to_str if val.b.count("\r\n") > 0 raise ArgumentError, 'header field value cannot include CR/LF' end @header[key.downcase.to_s] = [val] end end private def append_field_value(ary, val) case val when Enumerable val.each{|x| append_field_value(ary, x)} else val = val.to_s if /[\r\n]/n.match?(val.b) raise ArgumentError, 'header field value cannot include CR/LF' end ary.push val end end # Returns the array field value for the given +key+, # or +nil+ if there is no such field; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.get_fields('Connection') # => ["keep-alive"] # res.get_fields('Nosuch') # => nil # def get_fields(key) stringified_downcased_key = key.downcase.to_s return nil unless @header[stringified_downcased_key] @header[stringified_downcased_key].dup end # call-seq: # fetch(key, default_val = nil) {|key| ... } -> object # fetch(key, default_val = nil) -> value or default_val # # With a block, returns the string value for +key+ if it exists; # otherwise returns the value of the block; # ignores the +default_val+; # see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # # # Field exists; block not called. # res.fetch('Connection') do |value| # fail 'Cannot happen' # end # => "keep-alive" # # # Field does not exist; block called. # res.fetch('Nosuch') do |value| # value.downcase # end # => "nosuch" # # With no block, returns the string value for +key+ if it exists; # otherwise, returns +default_val+ if it was given; # otherwise raises an exception: # # res.fetch('Connection', 'Foo') # => "keep-alive" # res.fetch('Nosuch', 'Foo') # => "Foo" # res.fetch('Nosuch') # Raises KeyError. # def fetch(key, *args, &block) #:yield: +key+ a = @header.fetch(key.downcase.to_s, *args, &block) a.kind_of?(Array) ? a.join(', ') : a end # Calls the block with each key/value pair: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_header do |key, value| # p [key, value] if key.start_with?('c') # end # # Output: # # ["content-type", "application/json; charset=utf-8"] # ["connection", "keep-alive"] # ["cache-control", "max-age=43200"] # ["cf-cache-status", "HIT"] # ["cf-ray", "771d17e9bc542cf5-ORD"] # # Returns an enumerator if no block is given. # # Gem::Net::HTTPHeader#each is an alias for Gem::Net::HTTPHeader#each_header. def each_header #:yield: +key+, +value+ block_given? or return enum_for(__method__) { @header.size } @header.each do |k,va| yield k, va.join(', ') end end alias each each_header # Calls the block with each field key: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_key do |key| # p key if key.start_with?('c') # end # # Output: # # "content-type" # "connection" # "cache-control" # "cf-cache-status" # "cf-ray" # # Returns an enumerator if no block is given. # # Gem::Net::HTTPHeader#each_name is an alias for Gem::Net::HTTPHeader#each_key. def each_name(&block) #:yield: +key+ block_given? or return enum_for(__method__) { @header.size } @header.each_key(&block) end alias each_key each_name # Calls the block with each capitalized field name: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_capitalized_name do |key| # p key if key.start_with?('C') # end # # Output: # # "Content-Type" # "Connection" # "Cache-Control" # "Cf-Cache-Status" # "Cf-Ray" # # The capitalization is system-dependent; # see {Case Mapping}[https://docs.ruby-lang.org/en/master/case_mapping_rdoc.html]. # # Returns an enumerator if no block is given. def each_capitalized_name #:yield: +key+ block_given? or return enum_for(__method__) { @header.size } @header.each_key do |k| yield capitalize(k) end end # Calls the block with each string field value: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.each_value do |value| # p value if value.start_with?('c') # end # # Output: # # "chunked" # "cf-q-config;dur=6.0000002122251e-06" # "cloudflare" # # Returns an enumerator if no block is given. def each_value #:yield: +value+ block_given? or return enum_for(__method__) { @header.size } @header.each_value do |va| yield va.join(', ') end end # Removes the header for the given case-insensitive +key+ # (see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]); # returns the deleted value, or +nil+ if no such field exists: # # req = Gem::Net::HTTP::Get.new(uri) # req.delete('Accept') # => ["*/*"] # req.delete('Nosuch') # => nil # def delete(key) @header.delete(key.downcase.to_s) end # Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise: # # req = Gem::Net::HTTP::Get.new(uri) # req.key?('Accept') # => true # req.key?('Nosuch') # => false # def key?(key) @header.key?(key.downcase.to_s) end # Returns a hash of the key/value pairs: # # req = Gem::Net::HTTP::Get.new(uri) # req.to_hash # # => # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], # "accept"=>["*/*"], # "user-agent"=>["Ruby"], # "host"=>["jsonplaceholder.typicode.com"]} # def to_hash @header.dup end # Like #each_header, but the keys are returned in capitalized form. # # Gem::Net::HTTPHeader#canonical_each is an alias for Gem::Net::HTTPHeader#each_capitalized. def each_capitalized block_given? or return enum_for(__method__) { @header.size } @header.each do |k,v| yield capitalize(k), v.join(', ') end end alias canonical_each each_capitalized def capitalize(name) name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize # Returns an array of Range objects that represent # the value of field 'Range', # or +nil+ if there is no such field; # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: # # req = Gem::Net::HTTP::Get.new(uri) # req['Range'] = 'bytes=0-99,200-299,400-499' # req.range # => [0..99, 200..299, 400..499] # req.delete('Range') # req.range # # => nil # def range return nil unless @header['range'] value = self['Range'] # byte-range-set = *( "," OWS ) ( byte-range-spec / suffix-byte-range-spec ) # *( OWS "," [ OWS ( byte-range-spec / suffix-byte-range-spec ) ] ) # corrected collected ABNF # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#section-5.4.1 # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#appendix-C # http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-19#section-3.2.5 unless /\Abytes=((?:,[ \t]*)*(?:\d+-\d*|-\d+)(?:[ \t]*,(?:[ \t]*\d+-\d*|-\d+)?)*)\z/ =~ value raise Gem::Net::HTTPHeaderSyntaxError, "invalid syntax for byte-ranges-specifier: '#{value}'" end byte_range_set = $1 result = byte_range_set.split(/,/).map {|spec| m = /(\d+)?\s*-\s*(\d+)?/i.match(spec) or raise Gem::Net::HTTPHeaderSyntaxError, "invalid byte-range-spec: '#{spec}'" d1 = m[1].to_i d2 = m[2].to_i if m[1] and m[2] if d1 > d2 raise Gem::Net::HTTPHeaderSyntaxError, "last-byte-pos MUST greater than or equal to first-byte-pos but '#{spec}'" end d1..d2 elsif m[1] d1..-1 elsif m[2] -d2..-1 else raise Gem::Net::HTTPHeaderSyntaxError, 'range is not specified' end } # if result.empty? # byte-range-set must include at least one byte-range-spec or suffix-byte-range-spec # but above regexp already denies it. if result.size == 1 && result[0].begin == 0 && result[0].end == -1 raise Gem::Net::HTTPHeaderSyntaxError, 'only one suffix-byte-range-spec with zero suffix-length' end result end # call-seq: # set_range(length) -> length # set_range(offset, length) -> range # set_range(begin..length) -> range # # Sets the value for field 'Range'; # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: # # With argument +length+: # # req = Gem::Net::HTTP::Get.new(uri) # req.set_range(100) # => 100 # req['Range'] # => "bytes=0-99" # # With arguments +offset+ and +length+: # # req.set_range(100, 100) # => 100...200 # req['Range'] # => "bytes=100-199" # # With argument +range+: # # req.set_range(100..199) # => 100..199 # req['Range'] # => "bytes=100-199" # # Gem::Net::HTTPHeader#range= is an alias for Gem::Net::HTTPHeader#set_range. def set_range(r, e = nil) unless r @header.delete 'range' return r end r = (r...r+e) if e case r when Numeric n = r.to_i rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}") when Range first = r.first last = r.end last -= 1 if r.exclude_end? if last == -1 rangestr = (first > 0 ? "#{first}-" : "-#{-first}") else raise Gem::Net::HTTPHeaderSyntaxError, 'range.first is negative' if first < 0 raise Gem::Net::HTTPHeaderSyntaxError, 'range.last is negative' if last < 0 raise Gem::Net::HTTPHeaderSyntaxError, 'must be .first < .last' if first > last rangestr = "#{first}-#{last}" end else raise TypeError, 'Range/Integer is required' end @header['range'] = ["bytes=#{rangestr}"] r end alias range= set_range # Returns the value of field 'Content-Length' as an integer, # or +nil+ if there is no such field; # see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/nosuch/1') # res.content_length # => 2 # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res.content_length # => nil # def content_length return nil unless key?('Content-Length') len = self['Content-Length'].slice(/\d+/) or raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Length format' len.to_i end # Sets the value of field 'Content-Length' to the given numeric; # see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]: # # _uri = uri.dup # hostname = _uri.hostname # => "jsonplaceholder.typicode.com" # _uri.path = '/posts' # => "/posts" # req = Gem::Net::HTTP::Post.new(_uri) # => # # req.body = '{"title": "foo","body": "bar","userId": 1}' # req.content_length = req.body.size # => 42 # req.content_type = 'application/json' # res = Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # => # # def content_length=(len) unless len @header.delete 'content-length' return nil end @header['content-length'] = [len.to_i.to_s] end # Returns +true+ if field 'Transfer-Encoding' # exists and has value 'chunked', # +false+ otherwise; # see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Transfer-Encoding'] # => "chunked" # res.chunked? # => true # def chunked? return false unless @header['transfer-encoding'] field = self['Transfer-Encoding'] (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false end # Returns a Range object representing the value of field # 'Content-Range', or +nil+ if no such field exists; # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Content-Range'] # => nil # res['Content-Range'] = 'bytes 0-499/1000' # res['Content-Range'] # => "bytes 0-499/1000" # res.content_range # => 0..499 # def content_range return nil unless @header['content-range'] m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Range format' return unless m[1] == 'bytes' m[2].to_i .. m[3].to_i end # Returns the integer representing length of the value of field # 'Content-Range', or +nil+ if no such field exists; # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['Content-Range'] # => nil # res['Content-Range'] = 'bytes 0-499/1000' # res.range_length # => 500 # def range_length r = content_range() or return nil r.end - r.begin + 1 end # Returns the {media type}[https://en.wikipedia.org/wiki/Media_type] # from the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.content_type # => "application/json" # def content_type main = main_type() return nil unless main sub = sub_type() if sub "#{main}/#{sub}" else main end end # Returns the leading ('type') part of the # {media type}[https://en.wikipedia.org/wiki/Media_type] # from the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.main_type # => "application" # def main_type return nil unless @header['content-type'] self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip end # Returns the trailing ('subtype') part of the # {media type}[https://en.wikipedia.org/wiki/Media_type] # from the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.sub_type # => "json" # def sub_type return nil unless @header['content-type'] _, sub = *self['Content-Type'].split(';').first.to_s.split('/') return nil unless sub sub.strip end # Returns the trailing ('parameters') part of the value of field 'Content-Type', # or +nil+ if no such field exists; # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: # # res = Gem::Net::HTTP.get_response(hostname, '/todos/1') # res['content-type'] # => "application/json; charset=utf-8" # res.type_params # => {"charset"=>"utf-8"} # def type_params result = {} list = self['Content-Type'].to_s.split(';') list.shift list.each do |param| k, v = *param.split('=', 2) result[k.strip] = v.strip end result end # Sets the value of field 'Content-Type'; # returns the new value; # see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]: # # req = Gem::Net::HTTP::Get.new(uri) # req.set_content_type('application/json') # => ["application/json"] # # Gem::Net::HTTPHeader#content_type= is an alias for Gem::Net::HTTPHeader#set_content_type. def set_content_type(type, params = {}) @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')] end alias content_type= set_content_type # Sets the request body to a URL-encoded string derived from argument +params+, # and sets request header field 'Content-Type' # to 'application/x-www-form-urlencoded'. # # The resulting request is suitable for HTTP request +POST+ or +PUT+. # # Argument +params+ must be suitable for use as argument +enum+ to # {Gem::URI.encode_www_form}[https://docs.ruby-lang.org/en/master/Gem::URI.html#method-c-encode_www_form]. # # With only argument +params+ given, # sets the body to a URL-encoded string with the default separator '&': # # req = Gem::Net::HTTP::Post.new('example.com') # # req.set_form_data(q: 'ruby', lang: 'en') # req.body # => "q=ruby&lang=en" # req['Content-Type'] # => "application/x-www-form-urlencoded" # # req.set_form_data([['q', 'ruby'], ['lang', 'en']]) # req.body # => "q=ruby&lang=en" # # req.set_form_data(q: ['ruby', 'perl'], lang: 'en') # req.body # => "q=ruby&q=perl&lang=en" # # req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']]) # req.body # => "q=ruby&q=perl&lang=en" # # With string argument +sep+ also given, # uses that string as the separator: # # req.set_form_data({q: 'ruby', lang: 'en'}, '|') # req.body # => "q=ruby|lang=en" # # Gem::Net::HTTPHeader#form_data= is an alias for Gem::Net::HTTPHeader#set_form_data. def set_form_data(params, sep = '&') query = Gem::URI.encode_www_form(params) query.gsub!(/&/, sep) if sep != '&' self.body = query self.content_type = 'application/x-www-form-urlencoded' end alias form_data= set_form_data # Stores form data to be used in a +POST+ or +PUT+ request. # # The form data given in +params+ consists of zero or more fields; # each field is: # # - A scalar value. # - A name/value pair. # - An IO stream opened for reading. # # Argument +params+ should be an # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes] # (method params.map will be called), # and is often an array or hash. # # First, we set up a request: # # _uri = uri.dup # _uri.path ='/posts' # req = Gem::Net::HTTP::Post.new(_uri) # # Argument +params+ As an Array # # When +params+ is an array, # each of its elements is a subarray that defines a field; # the subarray may contain: # # - One string: # # req.set_form([['foo'], ['bar'], ['baz']]) # # - Two strings: # # req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]]) # # - When argument +enctype+ (see below) is given as # 'multipart/form-data': # # - A string name and an IO stream opened for reading: # # require 'stringio' # req.set_form([['file', StringIO.new('Ruby is cool.')]]) # # - A string name, an IO stream opened for reading, # and an options hash, which may contain these entries: # # - +:filename+: The name of the file to use. # - +:content_type+: The content type of the uploaded file. # # Example: # # req.set_form([['file', file, {filename: "other-filename.foo"}]] # # The various forms may be mixed: # # req.set_form(['foo', %w[bar 1], ['file', file]]) # # Argument +params+ As a Hash # # When +params+ is a hash, # each of its entries is a name/value pair that defines a field: # # - The name is a string. # - The value may be: # # - +nil+. # - Another string. # - An IO stream opened for reading # (only when argument +enctype+ -- see below -- is given as # 'multipart/form-data'). # # Examples: # # # Nil-valued fields. # req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil}) # # # String-valued fields. # req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2}) # # # IO-valued field. # require 'stringio' # req.set_form({'file' => StringIO.new('Ruby is cool.')}) # # # Mixture of fields. # req.set_form({'foo' => nil, 'bar' => 1, 'file' => file}) # # Optional argument +enctype+ specifies the value to be given # to field 'Content-Type', and must be one of: # # - 'application/x-www-form-urlencoded' (the default). # - 'multipart/form-data'; # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578]. # # Optional argument +formopt+ is a hash of options # (applicable only when argument +enctype+ # is 'multipart/form-data') # that may include the following entries: # # - +:boundary+: The value is the boundary string for the multipart message. # If not given, the boundary is a random string. # See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1]. # - +:charset+: Value is the character set for the form submission. # Field names and values of non-file fields should be encoded with this charset. # def set_form(params, enctype='application/x-www-form-urlencoded', formopt={}) @body_data = params @body = nil @body_stream = nil @form_option = formopt case enctype when /\Aapplication\/x-www-form-urlencoded\z/i, /\Amultipart\/form-data\z/i self.content_type = enctype else raise ArgumentError, "invalid enctype: #{enctype}" end end # Sets header 'Authorization' using the given # +account+ and +password+ strings: # # req.basic_auth('my_account', 'my_password') # req['Authorization'] # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" # def basic_auth(account, password) @header['authorization'] = [basic_encode(account, password)] end # Sets header 'Proxy-Authorization' using the given # +account+ and +password+ strings: # # req.proxy_basic_auth('my_account', 'my_password') # req['Proxy-Authorization'] # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" # def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end def basic_encode(account, password) 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @header['proxy-connection']&.grep(token) {return true} false end # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @header['proxy-connection']&.grep(token) {return true} false end end PK!)l~)vendor/net-http/lib/net/http/responses.rbnu[# frozen_string_literal: true #-- # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml module Gem::Net class HTTPUnknownResponse < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPError # end # Parent class for informational (1xx) HTTP response classes. # # An informational response indicates that the request was received and understood. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.1xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse HAS_BODY = false EXCEPTION_TYPE = HTTPError # end # Parent class for success (2xx) HTTP response classes. # # A success response indicates the action requested by the client # was received, understood, and accepted. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.2xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPError # end # Parent class for redirection (3xx) HTTP response classes. # # A redirection response indicates the client must take additional action # to complete the request. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.3xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end # Parent class for client error (4xx) HTTP response classes. # # A client error response indicates that the client may have caused an error. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.4xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end # Parent class for server error (5xx) HTTP response classes. # # A server error response indicates that the server failed to fulfill a request. # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.5xx]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end # Response class for +Continue+ responses (status code 100). # # A +Continue+ response indicates that the server has received the request headers. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-100-continue]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation HAS_BODY = false end # Response class for Switching Protocol responses (status code 101). # # The Switching Protocol response indicates that the server has received # a request to switch protocols, and has agreed to do so. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation HAS_BODY = false end # Response class for +Processing+ responses (status code 102). # # The +Processing+ response indicates that the server has received # and is processing the request, but no response is available yet. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 2518}[https://www.rfc-editor.org/rfc/rfc2518#section-10.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation HAS_BODY = false end # Response class for Early Hints responses (status code 103). # # The Early Hints indicates that the server has received # and is processing the request, and contains certain headers; # the final response is not available yet. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103]. # - {RFC 8297}[https://www.rfc-editor.org/rfc/rfc8297.html#section-2]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation HAS_BODY = false end # Response class for +OK+ responses (status code 200). # # The +OK+ response indicates that the server has received # a request and has responded successfully. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess HAS_BODY = true end # Response class for +Created+ responses (status code 201). # # The +Created+ response indicates that the server has received # and has fulfilled a request to create a new resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-201-created]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess HAS_BODY = true end # Response class for +Accepted+ responses (status code 202). # # The +Accepted+ response indicates that the server has received # and is processing a request, but the processing has not yet been completed. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-202-accepted]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess HAS_BODY = true end # Response class for Non-Authoritative Information responses (status code 203). # # The Non-Authoritative Information response indicates that the server # is a transforming proxy (such as a Web accelerator) # that received a 200 OK response from its origin, # and is returning a modified version of the origin's response. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/203]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-203-non-authoritative-infor]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess HAS_BODY = true end # Response class for No Content responses (status code 204). # # The No Content response indicates that the server # successfully processed the request, and is not returning any content. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-204-no-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess HAS_BODY = false end # Response class for Reset Content responses (status code 205). # # The Reset Content response indicates that the server # successfully processed the request, # asks that the client reset its document view, and is not returning any content. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/205]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-205-reset-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess HAS_BODY = false end # Response class for Partial Content responses (status code 206). # # The Partial Content response indicates that the server is delivering # only part of the resource (byte serving) # due to a Range header in the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-206-partial-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess HAS_BODY = true end # Response class for Multi-Status (WebDAV) responses (status code 207). # # The Multi-Status (WebDAV) response indicates that the server # has received the request, # and that the message body can contain a number of separate response codes. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 4818}[https://www.rfc-editor.org/rfc/rfc4918#section-11.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess HAS_BODY = true end # Response class for Already Reported (WebDAV) responses (status code 208). # # The Already Reported (WebDAV) response indicates that the server # has received the request, # and that the members of a DAV binding have already been enumerated # in a preceding part of the (multi-status) response, # and are not being included again. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 5842}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess HAS_BODY = true end # Response class for IM Used responses (status code 226). # # The IM Used response indicates that the server has fulfilled a request # for the resource, and the response is a representation of the result # of one or more instance-manipulations applied to the current instance. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 3229}[https://www.rfc-editor.org/rfc/rfc3229.html#section-10.4.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess HAS_BODY = true end # Response class for Multiple Choices responses (status code 300). # # The Multiple Choices response indicates that the server # offers multiple options for the resource from which the client may choose. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/300]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-300-multiple-choices]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices # Response class for Moved Permanently responses (status code 301). # # The Moved Permanently response indicates that links or records # returning this response should be updated to use the given URL. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-301-moved-permanently]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection HAS_BODY = true end # Response class for Found responses (status code 302). # # The Found response indicates that the client # should look at (browse to) another URL. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-302-found]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection HAS_BODY = true end HTTPMovedTemporarily = HTTPFound # Response class for See Other responses (status code 303). # # The response to the request can be found under another Gem::URI using the GET method. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-303-see-other]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection HAS_BODY = true end # Response class for Not Modified responses (status code 304). # # Indicates that the resource has not been modified since the version # specified by the request headers. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection HAS_BODY = false end # Response class for Use Proxy responses (status code 305). # # The requested resource is available only through a proxy, # whose address is provided in the response. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-305-use-proxy]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection HAS_BODY = false end # Response class for Temporary Redirect responses (status code 307). # # The request should be repeated with another Gem::URI; # however, future requests should still use the original Gem::URI. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-307-temporary-redirect]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection HAS_BODY = true end # Response class for Permanent Redirect responses (status code 308). # # This and all future requests should be directed to the given Gem::URI. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-308-permanent-redirect]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection HAS_BODY = true end # Response class for Bad Request responses (status code 400). # # The server cannot or will not process the request due to an apparent client error. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError HAS_BODY = true end # Response class for Unauthorized responses (status code 401). # # Authentication is required, but either was not provided or failed. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-401-unauthorized]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError HAS_BODY = true end # Response class for Payment Required responses (status code 402). # # Reserved for future use. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-402-payment-required]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError HAS_BODY = true end # Response class for Forbidden responses (status code 403). # # The request contained valid data and was understood by the server, # but the server is refusing action. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError HAS_BODY = true end # Response class for Not Found responses (status code 404). # # The requested resource could not be found but may be available in the future. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-404-not-found]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError HAS_BODY = true end # Response class for Method Not Allowed responses (status code 405). # # The request method is not supported for the requested resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-405-method-not-allowed]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError HAS_BODY = true end # Response class for Not Acceptable responses (status code 406). # # The requested resource is capable of generating only content # that not acceptable according to the Accept headers sent in the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-406-not-acceptable]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError HAS_BODY = true end # Response class for Proxy Authentication Required responses (status code 407). # # The client must first authenticate itself with the proxy. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-407-proxy-authentication-re]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError HAS_BODY = true end # Response class for Request Gem::Timeout responses (status code 408). # # The server timed out waiting for the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-408-request-timeout]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout # Response class for Conflict responses (status code 409). # # The request could not be processed because of conflict in the current state of the resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError HAS_BODY = true end # Response class for Gone responses (status code 410). # # The resource requested was previously in use but is no longer available # and will not be available again. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-410-gone]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError HAS_BODY = true end # Response class for Length Required responses (status code 411). # # The request did not specify the length of its content, # which is required by the requested resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-411-length-required]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError HAS_BODY = true end # Response class for Precondition Failed responses (status code 412). # # The server does not meet one of the preconditions # specified in the request headers. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-412-precondition-failed]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError HAS_BODY = true end # Response class for Payload Too Large responses (status code 413). # # The request is larger than the server is willing or able to process. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge # Response class for Gem::URI Too Long responses (status code 414). # # The Gem::URI provided was too long for the server to process. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-414-uri-too-long]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong HTTPRequestURITooLarge = HTTPRequestURITooLong # Response class for Unsupported Media Type responses (status code 415). # # The request entity has a media type which the server or resource does not support. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-415-unsupported-media-type]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError HAS_BODY = true end # Response class for Range Not Satisfiable responses (status code 416). # # The request entity has a media type which the server or resource does not support. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-416-range-not-satisfiable]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable # Response class for Expectation Failed responses (status code 417). # # The server cannot meet the requirements of the Expect request-header field. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-417-expectation-failed]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError HAS_BODY = true end # 418 I'm a teapot - RFC 2324; a joke RFC # See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#418. # 420 Enhance Your Calm - Twitter # Response class for Misdirected Request responses (status code 421). # # The request was directed at a server that is not able to produce a response. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-421-misdirected-request]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError HAS_BODY = true end # Response class for Unprocessable Entity responses (status code 422). # # The request was well-formed but had semantic errors. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-422-unprocessable-content]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError HAS_BODY = true end # Response class for Locked (WebDAV) responses (status code 423). # # The requested resource is locked. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.3]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError HAS_BODY = true end # Response class for Failed Dependency (WebDAV) responses (status code 424). # # The request failed because it depended on another request and that request failed. # See {424 Failed Dependency (WebDAV)}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.4]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError HAS_BODY = true end # 425 Too Early # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#425. # Response class for Upgrade Required responses (status code 426). # # The client should switch to the protocol given in the Upgrade header field. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-426-upgrade-required]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError HAS_BODY = true end # Response class for Precondition Required responses (status code 428). # # The origin server requires the request to be conditional. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/428]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-3]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError HAS_BODY = true end # Response class for Too Many Requests responses (status code 429). # # The user has sent too many requests in a given amount of time. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-4]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError HAS_BODY = true end # Response class for Request Header Fields Too Large responses (status code 431). # # An individual header field is too large, # or all the header fields collectively, are too large. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-5]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError HAS_BODY = true end # Response class for Unavailable For Legal Reasons responses (status code 451). # # A server operator has received a legal demand to deny access to a resource or to a set of resources # that includes the requested resource. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/451]. # - {RFC 7725}[https://www.rfc-editor.org/rfc/rfc7725.html#section-3]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError HAS_BODY = true end # 444 No Response - Nginx # 449 Retry With - Microsoft # 450 Blocked by Windows Parental Controls - Microsoft # 499 Client Closed Request - Nginx # Response class for Internal Server Error responses (status code 500). # # An unexpected condition was encountered and no more specific message is suitable. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-500-internal-server-error]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError HAS_BODY = true end # Response class for Not Implemented responses (status code 501). # # The server either does not recognize the request method, # or it lacks the ability to fulfil the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-501-not-implemented]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError HAS_BODY = true end # Response class for Bad Gateway responses (status code 502). # # The server was acting as a gateway or proxy # and received an invalid response from the upstream server. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError HAS_BODY = true end # Response class for Service Unavailable responses (status code 503). # # The server cannot handle the request # (because it is overloaded or down for maintenance). # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError HAS_BODY = true end # Response class for Gateway Gem::Timeout responses (status code 504). # # The server was acting as a gateway or proxy # and did not receive a timely response from the upstream server. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout # Response class for HTTP Version Not Supported responses (status code 505). # # The server does not support the HTTP version used in the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505]. # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-505-http-version-not-suppor]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError HAS_BODY = true end # Response class for Variant Also Negotiates responses (status code 506). # # Transparent content negotiation for the request results in a circular reference. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/506]. # - {RFC 2295}[https://www.rfc-editor.org/rfc/rfc2295#section-8.1]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError HAS_BODY = true end # Response class for Insufficient Storage (WebDAV) responses (status code 507). # # The server is unable to store the representation needed to complete the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/507]. # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.5]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError HAS_BODY = true end # Response class for Loop Detected (WebDAV) responses (status code 508). # # The server detected an infinite loop while processing the request. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508]. # - {RFC 5942}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.2]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension # Response class for Not Extended responses (status code 510). # # Further extensions to the request are required for the server to fulfill it. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/510]. # - {RFC 2774}[https://www.rfc-editor.org/rfc/rfc2774.html#section-7]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError HAS_BODY = true end # Response class for Network Authentication Required responses (status code 511). # # The client needs to authenticate to gain network access. # # :include: doc/net-http/included_getters.rdoc # # References: # # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511]. # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-6]. # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError HAS_BODY = true end end class Gem::Net::HTTPResponse CODE_CLASS_TO_OBJ = { '1' => Gem::Net::HTTPInformation, '2' => Gem::Net::HTTPSuccess, '3' => Gem::Net::HTTPRedirection, '4' => Gem::Net::HTTPClientError, '5' => Gem::Net::HTTPServerError }.freeze CODE_TO_OBJ = { '100' => Gem::Net::HTTPContinue, '101' => Gem::Net::HTTPSwitchProtocol, '102' => Gem::Net::HTTPProcessing, '103' => Gem::Net::HTTPEarlyHints, '200' => Gem::Net::HTTPOK, '201' => Gem::Net::HTTPCreated, '202' => Gem::Net::HTTPAccepted, '203' => Gem::Net::HTTPNonAuthoritativeInformation, '204' => Gem::Net::HTTPNoContent, '205' => Gem::Net::HTTPResetContent, '206' => Gem::Net::HTTPPartialContent, '207' => Gem::Net::HTTPMultiStatus, '208' => Gem::Net::HTTPAlreadyReported, '226' => Gem::Net::HTTPIMUsed, '300' => Gem::Net::HTTPMultipleChoices, '301' => Gem::Net::HTTPMovedPermanently, '302' => Gem::Net::HTTPFound, '303' => Gem::Net::HTTPSeeOther, '304' => Gem::Net::HTTPNotModified, '305' => Gem::Net::HTTPUseProxy, '307' => Gem::Net::HTTPTemporaryRedirect, '308' => Gem::Net::HTTPPermanentRedirect, '400' => Gem::Net::HTTPBadRequest, '401' => Gem::Net::HTTPUnauthorized, '402' => Gem::Net::HTTPPaymentRequired, '403' => Gem::Net::HTTPForbidden, '404' => Gem::Net::HTTPNotFound, '405' => Gem::Net::HTTPMethodNotAllowed, '406' => Gem::Net::HTTPNotAcceptable, '407' => Gem::Net::HTTPProxyAuthenticationRequired, '408' => Gem::Net::HTTPRequestTimeout, '409' => Gem::Net::HTTPConflict, '410' => Gem::Net::HTTPGone, '411' => Gem::Net::HTTPLengthRequired, '412' => Gem::Net::HTTPPreconditionFailed, '413' => Gem::Net::HTTPPayloadTooLarge, '414' => Gem::Net::HTTPURITooLong, '415' => Gem::Net::HTTPUnsupportedMediaType, '416' => Gem::Net::HTTPRangeNotSatisfiable, '417' => Gem::Net::HTTPExpectationFailed, '421' => Gem::Net::HTTPMisdirectedRequest, '422' => Gem::Net::HTTPUnprocessableEntity, '423' => Gem::Net::HTTPLocked, '424' => Gem::Net::HTTPFailedDependency, '426' => Gem::Net::HTTPUpgradeRequired, '428' => Gem::Net::HTTPPreconditionRequired, '429' => Gem::Net::HTTPTooManyRequests, '431' => Gem::Net::HTTPRequestHeaderFieldsTooLarge, '451' => Gem::Net::HTTPUnavailableForLegalReasons, '500' => Gem::Net::HTTPInternalServerError, '501' => Gem::Net::HTTPNotImplemented, '502' => Gem::Net::HTTPBadGateway, '503' => Gem::Net::HTTPServiceUnavailable, '504' => Gem::Net::HTTPGatewayTimeout, '505' => Gem::Net::HTTPVersionNotSupported, '506' => Gem::Net::HTTPVariantAlsoNegotiates, '507' => Gem::Net::HTTPInsufficientStorage, '508' => Gem::Net::HTTPLoopDetected, '510' => Gem::Net::HTTPNotExtended, '511' => Gem::Net::HTTPNetworkAuthenticationRequired, }.freeze end PK!T,u`NN(vendor/net-http/lib/net/http/response.rbnu[# frozen_string_literal: true # This class is the base class for \Gem::Net::HTTP response classes. # # == About the Examples # # :include: doc/net-http/examples.rdoc # # == Returned Responses # # \Method Gem::Net::HTTP.get_response returns # an instance of one of the subclasses of \Gem::Net::HTTPResponse: # # Gem::Net::HTTP.get_response(uri) # # => # # Gem::Net::HTTP.get_response(hostname, '/nosuch') # # => # # # As does method Gem::Net::HTTP#request: # # req = Gem::Net::HTTP::Get.new(uri) # Gem::Net::HTTP.start(hostname) do |http| # http.request(req) # end # => # # # \Class \Gem::Net::HTTPResponse includes module Gem::Net::HTTPHeader, # which provides access to response header values via (among others): # # - \Hash-like method []. # - Specific reader methods, such as +content_type+. # # Examples: # # res = Gem::Net::HTTP.get_response(uri) # => # # res['Content-Type'] # => "text/html; charset=UTF-8" # res.content_type # => "text/html" # # == Response Subclasses # # \Class \Gem::Net::HTTPResponse has a subclass for each # {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes]. # You can look up the response class for a given code: # # Gem::Net::HTTPResponse::CODE_TO_OBJ['200'] # => Gem::Net::HTTPOK # Gem::Net::HTTPResponse::CODE_TO_OBJ['400'] # => Gem::Net::HTTPBadRequest # Gem::Net::HTTPResponse::CODE_TO_OBJ['404'] # => Gem::Net::HTTPNotFound # # And you can retrieve the status code for a response object: # # Gem::Net::HTTP.get_response(uri).code # => "200" # Gem::Net::HTTP.get_response(hostname, '/nosuch').code # => "404" # # The response subclasses (indentation shows class hierarchy): # # - Gem::Net::HTTPUnknownResponse (for unhandled \HTTP extensions). # # - Gem::Net::HTTPInformation: # # - Gem::Net::HTTPContinue (100) # - Gem::Net::HTTPSwitchProtocol (101) # - Gem::Net::HTTPProcessing (102) # - Gem::Net::HTTPEarlyHints (103) # # - Gem::Net::HTTPSuccess: # # - Gem::Net::HTTPOK (200) # - Gem::Net::HTTPCreated (201) # - Gem::Net::HTTPAccepted (202) # - Gem::Net::HTTPNonAuthoritativeInformation (203) # - Gem::Net::HTTPNoContent (204) # - Gem::Net::HTTPResetContent (205) # - Gem::Net::HTTPPartialContent (206) # - Gem::Net::HTTPMultiStatus (207) # - Gem::Net::HTTPAlreadyReported (208) # - Gem::Net::HTTPIMUsed (226) # # - Gem::Net::HTTPRedirection: # # - Gem::Net::HTTPMultipleChoices (300) # - Gem::Net::HTTPMovedPermanently (301) # - Gem::Net::HTTPFound (302) # - Gem::Net::HTTPSeeOther (303) # - Gem::Net::HTTPNotModified (304) # - Gem::Net::HTTPUseProxy (305) # - Gem::Net::HTTPTemporaryRedirect (307) # - Gem::Net::HTTPPermanentRedirect (308) # # - Gem::Net::HTTPClientError: # # - Gem::Net::HTTPBadRequest (400) # - Gem::Net::HTTPUnauthorized (401) # - Gem::Net::HTTPPaymentRequired (402) # - Gem::Net::HTTPForbidden (403) # - Gem::Net::HTTPNotFound (404) # - Gem::Net::HTTPMethodNotAllowed (405) # - Gem::Net::HTTPNotAcceptable (406) # - Gem::Net::HTTPProxyAuthenticationRequired (407) # - Gem::Net::HTTPRequestTimeOut (408) # - Gem::Net::HTTPConflict (409) # - Gem::Net::HTTPGone (410) # - Gem::Net::HTTPLengthRequired (411) # - Gem::Net::HTTPPreconditionFailed (412) # - Gem::Net::HTTPRequestEntityTooLarge (413) # - Gem::Net::HTTPRequestURITooLong (414) # - Gem::Net::HTTPUnsupportedMediaType (415) # - Gem::Net::HTTPRequestedRangeNotSatisfiable (416) # - Gem::Net::HTTPExpectationFailed (417) # - Gem::Net::HTTPMisdirectedRequest (421) # - Gem::Net::HTTPUnprocessableEntity (422) # - Gem::Net::HTTPLocked (423) # - Gem::Net::HTTPFailedDependency (424) # - Gem::Net::HTTPUpgradeRequired (426) # - Gem::Net::HTTPPreconditionRequired (428) # - Gem::Net::HTTPTooManyRequests (429) # - Gem::Net::HTTPRequestHeaderFieldsTooLarge (431) # - Gem::Net::HTTPUnavailableForLegalReasons (451) # # - Gem::Net::HTTPServerError: # # - Gem::Net::HTTPInternalServerError (500) # - Gem::Net::HTTPNotImplemented (501) # - Gem::Net::HTTPBadGateway (502) # - Gem::Net::HTTPServiceUnavailable (503) # - Gem::Net::HTTPGatewayTimeOut (504) # - Gem::Net::HTTPVersionNotSupported (505) # - Gem::Net::HTTPVariantAlsoNegotiates (506) # - Gem::Net::HTTPInsufficientStorage (507) # - Gem::Net::HTTPLoopDetected (508) # - Gem::Net::HTTPNotExtended (510) # - Gem::Net::HTTPNetworkAuthenticationRequired (511) # # There is also the Gem::Net::HTTPBadResponse exception which is raised when # there is a protocol error. # class Gem::Net::HTTPResponse class << self # true if the response has a body. def body_permitted? self::HAS_BODY end def exception_type # :nodoc: internal use only self::EXCEPTION_TYPE end def read_new(sock) #:nodoc: internal use only httpv, code, msg = read_status_line(sock) res = response_class(code).new(httpv, code, msg) each_response_header(sock) do |k,v| res.add_field k, v end res end private def read_status_line(sock) str = sock.readline m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\z/in.match(str) or raise Gem::Net::HTTPBadResponse, "wrong status line: #{str.dump}" m.captures end def response_class(code) CODE_TO_OBJ[code] or CODE_CLASS_TO_OBJ[code[0,1]] or Gem::Net::HTTPUnknownResponse end def each_response_header(sock) key = value = nil while true line = sock.readuntil("\n", true).sub(/\s+\z/, '') break if line.empty? if line[0] == ?\s or line[0] == ?\t and value value << ' ' unless value.empty? value << line.strip else yield key, value if key key, value = line.strip.split(/\s*:\s*/, 2) raise Gem::Net::HTTPBadResponse, 'wrong header line format' if value.nil? end end yield key, value if key end end # next is to fix bug in RDoc, where the private inside class << self # spills out. public include Gem::Net::HTTPHeader def initialize(httpv, code, msg) #:nodoc: internal use only @http_version = httpv @code = code @message = msg initialize_http_header nil @body = nil @read = false @uri = nil @decode_content = false @body_encoding = false @ignore_eof = true end # The HTTP version supported by the server. attr_reader :http_version # The HTTP result code string. For example, '302'. You can also # determine the response type by examining which response subclass # the response object is an instance of. attr_reader :code # The HTTP result message sent by the server. For example, 'Not Found'. attr_reader :message alias msg message # :nodoc: obsolete # The Gem::URI used to fetch this response. The response Gem::URI is only available # if a Gem::URI was used to create the request. attr_reader :uri # Set to true automatically when the request did not contain an # Accept-Encoding header from the user. attr_accessor :decode_content # Returns the value set by body_encoding=, or +false+ if none; # see #body_encoding=. attr_reader :body_encoding # Sets the encoding that should be used when reading the body: # # - If the given value is an Encoding object, that encoding will be used. # - Otherwise if the value is a string, the value of # {Encoding#find(value)}[https://docs.ruby-lang.org/en/master/Encoding.html#method-c-find] # will be used. # - Otherwise an encoding will be deduced from the body itself. # # Examples: # # http = Gem::Net::HTTP.new(hostname) # req = Gem::Net::HTTP::Get.new('/') # # http.request(req) do |res| # p res.body.encoding # => # # end # # http.request(req) do |res| # res.body_encoding = "UTF-8" # p res.body.encoding # => # # end # def body_encoding=(value) value = Encoding.find(value) if value.is_a?(String) @body_encoding = value end # Whether to ignore EOF when reading bodies with a specified Content-Length # header. attr_accessor :ignore_eof def inspect "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end # # response <-> exception relationship # def code_type #:nodoc: self.class end def error! #:nodoc: message = @code message = "#{message} #{@message.dump}" if @message raise error_type().new(message, self) end def error_type #:nodoc: self.class::EXCEPTION_TYPE end # Raises an HTTP error if the response is not 2xx (success). def value error! unless self.kind_of?(Gem::Net::HTTPSuccess) end def uri= uri # :nodoc: @uri = uri.dup if uri end # # header (for backward compatibility only; DO NOT USE) # def response #:nodoc: warn "Gem::Net::HTTPResponse#response is obsolete", uplevel: 1 if $VERBOSE self end def header #:nodoc: warn "Gem::Net::HTTPResponse#header is obsolete", uplevel: 1 if $VERBOSE self end def read_header #:nodoc: warn "Gem::Net::HTTPResponse#read_header is obsolete", uplevel: 1 if $VERBOSE self end # # body # def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only @socket = sock @body_exist = reqmethodallowbody && self.class.body_permitted? begin yield self.body # ensure to read body ensure @socket = nil end end # Gets the entity body returned by the remote HTTP server. # # If a block is given, the body is passed to the block, and # the body is provided in fragments, as it is read in from the socket. # # If +dest+ argument is given, response is read into that variable, # with dest#<< method (it could be String or IO, or any # other object responding to <<). # # Calling this method a second or subsequent time for the same # HTTPResponse object will return the value already read. # # http.request_get('/index.html') {|res| # puts res.read_body # } # # http.request_get('/index.html') {|res| # p res.read_body.object_id # 538149362 # p res.read_body.object_id # 538149362 # } # # # using iterator # http.request_get('/index.html') {|res| # res.read_body do |segment| # print segment # end # } # def read_body(dest = nil, &block) if @read raise IOError, "#{self.class}\#read_body called twice" if dest or block return @body end to = procdest(dest, block) stream_check if @body_exist read_body_0 to @body = to else @body = nil end @read = true return if @body.nil? case enc = @body_encoding when Encoding, false, nil # Encoding: force given encoding # false/nil: do not force encoding else # other value: detect encoding from body enc = detect_encoding(@body) end @body.force_encoding(enc) if enc @body end # Returns the string response body; # note that repeated calls for the unmodified body return a cached string: # # path = '/todos/1' # Gem::Net::HTTP.start(hostname) do |http| # res = http.get(path) # p res.body # p http.head(path).body # No body. # end # # Output: # # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}" # nil # def body read_body() end # Sets the body of the response to the given value. def body=(value) @body = value end alias entity body #:nodoc: obsolete private # :nodoc: def detect_encoding(str, encoding=nil) if encoding elsif encoding = type_params['charset'] elsif encoding = check_bom(str) else encoding = case content_type&.downcase when %r{text/x(?:ht)?ml|application/(?:[^+]+\+)?xml} /\A' ss.getch return nil end name = ss.scan(/[^=\t\n\f\r \/>]*/) name.downcase! raise if name.empty? ss.skip(/[\t\n\f\r ]*/) if ss.getch != '=' value = '' return [name, value] end ss.skip(/[\t\n\f\r ]*/) case ss.peek(1) when '"' ss.getch value = ss.scan(/[^"]+/) value.downcase! ss.getch when "'" ss.getch value = ss.scan(/[^']+/) value.downcase! ss.getch when '>' value = '' else value = ss.scan(/[^\t\n\f\r >]+/) value.downcase! end [name, value] end def extracting_encodings_from_meta_elements(value) # http://dev.w3.org/html5/spec/fetching-resources.html#algorithm-for-extracting-an-encoding-from-a-meta-element if /charset[\t\n\f\r ]*=(?:"([^"]*)"|'([^']*)'|["']|\z|([^\t\n\f\r ;]+))/i =~ value return $1 || $2 || $3 end return nil end ## # Checks for a supported Content-Encoding header and yields an Inflate # wrapper for this response's socket when zlib is present. If the # Content-Encoding is not supported or zlib is missing, the plain socket is # yielded. # # If a Content-Range header is present, a plain socket is yielded as the # bytes in the range may not be a complete deflate block. def inflater # :nodoc: return yield @socket unless Gem::Net::HTTP::HAVE_ZLIB return yield @socket unless @decode_content return yield @socket if self['content-range'] v = self['content-encoding'] case v&.downcase when 'deflate', 'gzip', 'x-gzip' then self.delete 'content-encoding' inflate_body_io = Inflater.new(@socket) begin yield inflate_body_io success = true ensure begin inflate_body_io.finish if self['content-length'] self['content-length'] = inflate_body_io.bytes_inflated.to_s end rescue => err # Ignore #finish's error if there is an exception from yield raise err if success end end when 'none', 'identity' then self.delete 'content-encoding' yield @socket else yield @socket end end def read_body_0(dest) inflater do |inflate_body_io| if chunked? read_chunked dest, inflate_body_io return end @socket = inflate_body_io clen = content_length() if clen @socket.read clen, dest, @ignore_eof return end clen = range_length() if clen @socket.read clen, dest return end @socket.read_all dest end end ## # read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF, # etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip # encoded. # # See RFC 2616 section 3.6.1 for definitions def read_chunked(dest, chunk_data_io) # :nodoc: total = 0 while true line = @socket.readline hexlen = line.slice(/[0-9a-fA-F]+/) or raise Gem::Net::HTTPBadResponse, "wrong chunk size line: #{line}" len = hexlen.hex break if len == 0 begin chunk_data_io.read len, dest ensure total += len @socket.read 2 # \r\n end end until @socket.readline.empty? # none end end def stream_check raise IOError, 'attempt to read body out of block' if @socket.nil? || @socket.closed? end def procdest(dest, block) raise ArgumentError, 'both arg and block given for HTTP method' if dest and block if block Gem::Net::ReadAdapter.new(block) else dest || +'' end end ## # Inflater is a wrapper around Gem::Net::BufferedIO that transparently inflates # zlib and gzip streams. class Inflater # :nodoc: ## # Creates a new Inflater wrapping +socket+ def initialize socket @socket = socket # zlib with automatic gzip detection @inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS) end ## # Finishes the inflate stream. def finish return if @inflate.total_in == 0 @inflate.finish end ## # The number of bytes inflated, used to update the Content-Length of # the response. def bytes_inflated @inflate.total_out end ## # Returns a Gem::Net::ReadAdapter that inflates each read chunk into +dest+. # # This allows a large response body to be inflated without storing the # entire body in memory. def inflate_adapter(dest) if dest.respond_to?(:set_encoding) dest.set_encoding(Encoding::ASCII_8BIT) elsif dest.respond_to?(:force_encoding) dest.force_encoding(Encoding::ASCII_8BIT) end block = proc do |compressed_chunk| @inflate.inflate(compressed_chunk) do |chunk| compressed_chunk.clear dest << chunk end end Gem::Net::ReadAdapter.new(block) end ## # Reads +clen+ bytes from the socket, inflates them, then writes them to # +dest+. +ignore_eof+ is passed down to Gem::Net::BufferedIO#read # # Unlike Gem::Net::BufferedIO#read, this method returns more than +clen+ bytes. # At this time there is no way for a user of Gem::Net::HTTPResponse to read a # specific number of bytes from the HTTP response body, so this internal # API does not return the same number of bytes as were requested. # # See https://bugs.ruby-lang.org/issues/6492 for further discussion. def read clen, dest, ignore_eof = false temp_dest = inflate_adapter(dest) @socket.read clen, temp_dest, ignore_eof end ## # Reads the rest of the socket, inflates it, then writes it to +dest+. def read_all dest temp_dest = inflate_adapter(dest) @socket.read_all temp_dest end end end PK!,QQ!vendor/molinillo/lib/molinillo.rbnu[# frozen_string_literal: true require_relative 'molinillo/gem_metadata' require_relative 'molinillo/errors' require_relative 'molinillo/resolver' require_relative 'molinillo/modules/ui' require_relative 'molinillo/modules/specification_provider' # Gem::Molinillo is a generic dependency resolution algorithm. module Gem::Molinillo end PK!ww.vendor/molinillo/lib/molinillo/gem_metadata.rbnu[# frozen_string_literal: true module Gem::Molinillo # The version of Gem::Molinillo. VERSION = '0.8.0'.freeze end PK!?pp(vendor/molinillo/lib/molinillo/errors.rbnu[# frozen_string_literal: true module Gem::Molinillo # An error that occurred during the resolution process class ResolverError < StandardError; end # An error caused by searching for a dependency that is completely unknown, # i.e. has no versions available whatsoever. class NoSuchDependencyError < ResolverError # @return [Object] the dependency that could not be found attr_accessor :dependency # @return [Array] the specifications that depended upon {#dependency} attr_accessor :required_by # Initializes a new error with the given missing dependency. # @param [Object] dependency @see {#dependency} # @param [Array] required_by @see {#required_by} def initialize(dependency, required_by = []) @dependency = dependency @required_by = required_by.uniq super() end # The error message for the missing dependency, including the specifications # that had this dependency. def message sources = required_by.map { |r| "`#{r}`" }.join(' and ') message = "Unable to find a specification for `#{dependency}`" message += " depended upon by #{sources}" unless sources.empty? message end end # An error caused by attempting to fulfil a dependency that was circular # # @note This exception will be thrown if and only if a {Vertex} is added to a # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an # existing {DependencyGraph::Vertex} class CircularDependencyError < ResolverError # [Set] the dependencies responsible for causing the error attr_reader :dependencies # Initializes a new error with the given circular vertices. # @param [Array] vertices the vertices in the dependency # that caused the error def initialize(vertices) super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set end end # An error caused by conflicts in version class VersionConflict < ResolverError # @return [{String => Resolution::Conflict}] the conflicts that caused # resolution to fail attr_reader :conflicts # @return [SpecificationProvider] the specification provider used during # resolution attr_reader :specification_provider # Initializes a new error with the given version conflicts. # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} # @param [SpecificationProvider] specification_provider see {#specification_provider} def initialize(conflicts, specification_provider) pairs = [] conflicts.values.flat_map(&:requirements).each do |conflicting| conflicting.each do |source, conflict_requirements| conflict_requirements.each do |c| pairs << [c, source] end end end super "Unable to satisfy the following requirements:\n\n" \ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" @conflicts = conflicts @specification_provider = specification_provider end require_relative 'delegates/specification_provider' include Delegates::SpecificationProvider # @return [String] An error message that includes requirement trees, # which is much more detailed & customizable than the default message # @param [Hash] opts the options to create a message with. # @option opts [String] :solver_name The user-facing name of the solver # @option opts [String] :possibility_type The generic name of a possibility # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements # @option opts [Proc] :additional_message_for_conflict A proc that appends additional # messages for each conflict # @option opts [Proc] :version_for_spec A proc that returns the version number for a # possibility def message_with_trees(opts = {}) solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } possibility_type = opts.delete(:possibility_type) { 'possibility named' } reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do proc do |name, _conflict| %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) end end full_message_for_conflict = opts.delete(:full_message_for_conflict) do proc do |name, conflict| o = "\n".dup << incompatible_version_message_for_conflict.call(name, conflict) << "\n" if conflict.locked_requirement o << %( In snapshot (#{name_for_locking_dependency_source}):\n) o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) o << %(\n) end o << %( In #{name_for_explicit_dependency_source}:\n) trees = reduce_trees.call(conflict.requirement_trees) o << trees.map do |tree| t = ''.dup depth = 2 tree.each do |req| t << ' ' * depth << printable_requirement.call(req) unless tree.last == req if spec = conflict.activated_by_name[name_for(req)] t << %( was resolved to #{version_for_spec.call(spec)}, which) end t << %( depends on) end t << %(\n) depth += 1 end t end.join("\n") additional_message_for_conflict.call(o, name, conflict) o end end conflicts.sort.reduce(''.dup) do |o, (name, conflict)| o << full_message_for_conflict.call(name, conflict) end.strip end end end PK!N讟6vendor/molinillo/lib/molinillo/dependency_graph/tag.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#tag class Tag < Action # @!group Action # (see Action.action_name) def self.action_name :tag end # (see Action#up) def up(graph) end # (see Action#down) def down(graph) end # @!group Tag # @return [Object] An opaque tag attr_reader :tag # Initialize an action to tag a state of a dependency graph # @param [Object] tag an opaque tag def initialize(tag) @tag = tag end end end end PK!19DD9vendor/molinillo/lib/molinillo/dependency_graph/vertex.rbnu[# frozen_string_literal: true module Gem::Molinillo class DependencyGraph # A vertex in a {DependencyGraph} that encapsulates a {#name} and a # {#payload} class Vertex # @return [String] the name of the vertex attr_accessor :name # @return [Object] the payload the vertex holds attr_accessor :payload # @return [Array] the explicit requirements that required # this vertex attr_reader :explicit_requirements # @return [Boolean] whether the vertex is considered a root vertex attr_accessor :root alias root? root # Initializes a vertex with the given name and payload. # @param [String] name see {#name} # @param [Object] payload see {#payload} def initialize(name, payload) @name = name.frozen? ? name : name.dup.freeze @payload = payload @explicit_requirements = [] @outgoing_edges = [] @incoming_edges = [] end # @return [Array] all of the requirements that required # this vertex def requirements (incoming_edges.map(&:requirement) + explicit_requirements).uniq end # @return [Array] the edges of {#graph} that have `self` as their # {Edge#origin} attr_accessor :outgoing_edges # @return [Array] the edges of {#graph} that have `self` as their # {Edge#destination} attr_accessor :incoming_edges # @return [Array] the vertices of {#graph} that have an edge with # `self` as their {Edge#destination} def predecessors incoming_edges.map(&:origin) end # @return [Set] the vertices of {#graph} where `self` is a # {#descendent?} def recursive_predecessors _recursive_predecessors end # @param [Set] vertices the set to add the predecessors to # @return [Set] the vertices of {#graph} where `self` is a # {#descendent?} def _recursive_predecessors(vertices = new_vertex_set) incoming_edges.each do |edge| vertex = edge.origin next unless vertices.add?(vertex) vertex._recursive_predecessors(vertices) end vertices end protected :_recursive_predecessors # @return [Array] the vertices of {#graph} that have an edge with # `self` as their {Edge#origin} def successors outgoing_edges.map(&:destination) end # @return [Set] the vertices of {#graph} where `self` is an # {#ancestor?} def recursive_successors _recursive_successors end # @param [Set] vertices the set to add the successors to # @return [Set] the vertices of {#graph} where `self` is an # {#ancestor?} def _recursive_successors(vertices = new_vertex_set) outgoing_edges.each do |edge| vertex = edge.destination next unless vertices.add?(vertex) vertex._recursive_successors(vertices) end vertices end protected :_recursive_successors # @return [String] a string suitable for debugging def inspect "#{self.class}:#{name}(#{payload.inspect})" end # @return [Boolean] whether the two vertices are equal, determined # by a recursive traversal of each {Vertex#successors} def ==(other) return true if equal?(other) shallow_eql?(other) && successors.to_set == other.successors.to_set end # @param [Vertex] other the other vertex to compare to # @return [Boolean] whether the two vertices are equal, determined # solely by {#name} and {#payload} equality def shallow_eql?(other) return true if equal?(other) other && name == other.name && payload == other.payload end alias eql? == # @return [Fixnum] a hash for the vertex based upon its {#name} def hash name.hash end # Is there a path from `self` to `other` following edges in the # dependency graph? # @return whether there is a path following edges within this {#graph} def path_to?(other) _path_to?(other) end alias descendent? path_to? # @param [Vertex] other the vertex to check if there's a path to # @param [Set] visited the vertices of {#graph} that have been visited # @return [Boolean] whether there is a path to `other` from `self` def _path_to?(other, visited = new_vertex_set) return false unless visited.add?(self) return true if equal?(other) successors.any? { |v| v._path_to?(other, visited) } end protected :_path_to? # Is there a path from `other` to `self` following edges in the # dependency graph? # @return whether there is a path following edges within this {#graph} def ancestor?(other) other.path_to?(self) end alias is_reachable_from? ancestor? def new_vertex_set require 'set' Set.new end private :new_vertex_set end end end PK!?__=vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#add_vertex) class AddVertex < Action # :nodoc: # @!group Action # (see Action.action_name) def self.action_name :add_vertex end # (see Action#up) def up(graph) if existing = graph.vertices[name] @existing_payload = existing.payload @existing_root = existing.root end vertex = existing || Vertex.new(name, payload) graph.vertices[vertex.name] = vertex vertex.payload ||= payload vertex.root ||= root vertex end # (see Action#down) def down(graph) if defined?(@existing_payload) vertex = graph.vertices[name] vertex.payload = @existing_payload vertex.root = @existing_root else graph.vertices.delete(name) end end # @!group AddVertex # @return [String] the name of the vertex attr_reader :name # @return [Object] the payload for the vertex attr_reader :payload # @return [Boolean] whether the vertex is root or not attr_reader :root # Initialize an action to add a vertex to a dependency graph # @param [String] name the name of the vertex # @param [Object] payload the payload for the vertex # @param [Boolean] root whether the vertex is root or not def initialize(name, payload, root) @name = name @payload = payload @root = root end end end end PK!J N9vendor/molinillo/lib/molinillo/dependency_graph/action.rbnu[# frozen_string_literal: true module Gem::Molinillo class DependencyGraph # An action that modifies a {DependencyGraph} that is reversible. # @abstract class Action # rubocop:disable Lint/UnusedMethodArgument # @return [Symbol] The name of the action. def self.action_name raise 'Abstract' end # Performs the action on the given graph. # @param [DependencyGraph] graph the graph to perform the action on. # @return [Void] def up(graph) raise 'Abstract' end # Reverses the action on the given graph. # @param [DependencyGraph] graph the graph to reverse the action on. # @return [Void] def down(graph) raise 'Abstract' end # @return [Action,Nil] The previous action attr_accessor :previous # @return [Action,Nil] The next action attr_accessor :next end end end PK!8Fvendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#detach_vertex_named class DetachVertexNamed < Action # @!group Action # (see Action#name) def self.action_name :add_vertex end # (see Action#up) def up(graph) return [] unless @vertex = graph.vertices.delete(name) removed_vertices = [@vertex] @vertex.outgoing_edges.each do |e| v = e.destination v.incoming_edges.delete(e) if !v.root? && v.incoming_edges.empty? removed_vertices.concat graph.detach_vertex_named(v.name) end end @vertex.incoming_edges.each do |e| v = e.origin v.outgoing_edges.delete(e) end removed_vertices end # (see Action#down) def down(graph) return unless @vertex graph.vertices[@vertex.name] = @vertex @vertex.outgoing_edges.each do |e| e.destination.incoming_edges << e end @vertex.incoming_edges.each do |e| e.origin.outgoing_edges << e end end # @!group DetachVertexNamed # @return [String] the name of the vertex to detach attr_reader :name # Initialize an action to detach a vertex from a dependency graph # @param [String] name the name of the vertex to detach def initialize(name) @name = name end end end end PK!U=>vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#delete_edge) class DeleteEdge < Action # @!group Action # (see Action.action_name) def self.action_name :delete_edge end # (see Action#up) def up(graph) edge = make_edge(graph) edge.origin.outgoing_edges.delete(edge) edge.destination.incoming_edges.delete(edge) end # (see Action#down) def down(graph) edge = make_edge(graph) edge.origin.outgoing_edges << edge edge.destination.incoming_edges << edge edge end # @!group DeleteEdge # @return [String] the name of the origin of the edge attr_reader :origin_name # @return [String] the name of the destination of the edge attr_reader :destination_name # @return [Object] the requirement that the edge represents attr_reader :requirement # @param [DependencyGraph] graph the graph to find vertices from # @return [Edge] The edge this action adds def make_edge(graph) Edge.new( graph.vertex_named(origin_name), graph.vertex_named(destination_name), requirement ) end # Initialize an action to add an edge to a dependency graph # @param [String] origin_name the name of the origin of the edge # @param [String] destination_name the name of the destination of the edge # @param [Object] requirement the requirement that the edge represents def initialize(origin_name, destination_name, requirement) @origin_name = origin_name @destination_name = destination_name @requirement = requirement end end end end PK!N kkGvendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # (see DependencyGraph#add_edge_no_circular) class AddEdgeNoCircular < Action # @!group Action # (see Action.action_name) def self.action_name :add_vertex end # (see Action#up) def up(graph) edge = make_edge(graph) edge.origin.outgoing_edges << edge edge.destination.incoming_edges << edge edge end # (see Action#down) def down(graph) edge = make_edge(graph) delete_first(edge.origin.outgoing_edges, edge) delete_first(edge.destination.incoming_edges, edge) end # @!group AddEdgeNoCircular # @return [String] the name of the origin of the edge attr_reader :origin # @return [String] the name of the destination of the edge attr_reader :destination # @return [Object] the requirement that the edge represents attr_reader :requirement # @param [DependencyGraph] graph the graph to find vertices from # @return [Edge] The edge this action adds def make_edge(graph) Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement) end # Initialize an action to add an edge to a dependency graph # @param [String] origin the name of the origin of the edge # @param [String] destination the name of the destination of the edge # @param [Object] requirement the requirement that the edge represents def initialize(origin, destination, requirement) @origin = origin @destination = destination @requirement = requirement end private def delete_first(array, item) return unless index = array.index(item) array.delete_at(index) end end end end PK!fg6vendor/molinillo/lib/molinillo/dependency_graph/log.rbnu[# frozen_string_literal: true require_relative 'add_edge_no_circular' require_relative 'add_vertex' require_relative 'delete_edge' require_relative 'detach_vertex_named' require_relative 'set_payload' require_relative 'tag' module Gem::Molinillo class DependencyGraph # A log for dependency graph actions class Log # Initializes an empty log def initialize @current_action = @first_action = nil end # @!macro [new] action # {include:DependencyGraph#$0} # @param [Graph] graph the graph to perform the action on # @param (see DependencyGraph#$0) # @return (see DependencyGraph#$0) # @macro action def tag(graph, tag) push_action(graph, Tag.new(tag)) end # @macro action def add_vertex(graph, name, payload, root) push_action(graph, AddVertex.new(name, payload, root)) end # @macro action def detach_vertex_named(graph, name) push_action(graph, DetachVertexNamed.new(name)) end # @macro action def add_edge_no_circular(graph, origin, destination, requirement) push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) end # {include:DependencyGraph#delete_edge} # @param [Graph] graph the graph to perform the action on # @param [String] origin_name # @param [String] destination_name # @param [Object] requirement # @return (see DependencyGraph#delete_edge) def delete_edge(graph, origin_name, destination_name, requirement) push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) end # @macro action def set_payload(graph, name, payload) push_action(graph, SetPayload.new(name, payload)) end # Pops the most recent action from the log and undoes the action # @param [DependencyGraph] graph # @return [Action] the action that was popped off the log def pop!(graph) return unless action = @current_action unless @current_action = action.previous @first_action = nil end action.down(graph) action end extend Enumerable # @!visibility private # Enumerates each action in the log # @yield [Action] def each return enum_for unless block_given? action = @first_action loop do break unless action yield action action = action.next end self end # @!visibility private # Enumerates each action in the log in reverse order # @yield [Action] def reverse_each return enum_for(:reverse_each) unless block_given? action = @current_action loop do break unless action yield action action = action.previous end self end # @macro action def rewind_to(graph, tag) loop do action = pop!(graph) raise "No tag #{tag.inspect} found" unless action break if action.class.action_name == :tag && action.tag == tag end end private # Adds the given action to the log, running the action # @param [DependencyGraph] graph # @param [Action] action # @return The value returned by `action.up` def push_action(graph, action) action.previous = @current_action @current_action.next = action if @current_action @current_action = action @first_action ||= action action.up(graph) end end end end PK!]SS>vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rbnu[# frozen_string_literal: true require_relative 'action' module Gem::Molinillo class DependencyGraph # @!visibility private # @see DependencyGraph#set_payload class SetPayload < Action # :nodoc: # @!group Action # (see Action.action_name) def self.action_name :set_payload end # (see Action#up) def up(graph) vertex = graph.vertex_named(name) @old_payload = vertex.payload vertex.payload = payload end # (see Action#down) def down(graph) graph.vertex_named(name).payload = @old_payload end # @!group SetPayload # @return [String] the name of the vertex attr_reader :name # @return [Object] the payload for the vertex attr_reader :payload # Initialize an action to add set the payload for a vertex in a dependency # graph # @param [String] name the name of the vertex # @param [Object] payload the payload for the vertex def initialize(name, payload) @name = name @payload = payload end end end end PK! 5b b Bvendor/molinillo/lib/molinillo/delegates/specification_provider.rbnu[# frozen_string_literal: true module Gem::Molinillo module Delegates # Delegates all {Gem::Molinillo::SpecificationProvider} methods to a # `#specification_provider` property. module SpecificationProvider # (see Gem::Molinillo::SpecificationProvider#search_for) def search_for(dependency) with_no_such_dependency_error_handling do specification_provider.search_for(dependency) end end # (see Gem::Molinillo::SpecificationProvider#dependencies_for) def dependencies_for(specification) with_no_such_dependency_error_handling do specification_provider.dependencies_for(specification) end end # (see Gem::Molinillo::SpecificationProvider#requirement_satisfied_by?) def requirement_satisfied_by?(requirement, activated, spec) with_no_such_dependency_error_handling do specification_provider.requirement_satisfied_by?(requirement, activated, spec) end end # (see Gem::Molinillo::SpecificationProvider#dependencies_equal?) def dependencies_equal?(dependencies, other_dependencies) with_no_such_dependency_error_handling do specification_provider.dependencies_equal?(dependencies, other_dependencies) end end # (see Gem::Molinillo::SpecificationProvider#name_for) def name_for(dependency) with_no_such_dependency_error_handling do specification_provider.name_for(dependency) end end # (see Gem::Molinillo::SpecificationProvider#name_for_explicit_dependency_source) def name_for_explicit_dependency_source with_no_such_dependency_error_handling do specification_provider.name_for_explicit_dependency_source end end # (see Gem::Molinillo::SpecificationProvider#name_for_locking_dependency_source) def name_for_locking_dependency_source with_no_such_dependency_error_handling do specification_provider.name_for_locking_dependency_source end end # (see Gem::Molinillo::SpecificationProvider#sort_dependencies) def sort_dependencies(dependencies, activated, conflicts) with_no_such_dependency_error_handling do specification_provider.sort_dependencies(dependencies, activated, conflicts) end end # (see Gem::Molinillo::SpecificationProvider#allow_missing?) def allow_missing?(dependency) with_no_such_dependency_error_handling do specification_provider.allow_missing?(dependency) end end private # Ensures any raised {NoSuchDependencyError} has its # {NoSuchDependencyError#required_by} set. # @yield def with_no_such_dependency_error_handling yield rescue NoSuchDependencyError => error if state vertex = activated.vertex_named(name_for(error.dependency)) error.required_by += vertex.incoming_edges.map { |e| e.origin.name } error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? end raise end end end end PK!#:1V<vendor/molinillo/lib/molinillo/delegates/resolution_state.rbnu[# frozen_string_literal: true module Gem::Molinillo # @!visibility private module Delegates # Delegates all {Gem::Molinillo::ResolutionState} methods to a `#state` property. module ResolutionState # (see Gem::Molinillo::ResolutionState#name) def name current_state = state || Gem::Molinillo::ResolutionState.empty current_state.name end # (see Gem::Molinillo::ResolutionState#requirements) def requirements current_state = state || Gem::Molinillo::ResolutionState.empty current_state.requirements end # (see Gem::Molinillo::ResolutionState#activated) def activated current_state = state || Gem::Molinillo::ResolutionState.empty current_state.activated end # (see Gem::Molinillo::ResolutionState#requirement) def requirement current_state = state || Gem::Molinillo::ResolutionState.empty current_state.requirement end # (see Gem::Molinillo::ResolutionState#possibilities) def possibilities current_state = state || Gem::Molinillo::ResolutionState.empty current_state.possibilities end # (see Gem::Molinillo::ResolutionState#depth) def depth current_state = state || Gem::Molinillo::ResolutionState.empty current_state.depth end # (see Gem::Molinillo::ResolutionState#conflicts) def conflicts current_state = state || Gem::Molinillo::ResolutionState.empty current_state.conflicts end # (see Gem::Molinillo::ResolutionState#unused_unwind_options) def unused_unwind_options current_state = state || Gem::Molinillo::ResolutionState.empty current_state.unused_unwind_options end end end end PK!,*vendor/molinillo/lib/molinillo/resolver.rbnu[# frozen_string_literal: true require_relative 'dependency_graph' module Gem::Molinillo # This class encapsulates a dependency resolver. # The resolver is responsible for determining which set of dependencies to # activate, with feedback from the {#specification_provider} # # class Resolver require_relative 'resolution' # @return [SpecificationProvider] the specification provider used # in the resolution process attr_reader :specification_provider # @return [UI] the UI module used to communicate back to the user # during the resolution process attr_reader :resolver_ui # Initializes a new resolver. # @param [SpecificationProvider] specification_provider # see {#specification_provider} # @param [UI] resolver_ui # see {#resolver_ui} def initialize(specification_provider, resolver_ui) @specification_provider = specification_provider @resolver_ui = resolver_ui end # Resolves the requested dependencies into a {DependencyGraph}, # locking to the base dependency graph (if specified) # @param [Array] requested an array of 'requested' dependencies that the # {#specification_provider} can understand # @param [DependencyGraph,nil] base the base dependency graph to which # dependencies should be 'locked' def resolve(requested, base = DependencyGraph.new) Resolution.new(specification_provider, resolver_ui, requested, base). resolve end end end PK!Eã,vendor/molinillo/lib/molinillo/modules/ui.rbnu[# frozen_string_literal: true module Gem::Molinillo # Conveys information about the resolution process to a user. module UI # The {IO} object that should be used to print output. `STDOUT`, by default. # # @return [IO] def output STDOUT end # Called roughly every {#progress_rate}, this method should convey progress # to the user. # # @return [void] def indicate_progress output.print '.' unless debug? end # How often progress should be conveyed to the user via # {#indicate_progress}, in seconds. A third of a second, by default. # # @return [Float] def progress_rate 0.33 end # Called before resolution begins. # # @return [void] def before_resolution output.print 'Resolving dependencies...' end # Called after resolution ends (either successfully or with an error). # By default, prints a newline. # # @return [void] def after_resolution output.puts end # Conveys debug information to the user. # # @param [Integer] depth the current depth of the resolution process. # @return [void] def debug(depth = 0) if debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } output.puts debug_info end end # Whether or not debug messages should be printed. # By default, whether or not the `MOLINILLO_DEBUG` environment variable is # set. # # @return [Boolean] def debug? return @debug_mode if defined?(@debug_mode) @debug_mode = ENV['MOLINILLO_DEBUG'] end end end PK!օSS@vendor/molinillo/lib/molinillo/modules/specification_provider.rbnu[# frozen_string_literal: true module Gem::Molinillo # Provides information about specifications and dependencies to the resolver, # allowing the {Resolver} class to remain generic while still providing power # and flexibility. # # This module contains the methods that users of Gem::Molinillo must to implement, # using knowledge of their own model classes. module SpecificationProvider # Search for the specifications that match the given dependency. # The specifications in the returned array will be considered in reverse # order, so the latest version ought to be last. # @note This method should be 'pure', i.e. the return value should depend # only on the `dependency` parameter. # # @param [Object] dependency # @return [Array] the specifications that satisfy the given # `dependency`. def search_for(dependency) [] end # Returns the dependencies of `specification`. # @note This method should be 'pure', i.e. the return value should depend # only on the `specification` parameter. # # @param [Object] specification # @return [Array] the dependencies that are required by the given # `specification`. def dependencies_for(specification) [] end # Determines whether the given `requirement` is satisfied by the given # `spec`, in the context of the current `activated` dependency graph. # # @param [Object] requirement # @param [DependencyGraph] activated the current dependency graph in the # resolution process. # @param [Object] spec # @return [Boolean] whether `requirement` is satisfied by `spec` in the # context of the current `activated` dependency graph. def requirement_satisfied_by?(requirement, activated, spec) true end # Determines whether two arrays of dependencies are equal, and thus can be # grouped. # # @param [Array] dependencies # @param [Array] other_dependencies # @return [Boolean] whether `dependencies` and `other_dependencies` should # be considered equal. def dependencies_equal?(dependencies, other_dependencies) dependencies == other_dependencies end # Returns the name for the given `dependency`. # @note This method should be 'pure', i.e. the return value should depend # only on the `dependency` parameter. # # @param [Object] dependency # @return [String] the name for the given `dependency`. def name_for(dependency) dependency.to_s end # @return [String] the name of the source of explicit dependencies, i.e. # those passed to {Resolver#resolve} directly. def name_for_explicit_dependency_source 'user-specified dependency' end # @return [String] the name of the source of 'locked' dependencies, i.e. # those passed to {Resolver#resolve} directly as the `base` def name_for_locking_dependency_source 'Lockfile' end # Sort dependencies so that the ones that are easiest to resolve are first. # Easiest to resolve is (usually) defined by: # 1) Is this dependency already activated? # 2) How relaxed are the requirements? # 3) Are there any conflicts for this dependency? # 4) How many possibilities are there to satisfy this dependency? # # @param [Array] dependencies # @param [DependencyGraph] activated the current dependency graph in the # resolution process. # @param [{String => Array}] conflicts # @return [Array] a sorted copy of `dependencies`. def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by do |dependency| name = name_for(dependency) [ activated.vertex_named(name).payload ? 0 : 1, conflicts[name] ? 0 : 1, ] end end # Returns whether this dependency, which has no possible matching # specifications, can safely be ignored. # # @param [Object] dependency # @return [Boolean] whether this dependency can safely be skipped. def allow_missing?(dependency) false end end end PK!JT܂,vendor/molinillo/lib/molinillo/resolution.rbnu[# frozen_string_literal: true module Gem::Molinillo class Resolver # A specific resolution from a given {Resolver} class Resolution # A conflict that the resolution process encountered # @attr [Object] requirement the requirement that immediately led to the conflict # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict # @attr [Object, nil] existing the existing spec that was in conflict with # the {#possibility} # @attr [Object] possibility_set the set of specs that was unable to be # activated due to a conflict. # @attr [Object] locked_requirement the relevant locking requirement. # @attr [Array>] requirement_trees the different requirement # trees that led to every requirement for the conflicting name. # @attr [{String=>Object}] activated_by_name the already-activated specs. # @attr [Object] underlying_error an error that has occurred during resolution, and # will be raised at the end of it if no resolution is found. Conflict = Struct.new( :requirement, :requirements, :existing, :possibility_set, :locked_requirement, :requirement_trees, :activated_by_name, :underlying_error ) class Conflict # @return [Object] a spec that was unable to be activated due to a conflict def possibility possibility_set && possibility_set.latest_version end end # A collection of possibility states that share the same dependencies # @attr [Array] dependencies the dependencies for this set of possibilities # @attr [Array] possibilities the possibilities PossibilitySet = Struct.new(:dependencies, :possibilities) class PossibilitySet # String representation of the possibility set, for debugging def to_s "[#{possibilities.join(', ')}]" end # @return [Object] most up-to-date dependency in the possibility set def latest_version possibilities.last end end # Details of the state to unwind to when a conflict occurs, and the cause of the unwind # @attr [Integer] state_index the index of the state to unwind to # @attr [Object] state_requirement the requirement of the state we're unwinding to # @attr [Array] requirement_tree for the requirement we're relaxing # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict # @attr [Array] requirement_trees for the conflict # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind UnwindDetails = Struct.new( :state_index, :state_requirement, :requirement_tree, :conflicting_requirements, :requirement_trees, :requirements_unwound_to_instead ) class UnwindDetails include Comparable # We compare UnwindDetails when choosing which state to unwind to. If # two options have the same state_index we prefer the one most # removed from a requirement that caused the conflict. Both options # would unwind to the same state, but a `grandparent` option will # filter out fewer of its possibilities after doing so - where a state # is both a `parent` and a `grandparent` to requirements that have # caused a conflict this is the correct behaviour. # @param [UnwindDetail] other UnwindDetail to be compared # @return [Integer] integer specifying ordering def <=>(other) if state_index > other.state_index 1 elsif state_index == other.state_index reversed_requirement_tree_index <=> other.reversed_requirement_tree_index else -1 end end # @return [Integer] index of state requirement in reversed requirement tree # (the conflicting requirement itself will be at position 0) def reversed_requirement_tree_index @reversed_requirement_tree_index ||= if state_requirement requirement_tree.reverse.index(state_requirement) else 999_999 end end # @return [Boolean] where the requirement of the state we're unwinding # to directly caused the conflict. Note: in this case, it is # impossible for the state we're unwinding to be a parent of # any of the other conflicting requirements (or we would have # circularity) def unwinding_to_primary_requirement? requirement_tree.last == state_requirement end # @return [Array] array of sub-dependencies to avoid when choosing a # new possibility for the state we've unwound to. Only relevant for # non-primary unwinds def sub_dependencies_to_avoid @requirements_to_avoid ||= requirement_trees.map do |tree| index = tree.index(state_requirement) tree[index + 1] if index end.compact end # @return [Array] array of all the requirements that led to the need for # this unwind def all_requirements @all_requirements ||= requirement_trees.flatten(1) end end # @return [SpecificationProvider] the provider that knows about # dependencies, requirements, specifications, versions, etc. attr_reader :specification_provider # @return [UI] the UI that knows how to communicate feedback about the # resolution process back to the user attr_reader :resolver_ui # @return [DependencyGraph] the base dependency graph to which # dependencies should be 'locked' attr_reader :base # @return [Array] the dependencies that were explicitly required attr_reader :original_requested # Initializes a new resolution. # @param [SpecificationProvider] specification_provider # see {#specification_provider} # @param [UI] resolver_ui see {#resolver_ui} # @param [Array] requested see {#original_requested} # @param [DependencyGraph] base see {#base} def initialize(specification_provider, resolver_ui, requested, base) @specification_provider = specification_provider @resolver_ui = resolver_ui @original_requested = requested @base = base @states = [] @iteration_counter = 0 @parents_of = Hash.new { |h, k| h[k] = [] } end # Resolves the {#original_requested} dependencies into a full dependency # graph # @raise [ResolverError] if successful resolution is impossible # @return [DependencyGraph] the dependency graph of successfully resolved # dependencies def resolve start_resolution while state break if !state.requirement && state.requirements.empty? indicate_progress if state.respond_to?(:pop_possibility_state) # DependencyState debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } state.pop_possibility_state.tap do |s| if s states.push(s) activated.tag(s) end end end process_topmost_state end resolve_activated_specs ensure end_resolution end # @return [Integer] the number of resolver iterations in between calls to # {#resolver_ui}'s {UI#indicate_progress} method attr_accessor :iteration_rate private :iteration_rate # @return [Time] the time at which resolution began attr_accessor :started_at private :started_at # @return [Array] the stack of states for the resolution attr_accessor :states private :states private # Sets up the resolution process # @return [void] def start_resolution @started_at = Time.now push_initial_state debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } resolver_ui.before_resolution end def resolve_activated_specs activated.vertices.each do |_, vertex| next unless vertex.payload latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } end activated.set_payload(vertex.name, latest_version) end activated.freeze end # Ends the resolution process # @return [void] def end_resolution resolver_ui.after_resolution debug do "Finished resolution (#{@iteration_counter} steps) " \ "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" end debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state end require_relative 'state' require_relative 'modules/specification_provider' require_relative 'delegates/resolution_state' require_relative 'delegates/specification_provider' include Gem::Molinillo::Delegates::ResolutionState include Gem::Molinillo::Delegates::SpecificationProvider # Processes the topmost available {RequirementState} on the stack # @return [void] def process_topmost_state if possibility attempt_to_activate else create_conflict unwind_for_conflict end rescue CircularDependencyError => underlying_error create_conflict(underlying_error) unwind_for_conflict end # @return [Object] the current possibility that the resolution is trying # to activate def possibility possibilities.last end # @return [RequirementState] the current state the resolution is # operating upon def state states.last end # Creates and pushes the initial state for the resolution, based upon the # {#requested} dependencies # @return [void] def push_initial_state graph = DependencyGraph.new.tap do |dg| original_requested.each do |requested| vertex = dg.add_vertex(name_for(requested), nil, true) vertex.explicit_requirements << requested end dg.tag(:initial_state) end push_state_for_requirements(original_requested, true, graph) end # Unwinds the states stack because a conflict has been encountered # @return [void] def unwind_for_conflict details_for_unwind = build_details_for_unwind unwind_options = unused_unwind_options debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } conflicts.tap do |c| sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) raise_error_unless_state(c) activated.rewind_to(sliced_states.first || :initial_state) if sliced_states state.conflicts = c state.unused_unwind_options = unwind_options filter_possibilities_after_unwind(details_for_unwind) index = states.size - 1 @parents_of.each { |_, a| a.reject! { |i| i >= index } } state.unused_unwind_options.reject! { |uw| uw.state_index >= index } end end # Raises a VersionConflict error, or any underlying error, if there is no # current state # @return [void] def raise_error_unless_state(conflicts) return if state error = conflicts.values.map(&:underlying_error).compact.first raise error || VersionConflict.new(conflicts, specification_provider) end # @return [UnwindDetails] Details of the nearest index to which we could unwind def build_details_for_unwind # Get the possible unwinds for the current conflict current_conflict = conflicts[name] binding_requirements = binding_requirements_for_conflict(current_conflict) unwind_details = unwind_options_for_requirements(binding_requirements) last_detail_for_current_unwind = unwind_details.sort.last current_detail = last_detail_for_current_unwind # Look for past conflicts that could be unwound to affect the # requirement tree for the current conflict all_reqs = last_detail_for_current_unwind.all_requirements all_reqs_size = all_reqs.size relevant_unused_unwinds = unused_unwind_options.select do |alternative| diff_reqs = all_reqs - alternative.requirements_unwound_to_instead next if diff_reqs.size == all_reqs_size # Find the highest index unwind whilst looping through current_detail = alternative if alternative > current_detail alternative end # Add the current unwind options to the `unused_unwind_options` array. # The "used" option will be filtered out during `unwind_for_conflict`. state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } # Update the requirements_unwound_to_instead on any relevant unused unwinds relevant_unused_unwinds.each do |d| (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! end unwind_details.each do |d| (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! end current_detail end # @param [Array] binding_requirements array of requirements that combine to create a conflict # @return [Array] array of UnwindDetails that have a chance # of resolving the passed requirements def unwind_options_for_requirements(binding_requirements) unwind_details = [] trees = [] binding_requirements.reverse_each do |r| partial_tree = [r] trees << partial_tree unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) # If this requirement has alternative possibilities, check if any would # satisfy the other requirements that created this conflict requirement_state = find_state_for(r) if conflict_fixing_possibilities?(requirement_state, binding_requirements) unwind_details << UnwindDetails.new( states.index(requirement_state), r, partial_tree, binding_requirements, trees, [] ) end # Next, look at the parent of this requirement, and check if the requirement # could have been avoided if an alternative PossibilitySet had been chosen parent_r = parent_of(r) next if parent_r.nil? partial_tree.unshift(parent_r) requirement_state = find_state_for(parent_r) if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } unwind_details << UnwindDetails.new( states.index(requirement_state), parent_r, partial_tree, binding_requirements, trees, [] ) end # Finally, look at the grandparent and up of this requirement, looking # for any possibilities that wouldn't create their parent requirement grandparent_r = parent_of(parent_r) until grandparent_r.nil? partial_tree.unshift(grandparent_r) requirement_state = find_state_for(grandparent_r) if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } unwind_details << UnwindDetails.new( states.index(requirement_state), grandparent_r, partial_tree, binding_requirements, trees, [] ) end parent_r = grandparent_r grandparent_r = parent_of(parent_r) end end unwind_details end # @param [DependencyState] state # @param [Array] binding_requirements array of requirements # @return [Boolean] whether or not the given state has any possibilities # that could satisfy the given requirements def conflict_fixing_possibilities?(state, binding_requirements) return false unless state state.possibilities.any? do |possibility_set| possibility_set.possibilities.any? do |poss| possibility_satisfies_requirements?(poss, binding_requirements) end end end # Filter's a state's possibilities to remove any that would not fix the # conflict we've just rewound from # @param [UnwindDetails] unwind_details details of the conflict just # unwound from # @return [void] def filter_possibilities_after_unwind(unwind_details) return unless state && !state.possibilities.empty? if unwind_details.unwinding_to_primary_requirement? filter_possibilities_for_primary_unwind(unwind_details) else filter_possibilities_for_parent_unwind(unwind_details) end end # Filter's a state's possibilities to remove any that would not satisfy # the requirements in the conflict we've just rewound from # @param [UnwindDetails] unwind_details details of the conflict just unwound from # @return [void] def filter_possibilities_for_primary_unwind(unwind_details) unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } unwinds_to_state << unwind_details unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) state.possibilities.reject! do |possibility_set| possibility_set.possibilities.none? do |poss| unwind_requirement_sets.any? do |requirements| possibility_satisfies_requirements?(poss, requirements) end end end end # @param [Object] possibility a single possibility # @param [Array] requirements an array of requirements # @return [Boolean] whether the possibility satisfies all of the # given requirements def possibility_satisfies_requirements?(possibility, requirements) name = name_for(possibility) activated.tag(:swap) activated.set_payload(name, possibility) if activated.vertex_named(name) satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } activated.rewind_to(:swap) satisfied end # Filter's a state's possibilities to remove any that would (eventually) # create a requirement in the conflict we've just rewound from # @param [UnwindDetails] unwind_details details of the conflict just unwound from # @return [void] def filter_possibilities_for_parent_unwind(unwind_details) unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } unwinds_to_state << unwind_details primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq parent_unwinds = unwinds_to_state.uniq - primary_unwinds allowed_possibility_sets = primary_unwinds.flat_map do |unwind| states[unwind.state_index].possibilities.select do |possibility_set| possibility_set.possibilities.any? do |poss| possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) end end end requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) state.possibilities.reject! do |possibility_set| !allowed_possibility_sets.include?(possibility_set) && (requirements_to_avoid - possibility_set.dependencies).empty? end end # @param [Conflict] conflict # @return [Array] minimal array of requirements that would cause the passed # conflict to occur. def binding_requirements_for_conflict(conflict) return [conflict.requirement] if conflict.possibility.nil? possible_binding_requirements = conflict.requirements.values.flatten(1).uniq # When there's a `CircularDependency` error the conflicting requirement # (the one causing the circular) won't be `conflict.requirement` # (which won't be for the right state, because we won't have created it, # because it's circular). # We need to make sure we have that requirement in the conflict's list, # otherwise we won't be able to unwind properly, so we just return all # the requirements for the conflict. return possible_binding_requirements if conflict.underlying_error possibilities = search_for(conflict.requirement) # If all the requirements together don't filter out all possibilities, # then the only two requirements we need to consider are the initial one # (where the dependency's version was first chosen) and the last if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact end # Loop through the possible binding requirements, removing each one # that doesn't bind. Use a `reverse_each` as we want the earliest set of # binding requirements, and don't use `reject!` as we wish to refine the # array *on each iteration*. binding_requirements = possible_binding_requirements.dup possible_binding_requirements.reverse_each do |req| next if req == conflict.requirement unless binding_requirement_in_set?(req, binding_requirements, possibilities) binding_requirements -= [req] end end binding_requirements end # @param [Object] requirement we wish to check # @param [Array] possible_binding_requirements array of requirements # @param [Array] possibilities array of possibilities the requirements will be used to filter # @return [Boolean] whether or not the given requirement is required to filter # out all elements of the array of possibilities. def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) possibilities.any? do |poss| possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) end end # @param [Object] requirement # @return [Object] the requirement that led to `requirement` being added # to the list of requirements. def parent_of(requirement) return unless requirement return unless index = @parents_of[requirement].last return unless parent_state = @states[index] parent_state.requirement end # @param [String] name # @return [Object] the requirement that led to a version of a possibility # with the given name being activated. def requirement_for_existing_name(name) return nil unless vertex = activated.vertex_named(name) return nil unless vertex.payload states.find { |s| s.name == name }.requirement end # @param [Object] requirement # @return [ResolutionState] the state whose `requirement` is the given # `requirement`. def find_state_for(requirement) return nil unless requirement states.find { |i| requirement == i.requirement } end # @param [Object] underlying_error # @return [Conflict] a {Conflict} that reflects the failure to activate # the {#possibility} in conjunction with the current {#state} def create_conflict(underlying_error = nil) vertex = activated.vertex_named(name) locked_requirement = locked_requirement_named(name) requirements = {} unless vertex.explicit_requirements.empty? requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements end requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement vertex.incoming_edges.each do |edge| (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) end activated_by_name = {} activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } conflicts[name] = Conflict.new( requirement, requirements, vertex.payload && vertex.payload.latest_version, possibility, locked_requirement, requirement_trees, activated_by_name, underlying_error ) end # @return [Array>] The different requirement # trees that led to every requirement for the current spec. def requirement_trees vertex = activated.vertex_named(name) vertex.requirements.map { |r| requirement_tree_for(r) } end # @param [Object] requirement # @return [Array] the list of requirements that led to # `requirement` being required. def requirement_tree_for(requirement) tree = [] while requirement tree.unshift(requirement) requirement = parent_of(requirement) end tree end # Indicates progress roughly once every second # @return [void] def indicate_progress @iteration_counter += 1 @progress_rate ||= resolver_ui.progress_rate if iteration_rate.nil? if Time.now - started_at >= @progress_rate self.iteration_rate = @iteration_counter end end if iteration_rate && (@iteration_counter % iteration_rate) == 0 resolver_ui.indicate_progress end end # Calls the {#resolver_ui}'s {UI#debug} method # @param [Integer] depth the depth of the {#states} stack # @param [Proc] block a block that yields a {#to_s} # @return [void] def debug(depth = 0, &block) resolver_ui.debug(depth, &block) end # Attempts to activate the current {#possibility} # @return [void] def attempt_to_activate debug(depth) { 'Attempting to activate ' + possibility.to_s } existing_vertex = activated.vertex_named(name) if existing_vertex.payload debug(depth) { "Found existing spec (#{existing_vertex.payload})" } attempt_to_filter_existing_spec(existing_vertex) else latest = possibility.latest_version possibility.possibilities.select! do |possibility| requirement_satisfied_by?(requirement, activated, possibility) end if possibility.latest_version.nil? # ensure there's a possibility for better error messages possibility.possibilities << latest if latest create_conflict unwind_for_conflict else activate_new_spec end end end # Attempts to update the existing vertex's `PossibilitySet` with a filtered version # @return [void] def attempt_to_filter_existing_spec(vertex) filtered_set = filtered_possibility_set(vertex) if !filtered_set.possibilities.empty? activated.set_payload(name, filtered_set) new_requirements = requirements.dup push_state_for_requirements(new_requirements, false) else create_conflict debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } unwind_for_conflict end end # Generates a filtered version of the existing vertex's `PossibilitySet` using the # current state's `requirement` # @param [Object] vertex existing vertex # @return [PossibilitySet] filtered possibility set def filtered_possibility_set(vertex) PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) end # @param [String] requirement_name the spec name to search for # @return [Object] the locked spec named `requirement_name`, if one # is found on {#base} def locked_requirement_named(requirement_name) vertex = base.vertex_named(requirement_name) vertex && vertex.payload end # Add the current {#possibility} to the dependency graph of the current # {#state} # @return [void] def activate_new_spec conflicts.delete(name) debug(depth) { "Activated #{name} at #{possibility}" } activated.set_payload(name, possibility) require_nested_dependencies_for(possibility) end # Requires the dependencies that the recently activated spec has # @param [Object] possibility_set the PossibilitySet that has just been # activated # @return [void] def require_nested_dependencies_for(possibility_set) nested_dependencies = dependencies_for(possibility_set.latest_version) debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } nested_dependencies.each do |d| activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) parent_index = states.size - 1 parents = @parents_of[d] parents << parent_index if parents.empty? end push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) end # Pushes a new {DependencyState} that encapsulates both existing and new # requirements # @param [Array] new_requirements # @param [Boolean] requires_sort # @param [Object] new_activated # @return [void] def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort new_requirement = nil loop do new_requirement = new_requirements.shift break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } end new_name = new_requirement ? name_for(new_requirement) : ''.freeze possibilities = possibilities_for_requirement(new_requirement) handle_missing_or_push_dependency_state DependencyState.new( new_name, new_requirements, new_activated, new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup ) end # Checks a proposed requirement with any existing locked requirement # before generating an array of possibilities for it. # @param [Object] requirement the proposed requirement # @param [Object] activated # @return [Array] possibilities def possibilities_for_requirement(requirement, activated = self.activated) return [] unless requirement if locked_requirement_named(name_for(requirement)) return locked_requirement_possibility_set(requirement, activated) end group_possibilities(search_for(requirement)) end # @param [Object] requirement the proposed requirement # @param [Object] activated # @return [Array] possibility set containing only the locked requirement, if any def locked_requirement_possibility_set(requirement, activated = self.activated) all_possibilities = search_for(requirement) locked_requirement = locked_requirement_named(name_for(requirement)) # Longwinded way to build a possibilities array with either the locked # requirement or nothing in it. Required, since the API for # locked_requirement isn't guaranteed. locked_possibilities = all_possibilities.select do |possibility| requirement_satisfied_by?(locked_requirement, activated, possibility) end group_possibilities(locked_possibilities) end # Build an array of PossibilitySets, with each element representing a group of # dependency versions that all have the same sub-dependency version constraints # and are contiguous. # @param [Array] possibilities an array of possibilities # @return [Array] an array of possibility sets def group_possibilities(possibilities) possibility_sets = [] current_possibility_set = nil possibilities.reverse_each do |possibility| dependencies = dependencies_for(possibility) if current_possibility_set && dependencies_equal?(current_possibility_set.dependencies, dependencies) current_possibility_set.possibilities.unshift(possibility) else possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) current_possibility_set = possibility_sets.first end end possibility_sets end # Pushes a new {DependencyState}. # If the {#specification_provider} says to # {SpecificationProvider#allow_missing?} that particular requirement, and # there are no possibilities for that requirement, then `state` is not # pushed, and the vertex in {#activated} is removed, and we continue # resolving the remaining requirements. # @param [DependencyState] state # @return [void] def handle_missing_or_push_dependency_state(state) if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) state.activated.detach_vertex_named(state.name) push_state_for_requirements(state.requirements.dup, false, state.activated) else states.push(state).tap { activated.tag(state) } end end end end end PK!$$'vendor/molinillo/lib/molinillo/state.rbnu[# frozen_string_literal: true module Gem::Molinillo # A state that a {Resolution} can be in # @attr [String] name the name of the current requirement # @attr [Array] requirements currently unsatisfied requirements # @attr [DependencyGraph] activated the graph of activated dependencies # @attr [Object] requirement the current requirement # @attr [Object] possibilities the possibilities to satisfy the current requirement # @attr [Integer] depth the depth of the resolution # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name # @attr [Array] unused_unwind_options unwinds for previous conflicts that weren't explored ResolutionState = Struct.new( :name, :requirements, :activated, :requirement, :possibilities, :depth, :conflicts, :unused_unwind_options ) class ResolutionState # Returns an empty resolution state # @return [ResolutionState] an empty state def self.empty new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) end end # A state that encapsulates a set of {#requirements} with an {Array} of # possibilities class DependencyState < ResolutionState # Removes a possibility from `self` # @return [PossibilityState] a state with a single possibility, # the possibility that was removed from `self` def pop_possibility_state PossibilityState.new( name, requirements.dup, activated, requirement, [possibilities.pop], depth + 1, conflicts.dup, unused_unwind_options.dup ).tap do |state| state.activated.tag(state) end end end # A state that encapsulates a single possibility to fulfill the given # {#requirement} class PossibilityState < ResolutionState end end PK!~R 2vendor/molinillo/lib/molinillo/dependency_graph.rbnu[# frozen_string_literal: true require_relative '../../../../vendored_tsort' require_relative 'dependency_graph/log' require_relative 'dependency_graph/vertex' module Gem::Molinillo # A directed acyclic graph that is tuned to hold named dependencies class DependencyGraph include Enumerable # Enumerates through the vertices of the graph. # @return [Array] The graph's vertices. def each return vertices.values.each unless block_given? vertices.values.each { |v| yield v } end include Gem::TSort # @!visibility private alias tsort_each_node each # @!visibility private def tsort_each_child(vertex, &block) vertex.successors.each(&block) end # Topologically sorts the given vertices. # @param [Enumerable] vertices the vertices to be sorted, which must # all belong to the same graph. # @return [Array] The sorted vertices. def self.tsort(vertices) Gem::TSort.tsort( lambda { |b| vertices.each(&b) }, lambda { |v, &b| (v.successors & vertices).each(&b) } ) end # A directed edge of a {DependencyGraph} # @attr [Vertex] origin The origin of the directed edge # @attr [Vertex] destination The destination of the directed edge # @attr [Object] requirement The requirement the directed edge represents Edge = Struct.new(:origin, :destination, :requirement) # @return [{String => Vertex}] the vertices of the dependency graph, keyed # by {Vertex#name} attr_reader :vertices # @return [Log] the op log for this graph attr_reader :log # Initializes an empty dependency graph def initialize @vertices = {} @log = Log.new end # Tags the current state of the dependency as the given tag # @param [Object] tag an opaque tag for the current state of the graph # @return [Void] def tag(tag) log.tag(self, tag) end # Rewinds the graph to the state tagged as `tag` # @param [Object] tag the tag to rewind to # @return [Void] def rewind_to(tag) log.rewind_to(self, tag) end # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} # are properly copied. # @param [DependencyGraph] other the graph to copy. def initialize_copy(other) super @vertices = {} @log = other.log.dup traverse = lambda do |new_v, old_v| return if new_v.outgoing_edges.size == old_v.outgoing_edges.size old_v.outgoing_edges.each do |edge| destination = add_vertex(edge.destination.name, edge.destination.payload) add_edge_no_circular(new_v, destination, edge.requirement) traverse.call(destination, edge.destination) end end other.vertices.each do |name, vertex| new_vertex = add_vertex(name, vertex.payload, vertex.root?) new_vertex.explicit_requirements.replace(vertex.explicit_requirements) traverse.call(new_vertex, vertex) end end # @return [String] a string suitable for debugging def inspect "#{self.class}:#{vertices.values.inspect}" end # @param [Hash] options options for dot output. # @return [String] Returns a dot format representation of the graph def to_dot(options = {}) edge_label = options.delete(:edge_label) raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? dot_vertices = [] dot_edges = [] vertices.each do |n, v| dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" v.outgoing_edges.each do |e| label = edge_label ? edge_label.call(e) : e.requirement dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" end end dot_vertices.uniq! dot_vertices.sort! dot_edges.uniq! dot_edges.sort! dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') dot.join("\n") end # @param [DependencyGraph] other # @return [Boolean] whether the two dependency graphs are equal, determined # by a recursive traversal of each {#root_vertices} and its # {Vertex#successors} def ==(other) return false unless other return true if equal?(other) vertices.each do |name, vertex| other_vertex = other.vertex_named(name) return false unless other_vertex return false unless vertex.payload == other_vertex.payload return false unless other_vertex.successors.to_set == vertex.successors.to_set end end # @param [String] name # @param [Object] payload # @param [Array] parent_names # @param [Object] requirement the requirement that is requiring the child # @return [void] def add_child_vertex(name, payload, parent_names, requirement) root = !parent_names.delete(nil) { true } vertex = add_vertex(name, payload, root) vertex.explicit_requirements << requirement if root parent_names.each do |parent_name| parent_vertex = vertex_named(parent_name) add_edge(parent_vertex, vertex, requirement) end vertex end # Adds a vertex with the given name, or updates the existing one. # @param [String] name # @param [Object] payload # @return [Vertex] the vertex that was added to `self` def add_vertex(name, payload, root = false) log.add_vertex(self, name, payload, root) end # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively # removing any non-root vertices that were orphaned in the process # @param [String] name # @return [Array] the vertices which have been detached def detach_vertex_named(name) log.detach_vertex_named(self, name) end # @param [String] name # @return [Vertex,nil] the vertex with the given name def vertex_named(name) vertices[name] end # @param [String] name # @return [Vertex,nil] the root vertex with the given name def root_vertex_named(name) vertex = vertex_named(name) vertex if vertex && vertex.root? end # Adds a new {Edge} to the dependency graph # @param [Vertex] origin # @param [Vertex] destination # @param [Object] requirement the requirement that this edge represents # @return [Edge] the added edge def add_edge(origin, destination, requirement) if destination.path_to?(origin) raise CircularDependencyError.new(path(destination, origin)) end add_edge_no_circular(origin, destination, requirement) end # Deletes an {Edge} from the dependency graph # @param [Edge] edge # @return [Void] def delete_edge(edge) log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) end # Sets the payload of the vertex with the given name # @param [String] name the name of the vertex # @param [Object] payload the payload # @return [Void] def set_payload(name, payload) log.set_payload(self, name, payload) end private # Adds a new {Edge} to the dependency graph without checking for # circularity. # @param (see #add_edge) # @return (see #add_edge) def add_edge_no_circular(origin, destination, requirement) log.add_edge_no_circular(self, origin.name, destination.name, requirement) end # Returns the path between two vertices # @raise [ArgumentError] if there is no path between the vertices # @param [Vertex] from # @param [Vertex] to # @return [Array] the shortest path from `from` to `to` def path(from, to) distances = Hash.new(vertices.size + 1) distances[from.name] = 0 predecessors = {} each do |vertex| vertex.successors.each do |successor| if distances[successor.name] > distances[vertex.name] + 1 distances[successor.name] = distances[vertex.name] + 1 predecessors[successor] = vertex end end end path = [to] while before = predecessors[to] path << before to = before break if to == from end unless path.last.equal?(from) raise ArgumentError, "There is no path from #{from.name} to #{to.name}" end path.reverse end end end PK!^ژY.Y.'vendor/net-protocol/lib/net/protocol.rbnu[# frozen_string_literal: true # # = net/protocol.rb # #-- # Copyright (c) 1999-2004 Yukihiro Matsumoto # Copyright (c) 1999-2004 Minero Aoki # # written and maintained by Minero Aoki # # This program is free software. You can re-distribute and/or # modify this program under the same terms as Ruby itself, # Ruby Distribute License or GNU General Public License. # # $Id$ #++ # # WARNING: This file is going to remove. # Do not rely on the implementation written in this file. # require 'socket' require_relative '../../../timeout/lib/timeout' require 'io/wait' module Gem::Net # :nodoc: class Protocol #:nodoc: internal use only VERSION = "0.2.2" private def Protocol.protocol_param(name, val) module_eval(<<-End, __FILE__, __LINE__ + 1) def #{name} #{val} end End end def ssl_socket_connect(s, timeout) if timeout while true raise Gem::Net::OpenTimeout if timeout <= 0 start = Process.clock_gettime Process::CLOCK_MONOTONIC # to_io is required because SSLSocket doesn't have wait_readable yet case s.connect_nonblock(exception: false) when :wait_readable; s.to_io.wait_readable(timeout) when :wait_writable; s.to_io.wait_writable(timeout) else; break end timeout -= Process.clock_gettime(Process::CLOCK_MONOTONIC) - start end else s.connect end end end class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end class ProtoFatalError < ProtocolError; end class ProtoUnknownError < ProtocolError; end class ProtoServerError < ProtocolError; end class ProtoAuthError < ProtocolError; end class ProtoCommandError < ProtocolError; end class ProtoRetriableError < ProtocolError; end ProtocRetryError = ProtoRetriableError ## # OpenTimeout, a subclass of Gem::Timeout::Error, is raised if a connection cannot # be created within the open_timeout. class OpenTimeout < Gem::Timeout::Error; end ## # ReadTimeout, a subclass of Gem::Timeout::Error, is raised if a chunk of the # response cannot be read within the read_timeout. class ReadTimeout < Gem::Timeout::Error def initialize(io = nil) @io = io end attr_reader :io def message msg = super if @io msg = "#{msg} with #{@io.inspect}" end msg end end ## # WriteTimeout, a subclass of Gem::Timeout::Error, is raised if a chunk of the # response cannot be written within the write_timeout. Not raised on Windows. class WriteTimeout < Gem::Timeout::Error def initialize(io = nil) @io = io end attr_reader :io def message msg = super if @io msg = "#{msg} with #{@io.inspect}" end msg end end class BufferedIO #:nodoc: internal use only def initialize(io, read_timeout: 60, write_timeout: 60, continue_timeout: nil, debug_output: nil) @io = io @read_timeout = read_timeout @write_timeout = write_timeout @continue_timeout = continue_timeout @debug_output = debug_output @rbuf = ''.b @rbuf_empty = true @rbuf_offset = 0 end attr_reader :io attr_accessor :read_timeout attr_accessor :write_timeout attr_accessor :continue_timeout attr_accessor :debug_output def inspect "#<#{self.class} io=#{@io}>" end def eof? @io.eof? end def closed? @io.closed? end def close @io.close end # # Read # public def read(len, dest = ''.b, ignore_eof = false) LOG "reading #{len} bytes..." read_bytes = 0 begin while read_bytes + rbuf_size < len if s = rbuf_consume_all read_bytes += s.bytesize dest << s end rbuf_fill end s = rbuf_consume(len - read_bytes) read_bytes += s.bytesize dest << s rescue EOFError raise unless ignore_eof end LOG "read #{read_bytes} bytes" dest end def read_all(dest = ''.b) LOG 'reading all...' read_bytes = 0 begin while true if s = rbuf_consume_all read_bytes += s.bytesize dest << s end rbuf_fill end rescue EOFError ; end LOG "read #{read_bytes} bytes" dest end def readuntil(terminator, ignore_eof = false) offset = @rbuf_offset begin until idx = @rbuf.index(terminator, offset) offset = @rbuf.bytesize rbuf_fill end return rbuf_consume(idx + terminator.bytesize - @rbuf_offset) rescue EOFError raise unless ignore_eof return rbuf_consume end end def readline readuntil("\n").chop end private BUFSIZE = 1024 * 16 def rbuf_fill tmp = @rbuf_empty ? @rbuf : nil case rv = @io.read_nonblock(BUFSIZE, tmp, exception: false) when String @rbuf_empty = false if rv.equal?(tmp) @rbuf_offset = 0 else @rbuf << rv rv.clear end return when :wait_readable (io = @io.to_io).wait_readable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io) # continue looping when :wait_writable # OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable. # http://www.openssl.org/support/faq.html#PROG10 (io = @io.to_io).wait_writable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io) # continue looping when nil raise EOFError, 'end of file reached' end while true end def rbuf_flush if @rbuf_empty @rbuf.clear @rbuf_offset = 0 end nil end def rbuf_size @rbuf.bytesize - @rbuf_offset end def rbuf_consume_all rbuf_consume if rbuf_size > 0 end def rbuf_consume(len = nil) if @rbuf_offset == 0 && (len.nil? || len == @rbuf.bytesize) s = @rbuf @rbuf = ''.b @rbuf_offset = 0 @rbuf_empty = true elsif len.nil? s = @rbuf.byteslice(@rbuf_offset..-1) @rbuf = ''.b @rbuf_offset = 0 @rbuf_empty = true else s = @rbuf.byteslice(@rbuf_offset, len) @rbuf_offset += len @rbuf_empty = @rbuf_offset == @rbuf.bytesize rbuf_flush end @debug_output << %Q[-> #{s.dump}\n] if @debug_output s end # # Write # public def write(*strs) writing { write0(*strs) } end alias << write def writeline(str) writing { write0 str + "\r\n" } end private def writing @written_bytes = 0 @debug_output << '<- ' if @debug_output yield @debug_output << "\n" if @debug_output bytes = @written_bytes @written_bytes = nil bytes end def write0(*strs) @debug_output << strs.map(&:dump).join if @debug_output orig_written_bytes = @written_bytes strs.each_with_index do |str, i| need_retry = true case len = @io.write_nonblock(str, exception: false) when Integer @written_bytes += len len -= str.bytesize if len == 0 if strs.size == i+1 return @written_bytes - orig_written_bytes else need_retry = false # next string end elsif len < 0 str = str.byteslice(len, -len) else # len > 0 need_retry = false # next string end # continue looping when :wait_writable (io = @io.to_io).wait_writable(@write_timeout) or raise Gem::Net::WriteTimeout.new(io) # continue looping end while need_retry end end # # Logging # private def LOG_off @save_debug_out = @debug_output @debug_output = nil end def LOG_on @debug_output = @save_debug_out end def LOG(msg) return unless @debug_output @debug_output << msg + "\n" end end class InternetMessageIO < BufferedIO #:nodoc: internal use only def initialize(*, **) super @wbuf = nil end # # Read # def each_message_chunk LOG 'reading message...' LOG_off() read_bytes = 0 while (line = readuntil("\r\n")) != ".\r\n" read_bytes += line.size yield line.delete_prefix('.') end LOG_on() LOG "read message (#{read_bytes} bytes)" end # *library private* (cannot handle 'break') def each_list_item while (str = readuntil("\r\n")) != ".\r\n" yield str.chop end end def write_message_0(src) prev = @written_bytes each_crlf_line(src) do |line| write0 dot_stuff(line) end @written_bytes - prev end # # Write # def write_message(src) LOG "writing message from #{src.class}" LOG_off() len = writing { using_each_crlf_line { write_message_0 src } } LOG_on() LOG "wrote #{len} bytes" len end def write_message_by_block(&block) LOG 'writing message from block' LOG_off() len = writing { using_each_crlf_line { begin block.call(WriteAdapter.new(self.method(:write_message_0))) rescue LocalJumpError # allow `break' from writer block end } } LOG_on() LOG "wrote #{len} bytes" len end private def dot_stuff(s) s.sub(/\A\./, '..') end def using_each_crlf_line @wbuf = ''.b yield if not @wbuf.empty? # unterminated last line write0 dot_stuff(@wbuf.chomp) + "\r\n" elsif @written_bytes == 0 # empty src write0 "\r\n" end write0 ".\r\n" @wbuf = nil end def each_crlf_line(src) buffer_filling(@wbuf, src) do while line = @wbuf.slice!(/\A[^\r\n]*(?:\n|\r(?:\n|(?!\z)))/) yield line.chomp("\n") + "\r\n" end end end def buffer_filling(buf, src) case src when String # for speeding up. 0.step(src.size - 1, 1024) do |i| buf << src[i, 1024] yield end when File # for speeding up. while s = src.read(1024) buf << s yield end else # generic reader src.each do |str| buf << str yield if buf.size > 1024 end yield unless buf.empty? end end end # # The writer adapter class # class WriteAdapter def initialize(writer) @writer = writer end def inspect "#<#{self.class} writer=#{@writer.inspect}>" end def write(str) @writer.call(str) end alias print write def <<(str) write str self end def puts(str = '') write str.chomp("\n") + "\n" end def printf(*args) write sprintf(*args) end end class ReadAdapter #:nodoc: internal use only def initialize(block) @block = block end def inspect "#<#{self.class}>" end def <<(str) call_block(str, &@block) if @block end private # This method is needed because @block must be called by yield, # not Proc#call. You can see difference when using `break' in # the block. def call_block(str) yield str end end module NetPrivate #:nodoc: obsolete Socket = ::Gem::Net::InternetMessageIO end end # module Gem::Net PK!k & & vendor/uri/lib/uri.rbnu[# frozen_string_literal: false # Gem::URI is a module providing classes to handle Uniform Resource Identifiers # (RFC2396[https://www.rfc-editor.org/rfc/rfc2396]). # # == Features # # * Uniform way of handling URIs. # * Flexibility to introduce custom Gem::URI schemes. # * Flexibility to have an alternate Gem::URI::Parser (or just different patterns # and regexp's). # # == Basic example # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI("http://foo.com/posts?id=30&limit=5#time=1305298413") # #=> # # # uri.scheme #=> "http" # uri.host #=> "foo.com" # uri.path #=> "/posts" # uri.query #=> "id=30&limit=5" # uri.fragment #=> "time=1305298413" # # uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413" # # == Adding custom URIs # # module Gem::URI # class RSYNC < Generic # DEFAULT_PORT = 873 # end # register_scheme 'RSYNC', RSYNC # end # #=> Gem::URI::RSYNC # # Gem::URI.scheme_list # #=> {"FILE"=>Gem::URI::File, "FTP"=>Gem::URI::FTP, "HTTP"=>Gem::URI::HTTP, # # "HTTPS"=>Gem::URI::HTTPS, "LDAP"=>Gem::URI::LDAP, "LDAPS"=>Gem::URI::LDAPS, # # "MAILTO"=>Gem::URI::MailTo, "RSYNC"=>Gem::URI::RSYNC} # # uri = Gem::URI("rsync://rsync.foo.com") # #=> # # # == RFC References # # A good place to view an RFC spec is http://www.ietf.org/rfc.html. # # Here is a list of all related RFC's: # - RFC822[https://www.rfc-editor.org/rfc/rfc822] # - RFC1738[https://www.rfc-editor.org/rfc/rfc1738] # - RFC2255[https://www.rfc-editor.org/rfc/rfc2255] # - RFC2368[https://www.rfc-editor.org/rfc/rfc2368] # - RFC2373[https://www.rfc-editor.org/rfc/rfc2373] # - RFC2396[https://www.rfc-editor.org/rfc/rfc2396] # - RFC2732[https://www.rfc-editor.org/rfc/rfc2732] # - RFC3986[https://www.rfc-editor.org/rfc/rfc3986] # # == Class tree # # - Gem::URI::Generic (in uri/generic.rb) # - Gem::URI::File - (in uri/file.rb) # - Gem::URI::FTP - (in uri/ftp.rb) # - Gem::URI::HTTP - (in uri/http.rb) # - Gem::URI::HTTPS - (in uri/https.rb) # - Gem::URI::LDAP - (in uri/ldap.rb) # - Gem::URI::LDAPS - (in uri/ldaps.rb) # - Gem::URI::MailTo - (in uri/mailto.rb) # - Gem::URI::Parser - (in uri/common.rb) # - Gem::URI::REGEXP - (in uri/common.rb) # - Gem::URI::REGEXP::PATTERN - (in uri/common.rb) # - Gem::URI::Util - (in uri/common.rb) # - Gem::URI::Error - (in uri/common.rb) # - Gem::URI::InvalidURIError - (in uri/common.rb) # - Gem::URI::InvalidComponentError - (in uri/common.rb) # - Gem::URI::BadURIError - (in uri/common.rb) # # == Copyright Info # # Author:: Akira Yamada # Documentation:: # Akira Yamada # Dmitry V. Sabanin # Vincent Batts # License:: # Copyright (c) 2001 akira yamada # You can redistribute it and/or modify it under the same term as Ruby. # module Gem::URI end require_relative 'uri/version' require_relative 'uri/common' require_relative 'uri/generic' require_relative 'uri/file' require_relative 'uri/ftp' require_relative 'uri/http' require_relative 'uri/https' require_relative 'uri/ldap' require_relative 'uri/ldaps' require_relative 'uri/mailto' require_relative 'uri/ws' require_relative 'uri/wss' PK!r>cvendor/uri/lib/uri/version.rbnu[module Gem::URI # :stopdoc: VERSION = '1.1.1'.freeze VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end PK! BBvendor/uri/lib/uri/https.rbnu[# frozen_string_literal: false # = uri/https.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'http' module Gem::URI # The default port for HTTPS URIs is 443, and the scheme is 'https:' rather # than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs; # see Gem::URI::HTTP. class HTTPS < HTTP # A Default port of 443 for Gem::URI::HTTPS DEFAULT_PORT = 443 end register_scheme 'HTTPS', HTTPS end PK!HxMFMF$vendor/uri/lib/uri/rfc2396_parser.rbnu[# frozen_string_literal: false #-- # = uri/common.rb # # Author:: Akira Yamada # License:: # You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # module Gem::URI # # Includes Gem::URI::REGEXP::PATTERN # module RFC2396_REGEXP # # Patterns used to parse Gem::URI's # module PATTERN # :stopdoc: # RFC 2396 (Gem::URI Generic Syntax) # RFC 2732 (IPv6 Literal Addresses in URL's) # RFC 2373 (IPv6 Addressing Architecture) # alpha = lowalpha | upalpha ALPHA = "a-zA-Z" # alphanum = alpha | digit ALNUM = "#{ALPHA}\\d" # hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | # "a" | "b" | "c" | "d" | "e" | "f" HEX = "a-fA-F\\d" # escaped = "%" hex hex ESCAPED = "%[#{HEX}]{2}" # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | # "(" | ")" # unreserved = alphanum | mark UNRESERVED = "\\-_.!~*'()#{ALNUM}" # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | # "$" | "," # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | # "$" | "," | "[" | "]" (RFC 2732) RESERVED = ";/?:@&=+$,\\[\\]" # domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)" # toplabel = alpha | alpha *( alphanum | "-" ) alphanum TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)" # hostname = *( domainlabel "." ) toplabel [ "." ] HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?" # :startdoc: end # PATTERN # :startdoc: end # REGEXP # Class that parses String's into Gem::URI's. # # It contains a Hash set of patterns and Regexp's that match and validate. # class RFC2396_Parser include RFC2396_REGEXP # # == Synopsis # # Gem::URI::RFC2396_Parser.new([opts]) # # == Args # # The constructor accepts a hash as options for parser. # Keys of options are pattern names of Gem::URI components # and values of options are pattern strings. # The constructor generates set of regexps for parsing URIs. # # You can use the following keys: # # * :ESCAPED (Gem::URI::PATTERN::ESCAPED in default) # * :UNRESERVED (Gem::URI::PATTERN::UNRESERVED in default) # * :DOMLABEL (Gem::URI::PATTERN::DOMLABEL in default) # * :TOPLABEL (Gem::URI::PATTERN::TOPLABEL in default) # * :HOSTNAME (Gem::URI::PATTERN::HOSTNAME in default) # # == Examples # # p = Gem::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> # # Gem::URI.parse(u.to_s) #=> raises Gem::URI::InvalidURIError # # s = "http://example.com/ABCD" # u1 = p.parse(s) #=> # # u2 = Gem::URI.parse(s) #=> # # u1 == u2 #=> true # u1.eql?(u2) #=> false # def initialize(opts = {}) @pattern = initialize_pattern(opts) @pattern.each_value(&:freeze) @pattern.freeze @regexp = initialize_regexp(@pattern) @regexp.each_value(&:freeze) @regexp.freeze end # The Hash of patterns. # # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # # See also #initialize_regexp. attr_reader :regexp # Returns a split Gem::URI against +regexp[:ABS_URI]+. def split(uri) case uri when '' # null uri when @regexp[:ABS_URI] scheme, opaque, userinfo, host, port, registry, path, query, fragment = $~[1..-1] # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] # absoluteURI = scheme ":" ( hier_part | opaque_part ) # hier_part = ( net_path | abs_path ) [ "?" query ] # opaque_part = uric_no_slash *uric # abs_path = "/" path_segments # net_path = "//" authority [ abs_path ] # authority = server | reg_name # server = [ [ userinfo "@" ] hostport ] if !scheme raise InvalidURIError, "bad Gem::URI (absolute but no scheme): #{uri}" end if !opaque && (!path && (!host && !registry)) raise InvalidURIError, "bad Gem::URI (absolute but no path): #{uri}" end when @regexp[:REL_URI] scheme = nil opaque = nil userinfo, host, port, registry, rel_segment, abs_path, query, fragment = $~[1..-1] if rel_segment && abs_path path = rel_segment + abs_path elsif rel_segment path = rel_segment elsif abs_path path = abs_path end # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] # net_path = "//" authority [ abs_path ] # abs_path = "/" path_segments # rel_path = rel_segment [ abs_path ] # authority = server | reg_name # server = [ [ userinfo "@" ] hostport ] else raise InvalidURIError, "bad Gem::URI (is not Gem::URI?): #{uri}" end path = '' if !path && !opaque # (see RFC2396 Section 5.2) ret = [ scheme, userinfo, host, port, # X registry, # X path, # Y opaque, # Y query, fragment ] return ret end # # == Args # # +uri+:: # String # # == Description # # Parses +uri+ and constructs either matching Gem::URI scheme object # (File, FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or Gem::URI::Generic. # # == Usage # # Gem::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> # # def parse(uri) Gem::URI.for(*self.split(uri), self) end # # == Args # # +uris+:: # an Array of Strings # # == Description # # Attempts to parse and merge a set of URIs. # def join(*uris) uris[0] = convert_to_uri(uris[0]) uris.inject :merge end # # :call-seq: # extract( str ) # extract( str, schemes ) # extract( str, schemes ) {|item| block } # # == Args # # +str+:: # String to search # +schemes+:: # Patterns to apply to +str+ # # == Description # # Attempts to parse and merge a set of URIs. # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # # See also #make_regexp. # def extract(str, schemes = nil) if block_given? str.scan(make_regexp(schemes)) { yield $& } nil else result = [] str.scan(make_regexp(schemes)) { result.push $& } result end end # Returns Regexp that is default +self.regexp[:ABS_URI_REF]+, # unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+. def make_regexp(schemes = nil) unless schemes @regexp[:ABS_URI_REF] else /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end # # :call-seq: # escape( str ) # escape( str, unsafe ) # # == Args # # +str+:: # String to make safe # +unsafe+:: # Regexp to apply. Defaults to +self.regexp[:UNSAFE]+ # # == Description # # Constructs a safe String from +str+, removing unsafe characters, # replacing them with codes. # def escape(str, unsafe = @regexp[:UNSAFE]) unless unsafe.kind_of?(Regexp) # perhaps unsafe is String object unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false) end str.gsub(unsafe) do us = $& tmp = '' us.each_byte do |uc| tmp << sprintf('%%%02X', uc) end tmp end.force_encoding(Encoding::US_ASCII) end # # :call-seq: # unescape( str ) # unescape( str, escaped ) # # == Args # # +str+:: # String to remove escapes from # +escaped+:: # Regexp to apply. Defaults to +self.regexp[:ESCAPED]+ # # == Description # # Removes escapes from +str+. # def unescape(str, escaped = @regexp[:ESCAPED]) enc = str.encoding enc = Encoding::UTF_8 if enc == Encoding::US_ASCII str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } end TO_S = Kernel.instance_method(:to_s) # :nodoc: if TO_S.respond_to?(:bind_call) def inspect # :nodoc: TO_S.bind_call(self) end else def inspect # :nodoc: TO_S.bind(self).call end end private # Constructs the default Hash of patterns. def initialize_pattern(opts = {}) ret = {} ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED) ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME) # RFC 2396 (Gem::URI Generic Syntax) # RFC 2732 (IPv6 Literal Addresses in URL's) # RFC 2373 (IPv6 Addressing Architecture) # uric = reserved | unreserved | escaped ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})" # uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | # "&" | "=" | "+" | "$" | "," ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})" # query = *uric ret[:QUERY] = query = "#{uric}*" # fragment = *uric ret[:FRAGMENT] = fragment = "#{uric}*" # hostname = *( domainlabel "." ) toplabel [ "." ] # reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986 unless hostname ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+" end # RFC 2373, APPENDIX B: # IPv6address = hexpart [ ":" IPv4address ] # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT # hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] # hexseq = hex4 *( ":" hex4) # hex4 = 1*4HEXDIG # # XXX: This definition has a flaw. "::" + IPv4address must be # allowed too. Here is a replacement. # # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" # hex4 = 1*4HEXDIG hex4 = "[#{PATTERN::HEX}]{1,4}" # lastpart = hex4 | IPv4address lastpart = "(?:#{hex4}|#{ipv4addr})" # hexseq1 = *( hex4 ":" ) hex4 hexseq1 = "(?:#{hex4}:)*#{hex4}" # hexseq2 = *( hex4 ":" ) lastpart hexseq2 = "(?:#{hex4}:)*#{lastpart}" # IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ] ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)" # IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT # unused # ipv6reference = "[" IPv6address "]" (RFC 2732) ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]" # host = hostname | IPv4address # host = hostname | IPv4address | IPv6reference (RFC 2732) ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})" # port = *digit ret[:PORT] = port = '\d*' # hostport = host [ ":" port ] ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?" # userinfo = *( unreserved | escaped | # ";" | ":" | "&" | "=" | "+" | "$" | "," ) ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*" # pchar = unreserved | escaped | # ":" | "@" | "&" | "=" | "+" | "$" | "," pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})" # param = *pchar param = "#{pchar}*" # segment = *pchar *( ";" param ) segment = "#{pchar}*(?:;#{param})*" # path_segments = segment *( "/" segment ) ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*" # server = [ [ userinfo "@" ] hostport ] server = "(?:#{userinfo}@)?#{hostport}" # reg_name = 1*( unreserved | escaped | "$" | "," | # ";" | ":" | "@" | "&" | "=" | "+" ) ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+" # authority = server | reg_name authority = "(?:#{server}|#{reg_name})" # rel_segment = 1*( unreserved | escaped | # ";" | "@" | "&" | "=" | "+" | "$" | "," ) ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+" # scheme = alpha *( alpha | digit | "+" | "-" | "." ) ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*" # abs_path = "/" path_segments ret[:ABS_PATH] = abs_path = "/#{path_segments}" # rel_path = rel_segment [ abs_path ] ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?" # net_path = "//" authority [ abs_path ] ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?" # hier_part = ( net_path | abs_path ) [ "?" query ] ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?" # opaque_part = uric_no_slash *uric ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*" # absoluteURI = scheme ":" ( hier_part | opaque_part ) ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})" # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?" # Gem::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?" ret[:X_ABS_URI] = " (#{scheme}): (?# 1: scheme) (?: (#{opaque_part}) (?# 2: opaque) | (?:(?: //(?: (?:(?:(#{userinfo})@)? (?# 3: userinfo) (?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port) | (#{reg_name}) (?# 6: registry) ) | (?!//)) (?# XXX: '//' is the mark for hostport) (#{abs_path})? (?# 7: path) )(?:\\?(#{query}))? (?# 8: query) ) (?:\\#(#{fragment}))? (?# 9: fragment) " ret[:X_REL_URI] = " (?: (?: // (?: (?:(#{userinfo})@)? (?# 1: userinfo) (#{host})?(?::(\\d*))? (?# 2: host, 3: port) | (#{reg_name}) (?# 4: registry) ) ) | (#{rel_segment}) (?# 5: rel_segment) )? (#{abs_path})? (?# 6: abs_path) (?:\\?(#{query}))? (?# 7: query) (?:\\#(#{fragment}))? (?# 8: fragment) " ret end # Constructs the default Hash of Regexp's. def initialize_regexp(pattern) ret = {} # for Gem::URI::split ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) # for Gem::URI::extract ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED) ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED) # for Gem::URI::escape/unescape ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED]) ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]") # for Generic#initialize ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z") ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z") ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z") ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z") ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z") ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z") ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z") ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z") ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z") ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z") ret end # Returns +uri+ as-is if it is Gem::URI, or convert it to Gem::URI if it is # a String. def convert_to_uri(uri) if uri.is_a?(Gem::URI::Generic) uri elsif uri = String.try_convert(uri) parse(uri) else raise ArgumentError, "bad argument (expected Gem::URI object or Gem::URI string)" end end end # class Parser # Backward compatibility for Gem::URI::REGEXP::PATTERN::* RFC2396_Parser.new.pattern.each_pair do |sym, str| unless RFC2396_REGEXP::PATTERN.const_defined?(sym, false) RFC2396_REGEXP::PATTERN.const_set(sym, str) end end end # module Gem::URI PK!_vendor/uri/lib/uri/ldaps.rbnu[# frozen_string_literal: false # = uri/ldap.rb # # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'ldap' module Gem::URI # The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather # than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs; # see Gem::URI::LDAP. class LDAPS < LDAP # A Default port of 636 for Gem::URI::LDAPS DEFAULT_PORT = 636 end register_scheme 'LDAPS', LDAPS end PK!lqq$vendor/uri/lib/uri/rfc3986_parser.rbnu[# frozen_string_literal: true module Gem::URI class RFC3986_Parser # :nodoc: # Gem::URI defined in RFC3986 HOST = %r[ (?\[(?: (? (?:\h{1,4}:){6} (?\h{1,4}:\h{1,4} | (?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d) \.\g\.\g\.\g) ) | ::(?:\h{1,4}:){5}\g | \h{1,4}?::(?:\h{1,4}:){4}\g | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4} | (?:(?:\h{1,4}:){,6}\h{1,4})?:: ) | (?v\h++\.[!$&-.0-9:;=A-Z_a-z~]++) )\]) | \g | (?(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+) ]x USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/ SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source RFC3986_URI = %r[\A (?#{SEG}){0} (? (?#{SCHEME}): (?// (? (?:(?#{USERINFO.source})@)? (?#{HOST.source.delete(" \n")}) (?::(?\d*+))? ) (?(?:/\g*+)?) | (?/((?!/)\g++)?) | (?(?!/)\g++) | (?) ) (?:\?(?[^\#]*+))? (?:\#(?#{FRAGMENT}))? )\z]x RFC3986_relative_ref = %r[\A (?#{SEG}){0} (? (?// (? (?:(?#{USERINFO.source})@)? (?#{HOST.source.delete(" \n")}(?\d*+))? ) (?(?:/\g*+)?) | (?/\g*+) | (?#{SEG_NC}++(?:/\g*+)?) | (?) ) (?:\?(?[^#]*+))? (?:\#(?#{FRAGMENT}))? )\z]x attr_reader :regexp def initialize @regexp = default_regexp.each_value(&:freeze).freeze end def split(uri) #:nodoc: begin uri = uri.to_str rescue NoMethodError raise InvalidURIError, "bad Gem::URI (is not Gem::URI?): #{uri.inspect}" end uri.ascii_only? or raise InvalidURIError, "Gem::URI must be ascii only #{uri.dump}" if m = RFC3986_URI.match(uri) query = m["query"] scheme = m["scheme"] opaque = m["path-rootless"] if opaque opaque << "?#{query}" if query [ scheme, nil, # userinfo nil, # host nil, # port nil, # registry nil, # path opaque, nil, # query m["fragment"] ] else # normal [ scheme, m["userinfo"], m["host"], m["port"], nil, # registry (m["path-abempty"] || m["path-absolute"] || m["path-empty"]), nil, # opaque query, m["fragment"] ] end elsif m = RFC3986_relative_ref.match(uri) [ nil, # scheme m["userinfo"], m["host"], m["port"], nil, # registry, (m["path-abempty"] || m["path-absolute"] || m["path-noscheme"] || m["path-empty"]), nil, # opaque m["query"], m["fragment"] ] else raise InvalidURIError, "bad Gem::URI (is not Gem::URI?): #{uri.inspect}" end end def parse(uri) # :nodoc: Gem::URI.for(*self.split(uri), self) end def join(*uris) # :nodoc: uris[0] = convert_to_uri(uris[0]) uris.inject :merge end # Compatibility for RFC2396 parser def extract(str, schemes = nil, &block) # :nodoc: warn "Gem::URI::RFC3986_PARSER.extract is obsolete. Use Gem::URI::RFC2396_PARSER.extract explicitly.", uplevel: 1 if $VERBOSE RFC2396_PARSER.extract(str, schemes, &block) end # Compatibility for RFC2396 parser def make_regexp(schemes = nil) # :nodoc: warn "Gem::URI::RFC3986_PARSER.make_regexp is obsolete. Use Gem::URI::RFC2396_PARSER.make_regexp explicitly.", uplevel: 1 if $VERBOSE RFC2396_PARSER.make_regexp(schemes) end # Compatibility for RFC2396 parser def escape(str, unsafe = nil) # :nodoc: warn "Gem::URI::RFC3986_PARSER.escape is obsolete. Use Gem::URI::RFC2396_PARSER.escape explicitly.", uplevel: 1 if $VERBOSE unsafe ? RFC2396_PARSER.escape(str, unsafe) : RFC2396_PARSER.escape(str) end # Compatibility for RFC2396 parser def unescape(str, escaped = nil) # :nodoc: warn "Gem::URI::RFC3986_PARSER.unescape is obsolete. Use Gem::URI::RFC2396_PARSER.unescape explicitly.", uplevel: 1 if $VERBOSE escaped ? RFC2396_PARSER.unescape(str, escaped) : RFC2396_PARSER.unescape(str) end @@to_s = Kernel.instance_method(:to_s) if @@to_s.respond_to?(:bind_call) def inspect @@to_s.bind_call(self) end else def inspect @@to_s.bind(self).call end end private def default_regexp # :nodoc: { SCHEME: %r[\A#{SCHEME}\z]o, USERINFO: %r[\A#{USERINFO}\z]o, HOST: %r[\A#{HOST}\z]o, ABS_PATH: %r[\A/#{SEG}*+\z]o, REL_PATH: %r[\A(?!/)#{SEG}++\z]o, QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z], FRAGMENT: %r[\A#{FRAGMENT}\z]o, OPAQUE: %r[\A(?:[^/].*)?\z], PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, } end def convert_to_uri(uri) if uri.is_a?(Gem::URI::Generic) uri elsif uri = String.try_convert(uri) parse(uri) else raise ArgumentError, "bad argument (expected Gem::URI object or Gem::URI string)" end end end # class Parser end # module Gem::URI PK! vendor/uri/lib/uri/mailto.rbnu[# frozen_string_literal: false # = uri/mailto.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # RFC6068, the mailto URL scheme. # class MailTo < Generic include RFC2396_REGEXP # A Default port of nil for Gem::URI::MailTo. DEFAULT_PORT = nil # An Array of the available components for Gem::URI::MailTo. COMPONENT = [ :scheme, :to, :headers ].freeze # :stopdoc: # "hname" and "hvalue" are encodings of an RFC 822 header name and # value, respectively. As with "to", all URL reserved characters must # be encoded. # # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it # consists of zero or more comma-separated mail addresses, possibly # including "phrase" and "comment" components. Note that all URL # reserved characters in "to" must be encoded: in particular, # parentheses, commas, and the percent sign ("%"), which commonly occur # in the "mailbox" syntax. # # Within mailto URLs, the characters "?", "=", "&" are reserved. # ; RFC 6068 # hfields = "?" hfield *( "&" hfield ) # hfield = hfname "=" hfvalue # hfname = *qchar # hfvalue = *qchar # qchar = unreserved / pct-encoded / some-delims # some-delims = "!" / "$" / "'" / "(" / ")" / "*" # / "+" / "," / ";" / ":" / "@" # # ; RFC3986 # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" # pct-encoded = "%" HEXDIG HEXDIG HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/ # practical regexp for email address # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/ # :startdoc: # # == Description # # Creates a new Gem::URI::MailTo object from components, with syntax checking. # # Components can be provided as an Array or Hash. If an Array is used, # the components must be supplied as [to, headers]. # # If a Hash is used, the keys are the component names preceded by colons. # # The headers can be supplied as a pre-encoded string, such as # "subject=subscribe&cc=address", or as an Array of Arrays # like [['subject', 'subscribe'], ['cc', 'address']]. # # Examples: # # require 'rubygems/vendor/uri/lib/uri' # # m1 = Gem::URI::MailTo.build(['joe@example.com', 'subject=Ruby']) # m1.to_s # => "mailto:joe@example.com?subject=Ruby" # # m2 = Gem::URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]]) # m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com" # # m3 = Gem::URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]}) # m3.to_s # => "mailto:listman@example.com?subject=subscribe" # def self.build(args) tmp = Util.make_components_hash(self, args) case tmp[:to] when Array tmp[:opaque] = tmp[:to].join(',') when String tmp[:opaque] = tmp[:to].dup else tmp[:opaque] = '' end if tmp[:headers] query = case tmp[:headers] when Array tmp[:headers].collect { |x| if x.kind_of?(Array) x[0] + '=' + x[1..-1].join else x.to_s end }.join('&') when Hash tmp[:headers].collect { |h,v| h + '=' + v }.join('&') else tmp[:headers].to_s end unless query.empty? tmp[:opaque] << '?' << query end end super(tmp) end # # == Description # # Creates a new Gem::URI::MailTo object from generic URL components with # no syntax checking. # # This method is usually called from Gem::URI::parse, which checks # the validity of each component. # def initialize(*arg) super(*arg) @to = nil @headers = [] # The RFC3986 parser does not normally populate opaque @opaque = "?#{@query}" if @query && !@opaque unless @opaque raise InvalidComponentError, "missing opaque part for mailto URL" end to, header = @opaque.split('?', 2) # allow semicolon as a addr-spec separator # http://support.microsoft.com/kb/820868 unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to raise InvalidComponentError, "unrecognised opaque part for mailtoURL: #{@opaque}" end if arg[10] # arg_check self.to = to self.headers = header else set_to(to) set_headers(header) end end # The primary e-mail address of the URL, as a String. attr_reader :to # E-mail headers set by the URL, as an Array of Arrays. attr_reader :headers # Checks the to +v+ component. def check_to(v) return true unless v return true if v.size == 0 v.split(/[,;]/).each do |addr| # check url safety as path-rootless if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr raise InvalidComponentError, "an address in 'to' is invalid as Gem::URI #{addr.dump}" end # check addr-spec # don't s/\+/ /g addr.gsub!(/%\h\h/, Gem::URI::TBLDECWWWCOMP_) if EMAIL_REGEXP !~ addr raise InvalidComponentError, "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}" end end true end private :check_to # Private setter for to +v+. def set_to(v) @to = v end protected :set_to # Setter for to +v+. def to=(v) check_to(v) set_to(v) v end # Checks the headers +v+ component against either # * HEADER_REGEXP def check_headers(v) return true unless v return true if v.size == 0 if HEADER_REGEXP !~ v raise InvalidComponentError, "bad component(expected opaque component): #{v}" end true end private :check_headers # Private setter for headers +v+. def set_headers(v) @headers = [] if v v.split('&').each do |x| @headers << x.split(/=/, 2) end end end protected :set_headers # Setter for headers +v+. def headers=(v) check_headers(v) set_headers(v) v end # Constructs String from Gem::URI. def to_s @scheme + ':' + if @to @to else '' end + if @headers.size > 0 '?' + @headers.collect{|x| x.join('=')}.join('&') else '' end + if @fragment '#' + @fragment else '' end end # Returns the RFC822 e-mail text equivalent of the URL, as a String. # # Example: # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr") # uri.to_mailtext # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n" # def to_mailtext to = Gem::URI.decode_www_form_component(@to) head = '' body = '' @headers.each do |x| case x[0] when 'body' body = Gem::URI.decode_www_form_component(x[1]) when 'to' to << ', ' + Gem::URI.decode_www_form_component(x[1]) else head << Gem::URI.decode_www_form_component(x[0]).capitalize + ': ' + Gem::URI.decode_www_form_component(x[1]) + "\n" end end "To: #{to} #{head} #{body} " end alias to_rfc822text to_mailtext end register_scheme 'MAILTO', MailTo end PK!hvendor/uri/lib/uri/http.rbnu[# frozen_string_literal: false # = uri/http.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # The syntax of HTTP URIs is defined in RFC1738 section 3.3. # # Note that the Ruby Gem::URI library allows HTTP URLs containing usernames and # passwords. This is not legal as per the RFC, but used to be # supported in Internet Explorer 5 and 6, before the MS04-004 security # update. See . # class HTTP < Generic # A Default port of 80 for Gem::URI::HTTP. DEFAULT_PORT = 80 # An Array of the available components for Gem::URI::HTTP. COMPONENT = %i[ scheme userinfo host port path query fragment ].freeze # # == Description # # Creates a new Gem::URI::HTTP object from components, with syntax checking. # # The components accepted are userinfo, host, port, path, query, and # fragment. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [userinfo, host, port, path, query, fragment]. # # Example: # # uri = Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar') # # uri = Gem::URI::HTTP.build([nil, "www.example.com", nil, "/path", # "query", 'fragment']) # # Currently, if passed userinfo components this method generates # invalid HTTP URIs as per RFC 1738. # def self.build(args) tmp = Util.make_components_hash(self, args) super(tmp) end # Do not allow empty host names, as they are not allowed by RFC 3986. def check_host(v) ret = super if ret && v.empty? raise InvalidComponentError, "bad component(expected host component): #{v}" end ret end # # == Description # # Returns the full path for an HTTP request, as required by Net::HTTP::Get. # # If the Gem::URI contains a query, the full path is Gem::URI#path + '?' + Gem::URI#query. # Otherwise, the path is simply Gem::URI#path. # # Example: # # uri = Gem::URI::HTTP.build(path: '/foo/bar', query: 'test=true') # uri.request_uri # => "/foo/bar?test=true" # def request_uri return unless @path url = @query ? "#@path?#@query" : @path.dup url.start_with?(?/.freeze) ? url : ?/ + url end # # == Description # # Returns the authority for an HTTP uri, as defined in # https://www.rfc-editor.org/rfc/rfc3986#section-3.2. # # # Example: # # Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com" # Gem::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000" # Gem::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com" # def authority if port == default_port host else "#{host}:#{port}" end end # # == Description # # Returns the origin for an HTTP uri, as defined in # https://www.rfc-editor.org/rfc/rfc6454. # # # Example: # # Gem::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com" # Gem::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000" # Gem::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com" # Gem::URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com" # def origin "#{scheme}://#{authority}" end end register_scheme 'HTTP', HTTP end PK!u2vendor/uri/lib/uri/ftp.rbnu[# frozen_string_literal: false # = uri/ftp.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # FTP Gem::URI syntax is defined by RFC1738 section 3.2. # # This class will be redesigned because of difference of implementations; # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it # is a good summary about the de facto spec. # https://datatracker.ietf.org/doc/html/draft-hoffman-ftp-uri-04 # class FTP < Generic # A Default port of 21 for Gem::URI::FTP. DEFAULT_PORT = 21 # # An Array of the available components for Gem::URI::FTP. # COMPONENT = [ :scheme, :userinfo, :host, :port, :path, :typecode ].freeze # # Typecode is "a", "i", or "d". # # * "a" indicates a text file (the FTP command was ASCII) # * "i" indicates a binary file (FTP command IMAGE) # * "d" indicates the contents of a directory should be displayed # TYPECODE = ['a', 'i', 'd'].freeze # Typecode prefix ";type=". TYPECODE_PREFIX = ';type='.freeze def self.new2(user, password, host, port, path, typecode = nil, arg_check = true) # :nodoc: # Do not use this method! Not tested. [Bug #7301] # This methods remains just for compatibility, # Keep it undocumented until the active maintainer is assigned. typecode = nil if typecode.size == 0 if typecode && !TYPECODE.include?(typecode) raise ArgumentError, "bad typecode is specified: #{typecode}" end # do escape self.new('ftp', [user, password], host, port, nil, typecode ? path + TYPECODE_PREFIX + typecode : path, nil, nil, nil, arg_check) end # # == Description # # Creates a new Gem::URI::FTP object from components, with syntax checking. # # The components accepted are +userinfo+, +host+, +port+, +path+, and # +typecode+. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [userinfo, host, port, path, typecode]. # # If the path supplied is absolute, it will be escaped in order to # make it absolute in the Gem::URI. # # Examples: # # require 'rubygems/vendor/uri/lib/uri' # # uri1 = Gem::URI::FTP.build(['user:password', 'ftp.example.com', nil, # '/path/file.zip', 'i']) # uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i" # # uri2 = Gem::URI::FTP.build({:host => 'ftp.example.com', # :path => 'ruby/src'}) # uri2.to_s # => "ftp://ftp.example.com/ruby/src" # def self.build(args) # Fix the incoming path to be generic URL syntax # FTP path -> URL path # foo/bar /foo/bar # /foo/bar /%2Ffoo/bar # if args.kind_of?(Array) args[3] = '/' + args[3].sub(/^\//, '%2F') else args[:path] = '/' + args[:path].sub(/^\//, '%2F') end tmp = Util::make_components_hash(self, args) if tmp[:typecode] if tmp[:typecode].size == 1 tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode] end tmp[:path] << tmp[:typecode] end return super(tmp) end # # == Description # # Creates a new Gem::URI::FTP object from generic URL components with no # syntax checking. # # Unlike build(), this method does not escape the path component as # required by RFC1738; instead it is treated as per RFC2396. # # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, # +opaque+, +query+, and +fragment+, in that order. # def initialize(scheme, userinfo, host, port, registry, path, opaque, query, fragment, parser = nil, arg_check = false) raise InvalidURIError unless path path = path.sub(/^\//,'') path.sub!(/^%2F/,'/') super(scheme, userinfo, host, port, registry, path, opaque, query, fragment, parser, arg_check) @typecode = nil if tmp = @path.index(TYPECODE_PREFIX) typecode = @path[tmp + TYPECODE_PREFIX.size..-1] @path = @path[0..tmp - 1] if arg_check self.typecode = typecode else self.set_typecode(typecode) end end end # typecode accessor. # # See Gem::URI::FTP::COMPONENT. attr_reader :typecode # Validates typecode +v+, # returns +true+ or +false+. # def check_typecode(v) if TYPECODE.include?(v) return true else raise InvalidComponentError, "bad typecode(expected #{TYPECODE.join(', ')}): #{v}" end end private :check_typecode # Private setter for the typecode +v+. # # See also Gem::URI::FTP.typecode=. # def set_typecode(v) @typecode = v end protected :set_typecode # # == Args # # +v+:: # String # # == Description # # Public setter for the typecode +v+ # (with validation). # # See also Gem::URI::FTP.check_typecode. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("ftp://john@ftp.example.com/my_file.img") # #=> # # uri.typecode = "i" # uri # #=> # # def typecode=(typecode) check_typecode(typecode) set_typecode(typecode) typecode end def merge(oth) # :nodoc: tmp = super(oth) if self != tmp tmp.set_typecode(oth.typecode) end return tmp end # Returns the path from an FTP Gem::URI. # # RFC 1738 specifically states that the path for an FTP Gem::URI does not # include the / which separates the Gem::URI path from the Gem::URI host. Example: # # ftp://ftp.example.com/pub/ruby # # The above Gem::URI indicates that the client should connect to # ftp.example.com then cd to pub/ruby from the initial login directory. # # If you want to cd to an absolute directory, you must include an # escaped / (%2F) in the path. Example: # # ftp://ftp.example.com/%2Fpub/ruby # # This method will then return "/pub/ruby". # def path return @path.sub(/^\//,'').sub(/^%2F/,'/') end # Private setter for the path of the Gem::URI::FTP. def set_path(v) super("/" + v.sub(/^\//, "%2F")) end protected :set_path # Returns a String representation of the Gem::URI::FTP. def to_s save_path = nil if @typecode save_path = @path @path = @path + TYPECODE_PREFIX + @typecode end str = super if @typecode @path = save_path end return str end end register_scheme 'FTP', FTP end PK!r ; ; vendor/uri/lib/uri/ws.rbnu[# frozen_string_literal: false # = uri/ws.rb # # Author:: Matt Muller # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # The syntax of WS URIs is defined in RFC6455 section 3. # # Note that the Ruby Gem::URI library allows WS URLs containing usernames and # passwords. This is not legal as per the RFC, but used to be # supported in Internet Explorer 5 and 6, before the MS04-004 security # update. See . # class WS < Generic # A Default port of 80 for Gem::URI::WS. DEFAULT_PORT = 80 # An Array of the available components for Gem::URI::WS. COMPONENT = %i[ scheme userinfo host port path query ].freeze # # == Description # # Creates a new Gem::URI::WS object from components, with syntax checking. # # The components accepted are userinfo, host, port, path, and query. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [userinfo, host, port, path, query]. # # Example: # # uri = Gem::URI::WS.build(host: 'www.example.com', path: '/foo/bar') # # uri = Gem::URI::WS.build([nil, "www.example.com", nil, "/path", "query"]) # # Currently, if passed userinfo components this method generates # invalid WS URIs as per RFC 1738. # def self.build(args) tmp = Util.make_components_hash(self, args) super(tmp) end # # == Description # # Returns the full path for a WS Gem::URI, as required by Net::HTTP::Get. # # If the Gem::URI contains a query, the full path is Gem::URI#path + '?' + Gem::URI#query. # Otherwise, the path is simply Gem::URI#path. # # Example: # # uri = Gem::URI::WS.build(path: '/foo/bar', query: 'test=true') # uri.request_uri # => "/foo/bar?test=true" # def request_uri return unless @path url = @query ? "#@path?#@query" : @path.dup url.start_with?(?/.freeze) ? url : ?/ + url end end register_scheme 'WS', WS end PK!zcrprpvendor/uri/lib/uri/common.rbnu[# frozen_string_literal: true #-- # = uri/common.rb # # Author:: Akira Yamada # License:: # You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative "rfc2396_parser" require_relative "rfc3986_parser" module Gem::URI # The default parser instance for RFC 2396. RFC2396_PARSER = RFC2396_Parser.new Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) # The default parser instance for RFC 3986. RFC3986_PARSER = RFC3986_Parser.new Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) # The default parser instance. DEFAULT_PARSER = RFC3986_PARSER Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) # Set the default parser instance. def self.parser=(parser = RFC3986_PARSER) remove_const(:Parser) if defined?(::Gem::URI::Parser) const_set("Parser", parser.class) remove_const(:PARSER) if defined?(::Gem::URI::PARSER) const_set("PARSER", parser) remove_const(:REGEXP) if defined?(::Gem::URI::REGEXP) remove_const(:PATTERN) if defined?(::Gem::URI::PATTERN) if Parser == RFC2396_Parser const_set("REGEXP", Gem::URI::RFC2396_REGEXP) const_set("PATTERN", Gem::URI::RFC2396_REGEXP::PATTERN) end Parser.new.regexp.each_pair do |sym, str| remove_const(sym) if const_defined?(sym, false) const_set(sym, str) end end self.parser = RFC3986_PARSER def self.const_missing(const) # :nodoc: if const == :REGEXP warn "Gem::URI::REGEXP is obsolete. Use Gem::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE Gem::URI::RFC2396_REGEXP elsif value = RFC2396_PARSER.regexp[const] warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE value elsif value = RFC2396_Parser.const_get(const) warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE value else super end end module Util # :nodoc: def make_components_hash(klass, array_hash) tmp = {} if array_hash.kind_of?(Array) && array_hash.size == klass.component.size - 1 klass.component[1..-1].each_index do |i| begin tmp[klass.component[i + 1]] = array_hash[i].clone rescue TypeError tmp[klass.component[i + 1]] = array_hash[i] end end elsif array_hash.kind_of?(Hash) array_hash.each do |key, value| begin tmp[key] = value.clone rescue TypeError tmp[key] = value end end else raise ArgumentError, "expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})" end tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase return tmp end module_function :make_components_hash end module Schemes # :nodoc: class << self ReservedChars = ".+-" EscapedChars = "\u01C0\u01C1\u01C2" # Use Lo category chars as escaped chars for TruffleRuby, which # does not allow Symbol categories as identifiers. def escape(name) unless name and name.ascii_only? return nil end name.upcase.tr(ReservedChars, EscapedChars) end def unescape(name) name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase end def find(name) const_get(name, false) if name and const_defined?(name, false) end def register(name, klass) unless scheme = escape(name) raise ArgumentError, "invalid character as scheme - #{name}" end const_set(scheme, klass) end def list constants.map { |name| [unescape(name.to_s), const_get(name)] }.to_h end end end private_constant :Schemes # Registers the given +klass+ as the class to be instantiated # when parsing a \Gem::URI with the given +scheme+: # # Gem::URI.register_scheme('MS_SEARCH', Gem::URI::Generic) # => Gem::URI::Generic # Gem::URI.scheme_list['MS_SEARCH'] # => Gem::URI::Generic # # Note that after calling String#upcase on +scheme+, it must be a valid # constant name. def self.register_scheme(scheme, klass) Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: # # Gem::URI.scheme_list # # => # {"MAILTO"=>Gem::URI::MailTo, # "LDAPS"=>Gem::URI::LDAPS, # "WS"=>Gem::URI::WS, # "HTTP"=>Gem::URI::HTTP, # "HTTPS"=>Gem::URI::HTTPS, # "LDAP"=>Gem::URI::LDAP, # "FILE"=>Gem::URI::File, # "FTP"=>Gem::URI::FTP} # # Related: Gem::URI.register_scheme. def self.scheme_list Schemes.list end # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: # # - The new object is an instance of Gem::URI.scheme_list[scheme.upcase]. # - The object is initialized by calling the class initializer # using +scheme+ and +arguments+. # See Gem::URI::Generic.new. # # Examples: # # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top'] # Gem::URI.for('https', *values) # # => # # Gem::URI.for('foo', *values, default: Gem::URI::HTTP) # # => # # def self.for(scheme, *arguments, default: Generic) const_name = Schemes.escape(scheme) uri_class = INITIAL_SCHEMES[const_name] uri_class ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) end # # Base class for all Gem::URI exceptions. # class Error < StandardError; end # # Not a Gem::URI. # class InvalidURIError < Error; end # # Not a Gem::URI component. # class InvalidComponentError < Error; end # # Gem::URI is valid, bad usage is not. # class BadURIError < Error; end # Returns a 9-element array representing the parts of the \Gem::URI # formed from the string +uri+; # each array element is a string or +nil+: # # names = %w[scheme userinfo host port registry path opaque query fragment] # values = Gem::URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # names.zip(values) # # => # [["scheme", "https"], # ["userinfo", "john.doe"], # ["host", "www.example.com"], # ["port", "123"], # ["registry", nil], # ["path", "/forum/questions/"], # ["opaque", nil], # ["query", "tag=networking&order=newest"], # ["fragment", "top"]] # def self.split(uri) PARSER.split(uri) end # Returns a new \Gem::URI object constructed from the given string +uri+: # # Gem::URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => # # Gem::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => # # # It's recommended to first Gem::URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid Gem::URI characters. # def self.parse(uri) PARSER.parse(uri) end # Merges the given Gem::URI strings +str+ # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html]. # # Each string in +str+ is converted to an # {RFC3986 Gem::URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged. # # Examples: # # Gem::URI.join("http://example.com/","main.rbx") # # => # # # Gem::URI.join('http://example.com', 'foo') # # => # # # Gem::URI.join('http://example.com', '/foo', '/bar') # # => # # # Gem::URI.join('http://example.com', '/foo', 'bar') # # => # # # Gem::URI.join('http://example.com', '/foo/', 'bar') # # => # # def self.join(*str) DEFAULT_PARSER.join(*str) end # # == Synopsis # # Gem::URI::extract(str[, schemes][,&blk]) # # == Args # # +str+:: # String to extract URIs from. # +schemes+:: # Limit Gem::URI matching to specific schemes. # # == Description # # Extracts URIs from a string. If block given, iterates through all matched URIs. # Returns nil if block given or array with matches. # # == Usage # # require "rubygems/vendor/uri/lib/uri" # # Gem::URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") # # => ["http://foo.example.com/bla", "mailto:test@example.com"] # def self.extract(str, schemes = nil, &block) # :nodoc: warn "Gem::URI.extract is obsolete", uplevel: 1 if $VERBOSE PARSER.extract(str, schemes, &block) end # # == Synopsis # # Gem::URI::regexp([match_schemes]) # # == Args # # +match_schemes+:: # Array of schemes. If given, resulting regexp matches to URIs # whose scheme is one of the match_schemes. # # == Description # # Returns a Regexp object which matches to Gem::URI-like strings. # The Regexp object returned by this method includes arbitrary # number of capture group (parentheses). Never rely on its number. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # # extract first Gem::URI from html_string # html_string.slice(Gem::URI.regexp) # # # remove ftp URIs # html_string.sub(Gem::URI.regexp(['ftp']), '') # # # You should not rely on the number of parentheses # html_string.scan(Gem::URI.regexp) do |*matches| # p $& # end # def self.regexp(schemes = nil)# :nodoc: warn "Gem::URI.regexp is obsolete", uplevel: 1 if $VERBOSE PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: 256.times do |i| TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) end TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze # :nodoc: TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze TBLDECWWWCOMP_ = {} # :nodoc: 256.times do |i| h, l = i>>4, i&15 TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr end TBLDECWWWCOMP_['+'] = ' ' TBLDECWWWCOMP_.freeze # Returns a URL-encoded string derived from the given string +str+. # # The returned string: # # - Preserves: # # - Characters '*', '.', '-', and '_'. # - Character in ranges 'a'..'z', 'A'..'Z', # and '0'..'9'. # # Example: # # Gem::URI.encode_www_form_component('*.-_azAZ09') # # => "*.-_azAZ09" # # - Converts: # # - Character ' ' to character '+'. # - Any other character to "percent notation"; # the percent notation for character c is '%%%X' % c.ord. # # Example: # # Gem::URI.encode_www_form_component('Here are some punctuation characters: ,;?:') # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A" # # Encoding: # # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored. # - Otherwise +str+ is converted first to Encoding::UTF_8 # (with suitable character replacements), # and then to encoding +enc+. # # In either case, the returned string has forced encoding Encoding::US_ASCII. # # Related: Gem::URI.encode_uri_component (encodes ' ' as '%20'). def self.encode_www_form_component(str, enc=nil) _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc) end # Returns a string decoded from the given \URL-encoded string +str+. # # The given string is first encoded as Encoding::ASCII-8BIT (using String#b), # then decoded (as below), and finally force-encoded to the given encoding +enc+. # # The returned string: # # - Preserves: # # - Characters '*', '.', '-', and '_'. # - Character in ranges 'a'..'z', 'A'..'Z', # and '0'..'9'. # # Example: # # Gem::URI.decode_www_form_component('*.-_azAZ09') # # => "*.-_azAZ09" # # - Converts: # # - Character '+' to character ' '. # - Each "percent notation" to an ASCII character. # # Example: # # Gem::URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A') # # => "Here are some punctuation characters: ,;?:" # # Related: Gem::URI.decode_uri_component (preserves '+'). def self.decode_www_form_component(str, enc=Encoding::UTF_8) _decode_uri_component(/\+|%\h\h/, str, enc) end # Like Gem::URI.encode_www_form_component, except that ' ' (space) # is encoded as '%20' (instead of '+'). def self.encode_uri_component(str, enc=nil) _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc) end # Like Gem::URI.decode_www_form_component, except that '+' is preserved. def self.decode_uri_component(str, enc=Encoding::UTF_8) _decode_uri_component(/%\h\h/, str, enc) end # Returns a string derived from the given string +str+ with # Gem::URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT if enc && enc != Encoding::ASCII_8BIT str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) str.encode!(enc, fallback: ->(x){"&##{x.ord};"}) end str.force_encoding(Encoding::ASCII_8BIT) end str.gsub!(regexp, table) str.force_encoding(Encoding::US_ASCII) end private_class_method :_encode_uri_component # Returns a string decoding characters matching +regexp+ from the # given \URL-encoded string +str+. def self._decode_uri_component(regexp, str, enc) raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) end private_class_method :_decode_uri_component # Returns a URL-encoded string derived from the given # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes] # +enum+. # # The result is suitable for use as form data # for an \HTTP request whose Content-Type is # 'application/x-www-form-urlencoded'. # # The returned string consists of the elements of +enum+, # each converted to one or more URL-encoded strings, # and all joined with character '&'. # # Simple examples: # # Gem::URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]]) # # => "foo=0&bar=1&baz=2" # Gem::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) # # => "foo=0&bar=1&baz=2" # # The returned string is formed using method Gem::URI.encode_www_form_component, # which converts certain characters: # # Gem::URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@') # # => "f%23o=%2F&b-r=%24&b+z=%40" # # When +enum+ is Array-like, each element +ele+ is converted to a field: # # - If +ele+ is an array of two or more elements, # the field is formed from its first two elements # (and any additional elements are ignored): # # name = Gem::URI.encode_www_form_component(ele[0], enc) # value = Gem::URI.encode_www_form_component(ele[1], enc) # "#{name}=#{value}" # # Examples: # # Gem::URI.encode_www_form([%w[foo bar], %w[baz bat bah]]) # # => "foo=bar&baz=bat" # Gem::URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']]) # # => "foo=0&bar=baz" # # - If +ele+ is an array of one element, # the field is formed from ele[0]: # # Gem::URI.encode_www_form_component(ele[0]) # # Example: # # Gem::URI.encode_www_form([['foo'], [:bar], [0]]) # # => "foo&bar&0" # # - Otherwise the field is formed from +ele+: # # Gem::URI.encode_www_form_component(ele) # # Example: # # Gem::URI.encode_www_form(['foo', :bar, 0]) # # => "foo&bar&0" # # The elements of an Array-like +enum+ may be mixture: # # Gem::URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat]) # # => "foo=0&bar=1&baz&bat" # # When +enum+ is Hash-like, # each +key+/+value+ pair is converted to one or more fields: # # - If +value+ is # {Array-convertible}[rdoc-ref:implicit_conversion.rdoc@Array-Convertible+Objects], # each element +ele+ in +value+ is paired with +key+ to form a field: # # name = Gem::URI.encode_www_form_component(key, enc) # value = Gem::URI.encode_www_form_component(ele, enc) # "#{name}=#{value}" # # Example: # # Gem::URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]}) # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2" # # - Otherwise, +key+ and +value+ are paired to form a field: # # name = Gem::URI.encode_www_form_component(key, enc) # value = Gem::URI.encode_www_form_component(value, enc) # "#{name}=#{value}" # # Example: # # Gem::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) # # => "foo=0&bar=1&baz=2" # # The elements of a Hash-like +enum+ may be mixture: # # Gem::URI.encode_www_form({foo: [0, 1], bar: 2}) # # => "foo=0&foo=1&bar=2" # def self.encode_www_form(enum, enc=nil) enum.map do |k,v| if v.nil? encode_www_form_component(k, enc) elsif v.respond_to?(:to_ary) v.to_ary.map do |w| str = encode_www_form_component(k, enc) unless w.nil? str << '=' str << encode_www_form_component(w, enc) end end.join('&') else str = encode_www_form_component(k, enc) str << '=' str << encode_www_form_component(v, enc) end end.join('&') end # Returns name/value pairs derived from the given string +str+, # which must be an ASCII string. # # The method may be used to decode the body of Net::HTTPResponse object +res+ # for which res['Content-Type'] is 'application/x-www-form-urlencoded'. # # The returned data is an array of 2-element subarrays; # each subarray is a name/value pair (both are strings). # Each returned string has encoding +enc+, # and has had invalid characters removed via # {String#scrub}[rdoc-ref:String#scrub]. # # A simple example: # # Gem::URI.decode_www_form('foo=0&bar=1&baz') # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] # # The returned strings have certain conversions, # similar to those performed in Gem::URI.decode_www_form_component: # # Gem::URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40') # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]] # # The given string may contain consecutive separators: # # Gem::URI.decode_www_form('foo=0&&bar=1&&baz=2') # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]] # # A different separator may be specified: # # Gem::URI.decode_www_form('foo=0--bar=1--baz', separator: '--') # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] # def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false) raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only? ary = [] return ary if str.empty? enc = Encoding.find(enc) str.b.each_line(separator) do |string| string.chomp!(separator) key, sep, val = string.partition('=') if isindex if sep.empty? val = key key = +'' end isindex = false end if use__charset_ and key == '_charset_' and e = get_encoding(val) enc = e use__charset_ = false end key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) if val val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) else val = +'' end ary << [key, val] end ary.each do |k, v| k.force_encoding(enc) k.scrub! v.force_encoding(enc) v.scrub! end ary end private =begin command for WEB_ENCODINGS_ curl https://encoding.spec.whatwg.org/encodings.json| ruby -rjson -e 'H={} h={ "shift_jis"=>"Windows-31J", "euc-jp"=>"cp51932", "iso-2022-jp"=>"cp50221", "x-mac-cyrillic"=>"macCyrillic", } JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x| Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next x["labels"].each{|y|H[y]=n} } puts "{" H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]} puts "}" ' =end WEB_ENCODINGS_ = { "unicode-1-1-utf-8"=>"utf-8", "utf-8"=>"utf-8", "utf8"=>"utf-8", "866"=>"ibm866", "cp866"=>"ibm866", "csibm866"=>"ibm866", "ibm866"=>"ibm866", "csisolatin2"=>"iso-8859-2", "iso-8859-2"=>"iso-8859-2", "iso-ir-101"=>"iso-8859-2", "iso8859-2"=>"iso-8859-2", "iso88592"=>"iso-8859-2", "iso_8859-2"=>"iso-8859-2", "iso_8859-2:1987"=>"iso-8859-2", "l2"=>"iso-8859-2", "latin2"=>"iso-8859-2", "csisolatin3"=>"iso-8859-3", "iso-8859-3"=>"iso-8859-3", "iso-ir-109"=>"iso-8859-3", "iso8859-3"=>"iso-8859-3", "iso88593"=>"iso-8859-3", "iso_8859-3"=>"iso-8859-3", "iso_8859-3:1988"=>"iso-8859-3", "l3"=>"iso-8859-3", "latin3"=>"iso-8859-3", "csisolatin4"=>"iso-8859-4", "iso-8859-4"=>"iso-8859-4", "iso-ir-110"=>"iso-8859-4", "iso8859-4"=>"iso-8859-4", "iso88594"=>"iso-8859-4", "iso_8859-4"=>"iso-8859-4", "iso_8859-4:1988"=>"iso-8859-4", "l4"=>"iso-8859-4", "latin4"=>"iso-8859-4", "csisolatincyrillic"=>"iso-8859-5", "cyrillic"=>"iso-8859-5", "iso-8859-5"=>"iso-8859-5", "iso-ir-144"=>"iso-8859-5", "iso8859-5"=>"iso-8859-5", "iso88595"=>"iso-8859-5", "iso_8859-5"=>"iso-8859-5", "iso_8859-5:1988"=>"iso-8859-5", "arabic"=>"iso-8859-6", "asmo-708"=>"iso-8859-6", "csiso88596e"=>"iso-8859-6", "csiso88596i"=>"iso-8859-6", "csisolatinarabic"=>"iso-8859-6", "ecma-114"=>"iso-8859-6", "iso-8859-6"=>"iso-8859-6", "iso-8859-6-e"=>"iso-8859-6", "iso-8859-6-i"=>"iso-8859-6", "iso-ir-127"=>"iso-8859-6", "iso8859-6"=>"iso-8859-6", "iso88596"=>"iso-8859-6", "iso_8859-6"=>"iso-8859-6", "iso_8859-6:1987"=>"iso-8859-6", "csisolatingreek"=>"iso-8859-7", "ecma-118"=>"iso-8859-7", "elot_928"=>"iso-8859-7", "greek"=>"iso-8859-7", "greek8"=>"iso-8859-7", "iso-8859-7"=>"iso-8859-7", "iso-ir-126"=>"iso-8859-7", "iso8859-7"=>"iso-8859-7", "iso88597"=>"iso-8859-7", "iso_8859-7"=>"iso-8859-7", "iso_8859-7:1987"=>"iso-8859-7", "sun_eu_greek"=>"iso-8859-7", "csiso88598e"=>"iso-8859-8", "csisolatinhebrew"=>"iso-8859-8", "hebrew"=>"iso-8859-8", "iso-8859-8"=>"iso-8859-8", "iso-8859-8-e"=>"iso-8859-8", "iso-ir-138"=>"iso-8859-8", "iso8859-8"=>"iso-8859-8", "iso88598"=>"iso-8859-8", "iso_8859-8"=>"iso-8859-8", "iso_8859-8:1988"=>"iso-8859-8", "visual"=>"iso-8859-8", "csisolatin6"=>"iso-8859-10", "iso-8859-10"=>"iso-8859-10", "iso-ir-157"=>"iso-8859-10", "iso8859-10"=>"iso-8859-10", "iso885910"=>"iso-8859-10", "l6"=>"iso-8859-10", "latin6"=>"iso-8859-10", "iso-8859-13"=>"iso-8859-13", "iso8859-13"=>"iso-8859-13", "iso885913"=>"iso-8859-13", "iso-8859-14"=>"iso-8859-14", "iso8859-14"=>"iso-8859-14", "iso885914"=>"iso-8859-14", "csisolatin9"=>"iso-8859-15", "iso-8859-15"=>"iso-8859-15", "iso8859-15"=>"iso-8859-15", "iso885915"=>"iso-8859-15", "iso_8859-15"=>"iso-8859-15", "l9"=>"iso-8859-15", "iso-8859-16"=>"iso-8859-16", "cskoi8r"=>"koi8-r", "koi"=>"koi8-r", "koi8"=>"koi8-r", "koi8-r"=>"koi8-r", "koi8_r"=>"koi8-r", "koi8-ru"=>"koi8-u", "koi8-u"=>"koi8-u", "dos-874"=>"windows-874", "iso-8859-11"=>"windows-874", "iso8859-11"=>"windows-874", "iso885911"=>"windows-874", "tis-620"=>"windows-874", "windows-874"=>"windows-874", "cp1250"=>"windows-1250", "windows-1250"=>"windows-1250", "x-cp1250"=>"windows-1250", "cp1251"=>"windows-1251", "windows-1251"=>"windows-1251", "x-cp1251"=>"windows-1251", "ansi_x3.4-1968"=>"windows-1252", "ascii"=>"windows-1252", "cp1252"=>"windows-1252", "cp819"=>"windows-1252", "csisolatin1"=>"windows-1252", "ibm819"=>"windows-1252", "iso-8859-1"=>"windows-1252", "iso-ir-100"=>"windows-1252", "iso8859-1"=>"windows-1252", "iso88591"=>"windows-1252", "iso_8859-1"=>"windows-1252", "iso_8859-1:1987"=>"windows-1252", "l1"=>"windows-1252", "latin1"=>"windows-1252", "us-ascii"=>"windows-1252", "windows-1252"=>"windows-1252", "x-cp1252"=>"windows-1252", "cp1253"=>"windows-1253", "windows-1253"=>"windows-1253", "x-cp1253"=>"windows-1253", "cp1254"=>"windows-1254", "csisolatin5"=>"windows-1254", "iso-8859-9"=>"windows-1254", "iso-ir-148"=>"windows-1254", "iso8859-9"=>"windows-1254", "iso88599"=>"windows-1254", "iso_8859-9"=>"windows-1254", "iso_8859-9:1989"=>"windows-1254", "l5"=>"windows-1254", "latin5"=>"windows-1254", "windows-1254"=>"windows-1254", "x-cp1254"=>"windows-1254", "cp1255"=>"windows-1255", "windows-1255"=>"windows-1255", "x-cp1255"=>"windows-1255", "cp1256"=>"windows-1256", "windows-1256"=>"windows-1256", "x-cp1256"=>"windows-1256", "cp1257"=>"windows-1257", "windows-1257"=>"windows-1257", "x-cp1257"=>"windows-1257", "cp1258"=>"windows-1258", "windows-1258"=>"windows-1258", "x-cp1258"=>"windows-1258", "x-mac-cyrillic"=>"macCyrillic", "x-mac-ukrainian"=>"macCyrillic", "chinese"=>"gbk", "csgb2312"=>"gbk", "csiso58gb231280"=>"gbk", "gb2312"=>"gbk", "gb_2312"=>"gbk", "gb_2312-80"=>"gbk", "gbk"=>"gbk", "iso-ir-58"=>"gbk", "x-gbk"=>"gbk", "gb18030"=>"gb18030", "big5"=>"big5", "big5-hkscs"=>"big5", "cn-big5"=>"big5", "csbig5"=>"big5", "x-x-big5"=>"big5", "cseucpkdfmtjapanese"=>"cp51932", "euc-jp"=>"cp51932", "x-euc-jp"=>"cp51932", "csiso2022jp"=>"cp50221", "iso-2022-jp"=>"cp50221", "csshiftjis"=>"Windows-31J", "ms932"=>"Windows-31J", "ms_kanji"=>"Windows-31J", "shift-jis"=>"Windows-31J", "shift_jis"=>"Windows-31J", "sjis"=>"Windows-31J", "windows-31j"=>"Windows-31J", "x-sjis"=>"Windows-31J", "cseuckr"=>"euc-kr", "csksc56011987"=>"euc-kr", "euc-kr"=>"euc-kr", "iso-ir-149"=>"euc-kr", "korean"=>"euc-kr", "ks_c_5601-1987"=>"euc-kr", "ks_c_5601-1989"=>"euc-kr", "ksc5601"=>"euc-kr", "ksc_5601"=>"euc-kr", "windows-949"=>"euc-kr", "utf-16be"=>"utf-16be", "utf-16"=>"utf-16le", "utf-16le"=>"utf-16le", } # :nodoc: Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor) # :nodoc: # return encoding or nil # http://encoding.spec.whatwg.org/#concept-encoding-get def self.get_encoding(label) Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil end end # module Gem::URI module Gem # # Returns a \Gem::URI object derived from the given +uri+, # which may be a \Gem::URI string or an existing \Gem::URI object: # # require 'rubygems/vendor/uri/lib/uri' # # Returns a new Gem::URI. # uri = Gem::URI('http://github.com/ruby/ruby') # # => # # # Returns the given Gem::URI. # Gem::URI(uri) # # => # # # You must require 'rubygems/vendor/uri/lib/uri' to use this method. # def URI(uri) if uri.is_a?(Gem::URI::Generic) uri elsif uri = String.try_convert(uri) Gem::URI.parse(uri) else raise ArgumentError, "bad argument (expected Gem::URI object or Gem::URI string)" end end module_function :URI end PK!H)<''vendor/uri/lib/uri/wss.rbnu[# frozen_string_literal: false # = uri/wss.rb # # Author:: Matt Muller # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'ws' module Gem::URI # The default port for WSS URIs is 443, and the scheme is 'wss:' rather # than 'ws:'. Other than that, WSS URIs are identical to WS URIs; # see Gem::URI::WS. class WSS < WS # A Default port of 443 for Gem::URI::WSS DEFAULT_PORT = 443 end register_scheme 'WSS', WSS end PK!z!l l vendor/uri/lib/uri/file.rbnu[# frozen_string_literal: true require_relative 'generic' module Gem::URI # # The "file" Gem::URI is defined by RFC8089. # class File < Generic # A Default port of nil for Gem::URI::File. DEFAULT_PORT = nil # # An Array of the available components for Gem::URI::File. # COMPONENT = [ :scheme, :host, :path ].freeze # # == Description # # Creates a new Gem::URI::File object from components, with syntax checking. # # The components accepted are +host+ and +path+. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [host, path]. # # A path from e.g. the File class should be escaped before # being passed. # # Examples: # # require 'rubygems/vendor/uri/lib/uri' # # uri1 = Gem::URI::File.build(['host.example.com', '/path/file.zip']) # uri1.to_s # => "file://host.example.com/path/file.zip" # # uri2 = Gem::URI::File.build({:host => 'host.example.com', # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # # uri3 = Gem::URI::File.build({:path => Gem::URI::RFC2396_PARSER.escape('/path/my file.txt')}) # uri3.to_s # => "file:///path/my%20file.txt" # def self.build(args) tmp = Util::make_components_hash(self, args) super(tmp) end # Protected setter for the host component +v+. # # See also Gem::URI::Generic.host=. # def set_host(v) v = "" if v.nil? || v == "localhost" @host = v end # do nothing def set_port(v) end # raise InvalidURIError def check_userinfo(user) raise Gem::URI::InvalidURIError, "cannot set userinfo for file Gem::URI" end # raise InvalidURIError def check_user(user) raise Gem::URI::InvalidURIError, "cannot set user for file Gem::URI" end # raise InvalidURIError def check_password(user) raise Gem::URI::InvalidURIError, "cannot set password for file Gem::URI" end # do nothing def set_userinfo(v) end # do nothing def set_user(v) end # do nothing def set_password(v) end end register_scheme 'FILE', File end PK!Pyjjvendor/uri/lib/uri/ldap.rbnu[# frozen_string_literal: false # = uri/ldap.rb # # Author:: # Takaaki Tateishi # Akira Yamada # License:: # Gem::URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada. # You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'generic' module Gem::URI # # LDAP Gem::URI SCHEMA (described in RFC2255). #-- # ldap:///[?[?[?[?]]]] #++ class LDAP < Generic # A Default port of 389 for Gem::URI::LDAP. DEFAULT_PORT = 389 # An Array of the available components for Gem::URI::LDAP. COMPONENT = [ :scheme, :host, :port, :dn, :attributes, :scope, :filter, :extensions, ].freeze # Scopes available for the starting point. # # * SCOPE_BASE - the Base DN # * SCOPE_ONE - one level under the Base DN, not including the base DN and # not including any entries under this # * SCOPE_SUB - subtrees, all entries at all levels # SCOPE = [ SCOPE_ONE = 'one', SCOPE_SUB = 'sub', SCOPE_BASE = 'base', ].freeze # # == Description # # Creates a new Gem::URI::LDAP object from components, with syntax checking. # # The components accepted are host, port, dn, attributes, # scope, filter, and extensions. # # The components should be provided either as an Array, or as a Hash # with keys formed by preceding the component names with a colon. # # If an Array is used, the components must be passed in the # order [host, port, dn, attributes, scope, filter, extensions]. # # Example: # # uri = Gem::URI::LDAP.build({:host => 'ldap.example.com', # :dn => '/dc=example'}) # # uri = Gem::URI::LDAP.build(["ldap.example.com", nil, # "/dc=example;dc=com", "query", nil, nil, nil]) # def self.build(args) tmp = Util::make_components_hash(self, args) if tmp[:dn] tmp[:path] = tmp[:dn] end query = [] [:extensions, :filter, :scope, :attributes].collect do |x| next if !tmp[x] && query.size == 0 query.unshift(tmp[x]) end tmp[:query] = query.join('?') return super(tmp) end # # == Description # # Creates a new Gem::URI::LDAP object from generic Gem::URI components as per # RFC 2396. No LDAP-specific syntax checking is performed. # # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, # +opaque+, +query+, and +fragment+, in that order. # # Example: # # uri = Gem::URI::LDAP.new("ldap", nil, "ldap.example.com", nil, nil, # "/dc=example;dc=com", nil, "query", nil) # # See also Gem::URI::Generic.new. # def initialize(*arg) super(*arg) if @fragment raise InvalidURIError, 'bad LDAP URL' end parse_dn parse_query end # Private method to cleanup +dn+ from using the +path+ component attribute. def parse_dn raise InvalidURIError, 'bad LDAP URL' unless @path @dn = @path[1..-1] end private :parse_dn # Private method to cleanup +attributes+, +scope+, +filter+, and +extensions+ # from using the +query+ component attribute. def parse_query @attributes = nil @scope = nil @filter = nil @extensions = nil if @query attrs, scope, filter, extensions = @query.split('?') @attributes = attrs if attrs && attrs.size > 0 @scope = scope if scope && scope.size > 0 @filter = filter if filter && filter.size > 0 @extensions = extensions if extensions && extensions.size > 0 end end private :parse_query # Private method to assemble +query+ from +attributes+, +scope+, +filter+, and +extensions+. def build_path_query @path = '/' + @dn query = [] [@extensions, @filter, @scope, @attributes].each do |x| next if !x && query.size == 0 query.unshift(x) end @query = query.join('?') end private :build_path_query # Returns dn. def dn @dn end # Private setter for dn +val+. def set_dn(val) @dn = val build_path_query @dn end protected :set_dn # Setter for dn +val+. def dn=(val) set_dn(val) val end # Returns attributes. def attributes @attributes end # Private setter for attributes +val+. def set_attributes(val) @attributes = val build_path_query @attributes end protected :set_attributes # Setter for attributes +val+. def attributes=(val) set_attributes(val) val end # Returns scope. def scope @scope end # Private setter for scope +val+. def set_scope(val) @scope = val build_path_query @scope end protected :set_scope # Setter for scope +val+. def scope=(val) set_scope(val) val end # Returns filter. def filter @filter end # Private setter for filter +val+. def set_filter(val) @filter = val build_path_query @filter end protected :set_filter # Setter for filter +val+. def filter=(val) set_filter(val) val end # Returns extensions. def extensions @extensions end # Private setter for extensions +val+. def set_extensions(val) @extensions = val build_path_query @extensions end protected :set_extensions # Setter for extensions +val+. def extensions=(val) set_extensions(val) val end # Checks if Gem::URI has a path. # For Gem::URI::LDAP this will return +false+. def hierarchical? false end end register_scheme 'LDAP', LDAP end PK!' ייvendor/uri/lib/uri/generic.rbnu[# frozen_string_literal: true # = uri/generic.rb # # Author:: Akira Yamada # License:: You can redistribute it and/or modify it under the same term as Ruby. # # See Gem::URI for general documentation # require_relative 'common' autoload :IPSocket, 'socket' autoload :IPAddr, 'ipaddr' module Gem::URI # # Base class for all Gem::URI classes. # Implements generic Gem::URI syntax as per RFC 2396. # class Generic include Gem::URI # # A Default port of nil for Gem::URI::Generic. # DEFAULT_PORT = nil # # Returns default port. # def self.default_port self::DEFAULT_PORT end # # Returns default port. # def default_port self.class.default_port end # # An Array of the available components for Gem::URI::Generic. # COMPONENT = [ :scheme, :userinfo, :host, :port, :registry, :path, :opaque, :query, :fragment ].freeze # # Components of the Gem::URI in the order. # def self.component self::COMPONENT end USE_REGISTRY = false # :nodoc: def self.use_registry # :nodoc: self::USE_REGISTRY end # # == Synopsis # # See ::new. # # == Description # # At first, tries to create a new Gem::URI::Generic instance using # Gem::URI::Generic::build. But, if exception Gem::URI::InvalidComponentError is raised, # then it does Gem::URI::RFC2396_PARSER.escape all Gem::URI components and tries again. # def self.build2(args) begin return self.build(args) rescue InvalidComponentError if args.kind_of?(Array) return self.build(args.collect{|x| if x.is_a?(String) Gem::URI::RFC2396_PARSER.escape(x) else x end }) elsif args.kind_of?(Hash) tmp = {} args.each do |key, value| tmp[key] = if value Gem::URI::RFC2396_PARSER.escape(value) else value end end return self.build(tmp) end end end # # == Synopsis # # See ::new. # # == Description # # Creates a new Gem::URI::Generic instance from components of Gem::URI::Generic # with check. Components are: scheme, userinfo, host, port, registry, path, # opaque, query, and fragment. You can provide arguments either by an Array or a Hash. # See ::new for hash keys to use or for order of array items. # def self.build(args) if args.kind_of?(Array) && args.size == ::Gem::URI::Generic::COMPONENT.size tmp = args.dup elsif args.kind_of?(Hash) tmp = ::Gem::URI::Generic::COMPONENT.collect do |c| if args.include?(c) args[c] else nil end end else component = self.component rescue ::Gem::URI::Generic::COMPONENT raise ArgumentError, "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil tmp << true return self.new(*tmp) end # # == Args # # +scheme+:: # Protocol scheme, i.e. 'http','ftp','mailto' and so on. # +userinfo+:: # User name and password, i.e. 'sdmitry:bla'. # +host+:: # Server host name. # +port+:: # Server port. # +registry+:: # Registry of naming authorities. # +path+:: # Path on server. # +opaque+:: # Opaque part. # +query+:: # Query data. # +fragment+:: # Part of the Gem::URI after '#' character. # +parser+:: # Parser for internal use [Gem::URI::DEFAULT_PARSER by default]. # +arg_check+:: # Check arguments [false by default]. # # == Description # # Creates a new Gem::URI::Generic instance from ``generic'' components without check. # def initialize(scheme, userinfo, host, port, registry, path, opaque, query, fragment, parser = DEFAULT_PARSER, arg_check = false) @scheme = nil @user = nil @password = nil @host = nil @port = nil @path = nil @query = nil @opaque = nil @fragment = nil @parser = parser == DEFAULT_PARSER ? nil : parser if arg_check self.scheme = scheme self.hostname = host self.port = port self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) self.set_host(host) self.set_port(port) self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) self.fragment=(fragment) end if registry raise InvalidURIError, "the scheme #{@scheme} does not accept registry part: #{registry} (or bad hostname?)" end @scheme&.freeze self.set_path('') if !@path && !@opaque # (see RFC2396 Section 5.2) self.set_port(self.default_port) if self.default_port && !@port end # # Returns the scheme component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").scheme #=> "http" # attr_reader :scheme # Returns the host component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").host #=> "foo" # # It returns nil if no host component exists. # # Gem::URI("mailto:foo@example.org").host #=> nil # # The component does not contain the port number. # # Gem::URI("http://foo:8080/bar/baz").host #=> "foo" # # Since IPv6 addresses are wrapped with brackets in URIs, # this method returns IPv6 addresses wrapped with brackets. # This form is not appropriate to pass to socket methods such as TCPSocket.open. # If unwrapped host names are required, use the #hostname method. # # Gem::URI("http://[::1]/bar/baz").host #=> "[::1]" # Gem::URI("http://[::1]/bar/baz").hostname #=> "::1" # attr_reader :host # Returns the port component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").port #=> 80 # Gem::URI("http://foo:8080/bar/baz").port #=> 8080 # attr_reader :port def registry # :nodoc: nil end # Returns the path component of the Gem::URI. # # Gem::URI("http://foo/bar/baz").path #=> "/bar/baz" # attr_reader :path # Returns the query component of the Gem::URI. # # Gem::URI("http://foo/bar/baz?search=FooBar").query #=> "search=FooBar" # attr_reader :query # Returns the opaque part of the Gem::URI. # # Gem::URI("mailto:foo@example.org").opaque #=> "foo@example.org" # Gem::URI("http://foo/bar/baz").opaque #=> nil # # The portion of the path that does not make use of the slash '/'. # The path typically refers to an absolute path or an opaque part. # (See RFC2396 Section 3 and 5.2.) # attr_reader :opaque # Returns the fragment component of the Gem::URI. # # Gem::URI("http://foo/bar/baz?search=FooBar#ponies").fragment #=> "ponies" # attr_reader :fragment # Returns the parser to be used. # # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser DEFAULT_PARSER else @parser || DEFAULT_PARSER end end # Replaces self by other Gem::URI object. # def replace!(oth) if self.class != oth.class raise ArgumentError, "expected #{self.class} object" end component.each do |c| self.__send__("#{c}=", oth.__send__(c)) end end private :replace! # # Components of the Gem::URI in the order. # def component self.class.component end # # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v raise InvalidComponentError, "bad component(expected scheme component): #{v}" end return true end private :check_scheme # Protected setter for the scheme component +v+. # # See also Gem::URI::Generic.scheme=. # def set_scheme(v) @scheme = v&.downcase end protected :set_scheme # # == Args # # +v+:: # String # # == Description # # Public setter for the scheme component +v+ # (with validation). # # See also Gem::URI::Generic.check_scheme. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.scheme = "https" # uri.to_s #=> "https://my.example.com" # def scheme=(v) check_scheme(v) set_scheme(v) v end # # Checks the +user+ and +password+. # # If +password+ is not provided, then +user+ is # split, using Gem::URI::Generic.split_userinfo, to # pull +user+ and +password. # # See also Gem::URI::Generic.check_user, Gem::URI::Generic.check_password. # def check_userinfo(user, password = nil) if !password user, password = split_userinfo(user) end check_user(user) check_password(password, user) return true end private :check_userinfo # # Checks the user +v+ component for RFC2396 compliance # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. # def check_user(v) if @opaque raise InvalidURIError, "cannot set user with opaque" end return v unless v if parser.regexp[:USERINFO] !~ v raise InvalidComponentError, "bad component(expected userinfo component or user component): #{v}" end return true end private :check_user # # Checks the password +v+ component for RFC2396 compliance # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. # def check_password(v, user = @user) if @opaque raise InvalidURIError, "cannot set password with opaque" end return v unless v if !user raise InvalidURIError, "password component depends user component" end if parser.regexp[:USERINFO] !~ v raise InvalidComponentError, "bad password component" end return true end private :check_password # # Sets userinfo, argument is string like 'name:pass'. # def userinfo=(userinfo) if userinfo.nil? return nil end check_userinfo(*userinfo) set_userinfo(*userinfo) # returns userinfo end # # == Args # # +v+:: # String # # == Description # # Public setter for the +user+ component # (with validation). # # See also Gem::URI::Generic.check_user. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://john:S3nsit1ve@my.example.com") # uri.user = "sam" # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com" # def user=(user) check_user(user) set_user(user) # returns user end # # == Args # # +v+:: # String # # == Description # # Public setter for the +password+ component # (with validation). # # See also Gem::URI::Generic.check_password. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://john:S3nsit1ve@my.example.com") # uri.password = "V3ry_S3nsit1ve" # uri.to_s #=> "http://john:V3ry_S3nsit1ve@my.example.com" # def password=(password) check_password(password) set_password(password) # returns password end # Protected setter for the +user+ component, and +password+ if available # (with validation). # # See also Gem::URI::Generic.userinfo=. # def set_userinfo(user, password = nil) unless password user, password = split_userinfo(user) end @user = user @password = password [@user, @password] end protected :set_userinfo # Protected setter for the user component +v+. # # See also Gem::URI::Generic.user=. # def set_user(v) set_userinfo(v, nil) v end protected :set_user # Protected setter for the password component +v+. # # See also Gem::URI::Generic.password=. # def set_password(v) @password = v # returns v end protected :set_password # Returns the userinfo +ui+ as [user, password] # if properly formatted as 'user:password'. def split_userinfo(ui) return nil, nil unless ui user, password = ui.split(':', 2) return user, password end private :split_userinfo # Escapes 'user:password' +v+ based on RFC 1738 section 3.1. def escape_userpass(v) parser.escape(v, /[@:\/]/o) # RFC 1738 section 3.1 #/ end private :escape_userpass # Returns the userinfo, either as 'user' or 'user:password'. def userinfo if @user.nil? nil elsif @password.nil? @user else @user + ':' + @password end end # Returns the user component (without Gem::URI decoding). def user @user end # Returns the password component (without Gem::URI decoding). def password @password end # Returns the authority info (array of user, password, host and # port), if any is set. Or returns +nil+. def authority return @user, @password, @host, @port if @user || @password || @host || @port end # Returns the user component after Gem::URI decoding. def decoded_user Gem::URI.decode_uri_component(@user) if @user end # Returns the password component after Gem::URI decoding. def decoded_password Gem::URI.decode_uri_component(@password) if @password end # # Checks the host +v+ component for RFC2396 compliance # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. # def check_host(v) return v unless v if @opaque raise InvalidURIError, "cannot set host with registry or opaque" elsif parser.regexp[:HOST] !~ v raise InvalidComponentError, "bad component(expected host component): #{v}" end return true end private :check_host # Protected setter for the host component +v+. # # See also Gem::URI::Generic.host=. # def set_host(v) @host = v end protected :set_host # Protected setter for the authority info (+user+, +password+, +host+ # and +port+). If +port+ is +nil+, +default_port+ will be set. # protected def set_authority(user, password, host, port = nil) @user, @password, @host, @port = user, password, host, port || self.default_port end # # == Args # # +v+:: # String # # == Description # # Public setter for the host component +v+ # (with validation). # # See also Gem::URI::Generic.check_host. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.host = "foo.com" # uri.to_s #=> "http://foo.com" # def host=(v) check_host(v) set_host(v) set_userinfo(nil) v end # Extract the host part of the Gem::URI and unwrap brackets for IPv6 addresses. # # This method is the same as Gem::URI::Generic#host except # brackets for IPv6 (and future IP) addresses are removed. # # uri = Gem::URI("http://[::1]/bar") # uri.hostname #=> "::1" # uri.host #=> "[::1]" # def hostname v = self.host v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v end # Sets the host part of the Gem::URI as the argument with brackets for IPv6 addresses. # # This method is the same as Gem::URI::Generic#host= except # the argument can be a bare IPv6 address. # # uri = Gem::URI("http://foo/bar") # uri.hostname = "::1" # uri.to_s #=> "http://[::1]/bar" # # If the argument seems to be an IPv6 address, # it is wrapped with brackets. # def hostname=(v) v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':') self.host = v end # # Checks the port +v+ component for RFC2396 compliance # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. # def check_port(v) return v unless v if @opaque raise InvalidURIError, "cannot set port with registry or opaque" elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v raise InvalidComponentError, "bad component(expected port component): #{v.inspect}" end return true end private :check_port # Protected setter for the port component +v+. # # See also Gem::URI::Generic.port=. # def set_port(v) v = v.empty? ? nil : v.to_i unless !v || v.kind_of?(Integer) @port = v end protected :set_port # # == Args # # +v+:: # String # # == Description # # Public setter for the port component +v+ # (with validation). # # See also Gem::URI::Generic.check_port. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.port = 8080 # uri.to_s #=> "http://my.example.com:8080" # def port=(v) check_port(v) set_port(v) set_userinfo(nil) port end def check_registry(v) # :nodoc: raise InvalidURIError, "cannot set registry" end private :check_registry def set_registry(v) # :nodoc: raise InvalidURIError, "cannot set registry" end protected :set_registry def registry=(v) # :nodoc: raise InvalidURIError, "cannot set registry" end # # Checks the path +v+ component for RFC2396 compliance # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, # with a path component defined. # def check_path(v) # raise if both hier and opaque are not nil, because: # absoluteURI = scheme ":" ( hier_part | opaque_part ) # hier_part = ( net_path | abs_path ) [ "?" query ] if v && @opaque raise InvalidURIError, "path conflicts with opaque" end # If scheme is ftp, path may be relative. # See RFC 1738 section 3.2.2, and RFC 2396. if @scheme && @scheme != "ftp" if v && v != '' && parser.regexp[:ABS_PATH] !~ v raise InvalidComponentError, "bad component(expected absolute path component): #{v}" end else if v && v != '' && parser.regexp[:ABS_PATH] !~ v && parser.regexp[:REL_PATH] !~ v raise InvalidComponentError, "bad component(expected relative path component): #{v}" end end return true end private :check_path # Protected setter for the path component +v+. # # See also Gem::URI::Generic.path=. # def set_path(v) @path = v end protected :set_path # # == Args # # +v+:: # String # # == Description # # Public setter for the path component +v+ # (with validation). # # See also Gem::URI::Generic.check_path. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/pub/files") # uri.path = "/faq/" # uri.to_s #=> "http://my.example.com/faq/" # def path=(v) check_path(v) set_path(v) v end # # == Args # # +v+:: # String # # == Description # # Public setter for the query component +v+. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/?id=25") # uri.query = "id=1" # uri.to_s #=> "http://my.example.com/?id=1" # def query=(v) return @query = nil unless v raise InvalidURIError, "query conflicts with opaque" if @opaque x = v.to_str v = x.dup if x.equal? v v.encode!(Encoding::UTF_8) rescue nil v.delete!("\t\r\n") v.force_encoding(Encoding::ASCII_8BIT) raise InvalidURIError, "invalid percent escape: #{$1}" if /(%\H\H)/n.match(v) v.gsub!(/(?!%\h\h|[!$-&(-;=?-_a-~])./n.freeze){'%%%02X' % $&.ord} v.force_encoding(Encoding::US_ASCII) @query = v end # # Checks the opaque +v+ component for RFC2396 compliance and # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. # def check_opaque(v) return v unless v # raise if both hier and opaque are not nil, because: # absoluteURI = scheme ":" ( hier_part | opaque_part ) # hier_part = ( net_path | abs_path ) [ "?" query ] if @host || @port || @user || @path # userinfo = @user + ':' + @password raise InvalidURIError, "cannot set opaque with host, port, userinfo or path" elsif v && parser.regexp[:OPAQUE] !~ v raise InvalidComponentError, "bad component(expected opaque component): #{v}" end return true end private :check_opaque # Protected setter for the opaque component +v+. # # See also Gem::URI::Generic.opaque=. # def set_opaque(v) @opaque = v end protected :set_opaque # # == Args # # +v+:: # String # # == Description # # Public setter for the opaque component +v+ # (with validation). # # See also Gem::URI::Generic.check_opaque. # def opaque=(v) check_opaque(v) set_opaque(v) v end # # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args # # +v+:: # String # # == Description # # Public setter for the fragment component +v+ # (with validation). # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/?id=25#time=1305212049") # uri.fragment = "time=1305212086" # uri.to_s #=> "http://my.example.com/?id=25#time=1305212086" # def fragment=(v) return @fragment = nil unless v x = v.to_str v = x.dup if x.equal? v v.encode!(Encoding::UTF_8) rescue nil v.delete!("\t\r\n") v.force_encoding(Encoding::ASCII_8BIT) v.gsub!(/(?!%\h\h|[!-~])./n){'%%%02X' % $&.ord} v.force_encoding(Encoding::US_ASCII) @fragment = v end # # Returns true if Gem::URI is hierarchical. # # == Description # # Gem::URI has components listed in order of decreasing significance from left to right, # see RFC3986 https://www.rfc-editor.org/rfc/rfc3986 1.2.3. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com/") # uri.hierarchical? # #=> true # uri = Gem::URI.parse("mailto:joe@example.com") # uri.hierarchical? # #=> false # def hierarchical? if @path true else false end end # # Returns true if Gem::URI has a scheme (e.g. http:// or https://) specified. # def absolute? if @scheme true else false end end alias absolute absolute? # # Returns true if Gem::URI does not have a scheme (e.g. http:// or https://) specified. # def relative? !absolute? end # # Returns an Array of the path split on '/'. # def split_path(path) path.split("/", -1) end private :split_path # # Merges a base path +base+, with relative path +rel+, # returns a modified base path. # def merge_path(base, rel) # RFC2396, Section 5.2, 5) # RFC2396, Section 5.2, 6) base_path = split_path(base) rel_path = split_path(rel) # RFC2396, Section 5.2, 6), a) base_path << '' if base_path.last == '..' while i = base_path.index('..') base_path.slice!(i - 1, 2) end if (first = rel_path.first) and first.empty? base_path.clear rel_path.shift end # RFC2396, Section 5.2, 6), c) # RFC2396, Section 5.2, 6), d) rel_path.push('') if rel_path.last == '.' || rel_path.last == '..' rel_path.delete('.') # RFC2396, Section 5.2, 6), e) tmp = [] rel_path.each do |x| if x == '..' && !(tmp.empty? || tmp.last == '..') tmp.pop else tmp << x end end add_trailer_slash = !tmp.empty? if base_path.empty? base_path = [''] # keep '/' for root directory elsif add_trailer_slash base_path.pop end while x = tmp.shift if x == '..' # RFC2396, Section 4 # a .. or . in an absolute path has no special meaning base_path.pop if base_path.size > 1 else # if x == '..' # valid absolute (but abnormal) path "/../..." # else # valid absolute path # end base_path << x tmp.each {|t| base_path << t} add_trailer_slash = false break end end base_path.push('') if add_trailer_slash return base_path.join('/') end private :merge_path # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Destructive form of #merge. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.merge!("/main.rbx?page=1") # uri.to_s # => "http://my.example.com/main.rbx?page=1" # def merge!(oth) t = merge(oth) if self == t nil else replace!(t) self end end # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Merges two URIs. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.merge("/main.rbx?page=1") # # => "http://my.example.com/main.rbx?page=1" # def merge(oth) rel = parser.__send__(:convert_to_uri, oth) if rel.absolute? #raise BadURIError, "both Gem::URI are absolute" if absolute? # hmm... should return oth for usability? return rel end unless self.absolute? raise BadURIError, "both Gem::URI are relative" end base = self.dup authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query base.fragment=(rel.fragment) if rel.fragment return base end base.query = nil base.fragment=(nil) # RFC2396, Section 5.2, 4) if authority base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) end # RFC2396, Section 5.2, 7) base.query = rel.query if rel.query base.fragment=(rel.fragment) if rel.fragment return base end # merge alias + merge # :stopdoc: def route_from_path(src, dst) case dst when src # RFC2396, Section 4.2 return '' when %r{(?:\A|/)\.\.?(?:/|\z)} # dst has abnormal absolute path, # like "/./", "/../", "/x/../", ... return dst.dup end src_path = src.scan(%r{[^/]*/}) dst_path = dst.scan(%r{[^/]*/?}) # discard same parts while !dst_path.empty? && dst_path.first == src_path.first src_path.shift dst_path.shift end tmp = dst_path.join # calculate if src_path.empty? if tmp.empty? return './' elsif dst_path.first.include?(':') # (see RFC2396 Section 5) return './' + tmp else return tmp end end return '../' * src_path.size + tmp end private :route_from_path # :startdoc: # :stopdoc: def route_from0(oth) oth = parser.__send__(:convert_to_uri, oth) if self.relative? raise BadURIError, "relative Gem::URI: #{self}" end if oth.relative? raise BadURIError, "relative Gem::URI: #{oth}" end if self.scheme != oth.scheme return self, self.dup end rel = Gem::URI::Generic.new(nil, # it is relative Gem::URI self.userinfo, self.host, self.port, nil, self.path, self.opaque, self.query, self.fragment, parser) if rel.userinfo != oth.userinfo || rel.host.to_s.downcase != oth.host.to_s.downcase || rel.port != oth.port if self.userinfo.nil? && self.host.nil? return self, self.dup end rel.set_port(nil) if rel.port == oth.default_port return rel, rel end rel.set_userinfo(nil) rel.set_host(nil) rel.set_port(nil) if rel.path && rel.path == oth.path rel.set_path('') rel.query = nil if rel.query == oth.query return rel, rel elsif rel.opaque && rel.opaque == oth.opaque rel.set_opaque('') rel.query = nil if rel.query == oth.query return rel, rel end # you can modify `rel', but cannot `oth'. return oth, rel end private :route_from0 # :startdoc: # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Calculates relative path from oth to self. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse('http://my.example.com/main.rbx?page=1') # uri.route_from('http://my.example.com') # #=> # # def route_from(oth) # you can modify `rel', but cannot `oth'. begin oth, rel = route_from0(oth) rescue raise $!.class, $!.message end if oth == rel return rel end rel.set_path(route_from_path(oth.path, self.path)) if rel.path == './' && self.query # "./?foo" -> "?foo" rel.set_path('') end return rel end alias - route_from # # == Args # # +oth+:: # Gem::URI or String # # == Description # # Calculates relative path to oth from self. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse('http://my.example.com') # uri.route_to('http://my.example.com/main.rbx?page=1') # #=> # # def route_to(oth) parser.__send__(:convert_to_uri, oth).route_from(self) end # # Returns normalized Gem::URI. # # require 'rubygems/vendor/uri/lib/uri' # # Gem::URI("HTTP://my.EXAMPLE.com").normalize # #=> # # # Normalization here means: # # * scheme and host are converted to lowercase, # * an empty path component is set to "/". # def normalize uri = dup uri.normalize! uri end # # Destructive version of #normalize. # def normalize! if path&.empty? set_path('/') end if scheme && scheme != scheme.downcase set_scheme(self.scheme.downcase) end if host && host != host.downcase set_host(self.host.downcase) end end # # Constructs String from Gem::URI. # def to_s str = ''.dup if @scheme str << @scheme str << ':' end if @opaque str << @opaque else if @host || %w[file postgres].include?(@scheme) str << '//' end if self.userinfo str << self.userinfo str << '@' end if @host str << @host end if @port && @port != self.default_port str << ':' str << @port.to_s end if (@host || @port) && !@path.empty? && !@path.start_with?('/') str << '/' end str << @path if @query str << '?' str << @query end end if @fragment str << '#' str << @fragment end str end alias to_str to_s # # Compares two URIs. # def ==(oth) if self.class == oth.class self.normalize.component_ary == oth.normalize.component_ary else false end end # Returns the hash value. def hash self.component_ary.hash end # Compares with _oth_ for Hash. def eql?(oth) self.class == oth.class && parser == oth.parser && self.component_ary.eql?(oth.component_ary) end # Returns an Array of the components defined from the COMPONENT Array. def component_ary component.collect do |x| self.__send__(x) end end protected :component_ary # == Args # # +components+:: # Multiple Symbol arguments defined in Gem::URI::HTTP. # # == Description # # Selects specified components from Gem::URI. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse('http://myuser:mypass@my.example.com/test.rbx') # uri.select(:userinfo, :host, :path) # # => ["myuser:mypass", "my.example.com", "/test.rbx"] # def select(*components) components.collect do |c| if component.include?(c) self.__send__(c) else raise ArgumentError, "expected of components of #{self.class} (#{self.class.component.join(', ')})" end end end def inspect # :nodoc: "#<#{self.class} #{self}>" end # # == Args # # +v+:: # Gem::URI or String # # == Description # # Attempts to parse other Gem::URI +oth+, # returns [parsed_oth, self]. # # == Usage # # require 'rubygems/vendor/uri/lib/uri' # # uri = Gem::URI.parse("http://my.example.com") # uri.coerce("http://foo.com") # #=> [#, #] # def coerce(oth) case oth when String oth = parser.parse(oth) else super end return oth, self end # Returns a proxy Gem::URI. # The proxy Gem::URI is obtained from environment variables such as http_proxy, # ftp_proxy, no_proxy, etc. # If there is no proper proxy, nil is returned. # # If the optional parameter +env+ is specified, it is used instead of ENV. # # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.) # are examined, too. # # But http_proxy and HTTP_PROXY is treated specially under CGI environment. # It's because HTTP_PROXY may be set by Proxy: header. # So HTTP_PROXY is not used. # http_proxy is not used too if the variable is case insensitive. # CGI_HTTP_PROXY can be used instead. def find_proxy(env=ENV) raise BadURIError, "relative Gem::URI: #{self}" if self.relative? name = self.scheme.downcase + '_proxy' proxy_uri = nil if name == 'http_proxy' && env.include?('REQUEST_METHOD') # CGI? # HTTP_PROXY conflicts with *_proxy for proxy settings and # HTTP_* for header information in CGI. # So it should be careful to use it. pairs = env.reject {|k, v| /\Ahttp_proxy\z/i !~ k } case pairs.length when 0 # no proxy setting anyway. proxy_uri = nil when 1 k, _ = pairs.shift if k == 'http_proxy' && env[k.upcase] == nil # http_proxy is safe to use because ENV is case sensitive. proxy_uri = env[name] else proxy_uri = nil end else # http_proxy is safe to use because ENV is case sensitive. proxy_uri = env.to_hash[name] end if !proxy_uri # Use CGI_HTTP_PROXY. cf. libwww-perl. proxy_uri = env["CGI_#{name.upcase}"] end elsif name == 'http_proxy' if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost'] p_port = ENV_JAVA['http.proxyPort'] if p_user = ENV_JAVA['http.proxyUser'] p_pass = ENV_JAVA['http.proxyPass'] proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}" else proxy_uri = "http://#{p_addr}:#{p_port}" end else unless proxy_uri = env[name] if proxy_uri = env[name.upcase] warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end else proxy_uri = env[name] || env[name.upcase] end if proxy_uri.nil? || proxy_uri.empty? return nil end if self.hostname begin addr = IPSocket.getaddress(self.hostname) return nil if /\A127\.|\A::1\z/ =~ addr rescue SocketError end end name = 'no_proxy' if no_proxy = env[name] || env[name.upcase] return nil unless Gem::URI::Generic.use_proxy?(self.hostname, addr, self.port, no_proxy) end Gem::URI.parse(proxy_uri) end def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc: hostname = hostname.downcase dothostname = ".#{hostname}" no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) {|p_host, p_port| if !p_port || port == p_port.to_i if p_host.start_with?('.') return false if hostname.end_with?(p_host.downcase) else return false if dothostname.end_with?(".#{p_host.downcase}") end if addr begin return false if IPAddr.new(p_host).include?(addr) rescue IPAddr::InvalidAddressError next end end end } true end end end PK!Nccvendor/resolv/lib/resolv.rbnu[# 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 PK!.V : :vendor/tsort/lib/tsort.rbnu[# frozen_string_literal: true #-- # tsort.rb - provides a module for topological sorting and strongly connected components. #++ # # # Gem::TSort implements topological sorting using Tarjan's algorithm for # strongly connected components. # # Gem::TSort is designed to be able to be used with any object which can be # interpreted as a directed graph. # # Gem::TSort requires two methods to interpret an object as a graph, # tsort_each_node and tsort_each_child. # # * tsort_each_node is used to iterate for all nodes over a graph. # * tsort_each_child is used to iterate for child nodes of a given node. # # The equality of nodes are defined by eql? and hash since # Gem::TSort uses Hash internally. # # == A Simple Example # # The following example demonstrates how to mix the Gem::TSort module into an # existing class (in this case, Hash). Here, we're treating each key in # the hash as a node in the graph, and so we simply alias the required # #tsort_each_node method to Hash's #each_key method. For each key in the # hash, the associated value is an array of the node's child nodes. This # choice in turn leads to our implementation of the required #tsort_each_child # method, which fetches the array of child nodes and then iterates over that # array using the user-supplied block. # # require 'rubygems/vendor/tsort/lib/tsort' # # class Hash # include Gem::TSort # alias tsort_each_node each_key # def tsort_each_child(node, &block) # fetch(node).each(&block) # end # end # # {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort # #=> [3, 2, 1, 4] # # {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components # #=> [[4], [2, 3], [1]] # # == A More Realistic Example # # A very simple `make' like tool can be implemented as follows: # # require 'rubygems/vendor/tsort/lib/tsort' # # class Make # def initialize # @dep = {} # @dep.default = [] # end # # def rule(outputs, inputs=[], &block) # triple = [outputs, inputs, block] # outputs.each {|f| @dep[f] = [triple]} # @dep[triple] = inputs # end # # def build(target) # each_strongly_connected_component_from(target) {|ns| # if ns.length != 1 # fs = ns.delete_if {|n| Array === n} # raise Gem::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") # end # n = ns.first # if Array === n # outputs, inputs, block = n # inputs_time = inputs.map {|f| File.mtime f}.max # begin # outputs_time = outputs.map {|f| File.mtime f}.min # rescue Errno::ENOENT # outputs_time = nil # end # if outputs_time == nil || # inputs_time != nil && outputs_time <= inputs_time # sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i # block.call # end # end # } # end # # def tsort_each_child(node, &block) # @dep[node].each(&block) # end # include Gem::TSort # end # # def command(arg) # print arg, "\n" # system arg # end # # m = Make.new # m.rule(%w[t1]) { command 'date > t1' } # m.rule(%w[t2]) { command 'date > t2' } # m.rule(%w[t3]) { command 'date > t3' } # m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } # m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } # m.build('t5') # # == Bugs # # * 'tsort.rb' is wrong name because this library uses # Tarjan's algorithm for strongly connected components. # Although 'strongly_connected_components.rb' is correct but too long. # # == References # # R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", # SIAM Journal on Computing, Vol. 1, No. 2, pp. 146-160, June 1972. # module Gem::TSort VERSION = "0.2.0" class Cyclic < StandardError end # Returns a topologically sorted array of nodes. # The array is sorted from children to parents, i.e. # the first element has no child and the last node has no parent. # # If there is a cycle, Gem::TSort::Cyclic is raised. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # p graph.tsort #=> [4, 2, 3, 1] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # p graph.tsort # raises Gem::TSort::Cyclic # def tsort each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.tsort(each_node, each_child) end # Returns a topologically sorted array of nodes. # The array is sorted from children to parents, i.e. # the first element has no child and the last node has no parent. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # If there is a cycle, Gem::TSort::Cyclic is raised. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] # # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.tsort(each_node, each_child) # raises Gem::TSort::Cyclic # def self.tsort(each_node, each_child) tsort_each(each_node, each_child).to_a end # The iterator version of the #tsort method. # obj.tsort_each is similar to obj.tsort.each, but # modification of _obj_ during the iteration may lead to unexpected results. # # #tsort_each returns +nil+. # If there is a cycle, Gem::TSort::Cyclic is raised. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # graph.tsort_each {|n| p n } # #=> 4 # # 2 # # 3 # # 1 # def tsort_each(&block) # :yields: node each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.tsort_each(each_node, each_child, &block) end # The iterator version of the Gem::TSort.tsort method. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # Gem::TSort.tsort_each(each_node, each_child) {|n| p n } # #=> 4 # # 2 # # 3 # # 1 # def self.tsort_each(each_node, each_child) # :yields: node return to_enum(__method__, each_node, each_child) unless block_given? each_strongly_connected_component(each_node, each_child) {|component| if component.size == 1 yield component.first else raise Cyclic.new("topological sort failed: #{component.inspect}") end } end # Returns strongly connected components as an array of arrays of nodes. # The array is sorted from children to parents. # Each elements of the array represents a strongly connected component. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] # def strongly_connected_components each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.strongly_connected_components(each_node, each_child) end # Returns strongly connected components as an array of arrays of nodes. # The array is sorted from children to parents. # Each elements of the array represents a strongly connected component. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.strongly_connected_components(each_node, each_child) # #=> [[4], [2], [3], [1]] # # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # p Gem::TSort.strongly_connected_components(each_node, each_child) # #=> [[4], [2, 3], [1]] # def self.strongly_connected_components(each_node, each_child) each_strongly_connected_component(each_node, each_child).to_a end # The iterator version of the #strongly_connected_components method. # obj.each_strongly_connected_component is similar to # obj.strongly_connected_components.each, but # modification of _obj_ during the iteration may lead to unexpected results. # # #each_strongly_connected_component returns +nil+. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # graph.each_strongly_connected_component {|scc| p scc } # #=> [4] # # [2] # # [3] # # [1] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # graph.each_strongly_connected_component {|scc| p scc } # #=> [4] # # [2, 3] # # [1] # def each_strongly_connected_component(&block) # :yields: nodes each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) Gem::TSort.each_strongly_connected_component(each_node, each_child, &block) end # The iterator version of the Gem::TSort.strongly_connected_components method. # # The graph is represented by _each_node_ and _each_child_. # _each_node_ should have +call+ method which yields for each node in the graph. # _each_child_ should have +call+ method which takes a node argument and yields for each child node. # # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } # #=> [4] # # [2] # # [3] # # [1] # # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_node = lambda {|&b| g.each_key(&b) } # each_child = lambda {|n, &b| g[n].each(&b) } # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } # #=> [4] # # [2, 3] # # [1] # def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes return to_enum(__method__, each_node, each_child) unless block_given? id_map = {} stack = [] each_node.call {|node| unless id_map.include? node each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| yield c } end } nil end # Iterates over strongly connected component in the subgraph reachable from # _node_. # # Return value is unspecified. # # #each_strongly_connected_component_from doesn't call #tsort_each_node. # # class G # include Gem::TSort # def initialize(g) # @g = g # end # def tsort_each_child(n, &b) @g[n].each(&b) end # def tsort_each_node(&b) @g.each_key(&b) end # end # # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) # graph.each_strongly_connected_component_from(2) {|scc| p scc } # #=> [4] # # [2] # # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) # graph.each_strongly_connected_component_from(2) {|scc| p scc } # #=> [4] # # [2, 3] # def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes Gem::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) end # Iterates over strongly connected components in a graph. # The graph is represented by _node_ and _each_child_. # # _node_ is the first node. # _each_child_ should have +call+ method which takes a node argument # and yields for each child node. # # Return value is unspecified. # # #Gem::TSort.each_strongly_connected_component_from is a class method and # it doesn't need a class to represent a graph which includes Gem::TSort. # # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} # each_child = lambda {|n, &b| graph[n].each(&b) } # Gem::TSort.each_strongly_connected_component_from(1, each_child) {|scc| # p scc # } # #=> [4] # # [2, 3] # # [1] # def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes return to_enum(__method__, node, each_child, id_map, stack) unless block_given? minimum_id = node_id = id_map[node] = id_map.size stack_length = stack.length stack << node each_child.call(node) {|child| if id_map.include? child child_id = id_map[child] minimum_id = child_id if child_id && child_id < minimum_id else sub_minimum_id = each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| yield c } minimum_id = sub_minimum_id if sub_minimum_id < minimum_id end } if node_id == minimum_id component = stack.slice!(stack_length .. -1) component.each {|n| id_map[n] = nil} yield component end minimum_id end # Should be implemented by a extended class. # # #tsort_each_node is used to iterate for all nodes over a graph. # def tsort_each_node # :yields: node raise NotImplementedError.new end # Should be implemented by a extended class. # # #tsort_each_child is used to iterate for child nodes of _node_. # def tsort_each_child(node) # :yields: child raise NotImplementedError.new end end PK!22+vendor/securerandom/lib/random/formatter.rbnu[# -*- coding: us-ascii -*- # frozen_string_literal: true # == \Random number formatter. # # Formats generated random numbers in many manners. When 'random/formatter' # is required, several methods are added to empty core module Gem::Random::Formatter, # making them available as Random's instance and module methods. # # Standard library Gem::SecureRandom is also extended with the module, and the methods # described below are available as a module methods in it. # # === Examples # # Generate random hexadecimal strings: # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # prng = Random.new # prng.hex(10) #=> "52750b30ffbc7de3b362" # prng.hex(10) #=> "92b15d6c8dc4beb5f559" # prng.hex(13) #=> "39b290146bea6ce975c37cfc23" # # or just # Random.hex #=> "1aed0c631e41be7f77365415541052ee" # # Generate random base64 strings: # # prng.base64(10) #=> "EcmTPZwWRAozdA==" # prng.base64(10) #=> "KO1nIU+p9DKxGg==" # prng.base64(12) #=> "7kJSM/MzBJI+75j8" # Random.base64(4) #=> "bsQ3fQ==" # # Generate random binary strings: # # prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301" # prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" # Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43" # # Generate alphanumeric strings: # # prng.alphanumeric(10) #=> "S8baxMJnPl" # prng.alphanumeric(10) #=> "aOxAg8BAJe" # Random.alphanumeric #=> "TmP9OsJHJLtaZYhP" # # Generate UUIDs: # # prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" # prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" # Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd" # # All methods are available in the standard library Gem::SecureRandom, too: # # Gem::SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf" module Gem::Random::Formatter # Generate a random binary string. # # The argument _n_ specifies the length of the result string. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in future. # # The result may contain any byte: "\x00" - "\xff". # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" # # or # prng = Random.new # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" def random_bytes(n=nil) n = n ? n.to_int : 16 gen_random(n) end # Generate a random hexadecimal string. # # The argument _n_ specifies the length, in bytes, of the random number to be generated. # The length of the resulting hexadecimal string is twice of _n_. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The result may contain 0-9 and a-f. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" # # or # prng = Random.new # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61" def hex(n=nil) random_bytes(n).unpack1("H*") end # Generate a random base64 string. # # The argument _n_ specifies the length, in bytes, of the random number # to be generated. The length of the result string is about 4/3 of _n_. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The result may contain A-Z, a-z, 0-9, "+", "/" and "=". # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" # # or # prng = Random.new # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" # # See RFC 3548 for the definition of base64. def base64(n=nil) [random_bytes(n)].pack("m0") end # Generate a random URL-safe base64 string. # # The argument _n_ specifies the length, in bytes, of the random number # to be generated. The length of the result string is about 4/3 of _n_. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The boolean argument _padding_ specifies the padding. # If it is false or nil, padding is not generated. # Otherwise padding is generated. # By default, padding is not generated because "=" may be used as a URL delimiter. # # The result may contain A-Z, a-z, 0-9, "-" and "_". # "=" is also used if _padding_ is true. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" # # or # prng = Random.new # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" # # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" # prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg==" # # See RFC 3548 for the definition of URL-safe base64. def urlsafe_base64(n=nil, padding=false) s = [random_bytes(n)].pack("m0") s.tr!("+/", "-_") s.delete!("=") unless padding s end # Generate a random v4 UUID (Universally Unique IDentifier). # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" # # or # prng = Random.new # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" # # The version 4 UUID is purely random (except the version). # It doesn't contain meaningful information such as MAC addresses, timestamps, etc. # # The result contains 122 random bits (15.25 random bytes). # # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. # def uuid ary = random_bytes(16).unpack("NnnnnN") ary[2] = (ary[2] & 0x0fff) | 0x4000 ary[3] = (ary[3] & 0x3fff) | 0x8000 "%08x-%04x-%04x-%04x-%04x%08x" % ary end alias uuid_v4 uuid # Generate a random v7 UUID (Universally Unique IDentifier). # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e" # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5" # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23" # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31" # # |<--sorted-->| |<----- random ---->| # # # or # prng = Random.new # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98" # # The version 7 UUID starts with the least significant 48 bits of a 64 bit # Unix timestamp (milliseconds since the epoch) and fills the remaining bits # with random data, excluding the version and variant bits. # # This allows version 7 UUIDs to be sorted by creation time. Time ordered # UUIDs can be used for better database index locality of newly inserted # records, which may have a significant performance benefit compared to random # data inserts. # # The result contains 74 random bits (9.25 random bytes). # # Note that this method cannot be made reproducable because its output # includes not only random bits but also timestamp. # # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/] # for details of UUIDv7. # # ==== Monotonicity # # UUIDv7 has millisecond precision by default, so multiple UUIDs created # within the same millisecond are not issued in monotonically increasing # order. To create UUIDs that are time-ordered with sub-millisecond # precision, up to 12 bits of additional timestamp may added with # +extra_timestamp_bits+. The extra timestamp precision comes at the expense # of random bits. Setting extra_timestamp_bits: 12 provides ~244ns # of precision, but only 62 random bits (7.75 random bytes). # # prng = Random.new # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) } # # => # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a", # "0188d4c7-13da-753b-83a5-7fb9b2afaeea", # "0188d4c7-13da-754a-88ea-ac0baeedd8db", # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"] # # |<--- sorted --->| |<-- random --->| # # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) } # # => # ["0188d4c7-3333-7a95-850a-de6edb858f7e", # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order # "0188d4c7-3333-7af9-87c3-8f612edac82e"] # # |<--- sorted -->||<---- random --->| # # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based # on UTC, which excludes leap seconds and can rollback the clock. To avoid # this, the system clock can synchronize with an NTP server configured to use # a "leap smear" approach. NTP or PTP will also be needed to synchronize # across distributed nodes. # # Counters and other mechanisms for stronger guarantees of monotonicity are # not implemented. Applications with stricter requirements should follow # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters] # of the specification. # def uuid_v7(extra_timestamp_bits: 0) case (extra_timestamp_bits = Integer(extra_timestamp_bits)) when 0 # min timestamp precision ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) rand = random_bytes(10) rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant "%08x-%04x-%s" % [ (ms & 0x0000_ffff_ffff_0000) >> 16, (ms & 0x0000_0000_0000_ffff), rand.unpack("H4H4H12").join("-") ] when 12 # max timestamp precision ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) .divmod(1_000_000) extra_bits = ns * 4096 / 1_000_000 rand = random_bytes(8) rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant "%08x-%04x-7%03x-%s" % [ (ms & 0x0000_ffff_ffff_0000) >> 16, (ms & 0x0000_0000_0000_ffff), extra_bits, rand.unpack("H4H12").join("-") ] when (0..12) # the generic version is slower than the special cases above rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN") rand_mask_bits = 12 - extra_timestamp_bits ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) .divmod(1_000_000) "%08x-%04x-%04x-%04x-%04x%08x" % [ (ms & 0x0000_ffff_ffff_0000) >> 16, (ms & 0x0000_0000_0000_ffff), 0x7000 | ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) | rand_a & ((1 << rand_mask_bits) - 1), 0x8000 | (rand_b1 & 0x3fff), rand_b2, rand_b3 ] else raise ArgumentError, "extra_timestamp_bits must be in 0..12" end end # Internal interface to Random; Generate random data _n_ bytes. private def gen_random(n) self.bytes(n) end # Generate a string that randomly draws from a # source array of characters. # # The argument _source_ specifies the array of characters from which # to generate the string. # The argument _n_ specifies the length, in characters, of the string to be # generated. # # The result may contain whatever characters are in the source array. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron" # prng.choose([*'0'..'9'], 5) #=> "27309" private def choose(source, n) size = source.size m = 1 limit = size while limit * size <= 0x100000000 limit *= size m += 1 end result = ''.dup while m <= n rs = random_number(limit) is = rs.digits(size) (m-is.length).times { is << 0 } result << source.values_at(*is).join('') n -= m end if 0 < n rs = random_number(limit) is = rs.digits(size) if is.length < n (n-is.length).times { is << 0 } else is.pop while n < is.length end result.concat source.values_at(*is).join('') end result end # The default character list for #alphanumeric. ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] # Generate a random alphanumeric string. # # The argument _n_ specifies the length, in characters, of the alphanumeric # string to be generated. # The argument _chars_ specifies the character list which the result is # consist of. # # If _n_ is not specified or is nil, 16 is assumed. # It may be larger in the future. # # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified. # # require 'rubygems/vendor/securerandom/lib/random/formatter' # # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR" # # or # prng = Random.new # prng.alphanumeric(10) #=> "i6K93NdqiH" # # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952" # # or # prng = Random.new # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''." def alphanumeric(n = nil, chars: ALPHANUMERIC) n = 16 if n.nil? choose(chars, n) end end PK!7OV V 'vendor/securerandom/lib/securerandom.rbnu[# -*- coding: us-ascii -*- # frozen_string_literal: true require 'random/formatter' # == Secure random number generator interface. # # This library is an interface to secure random number generators which are # suitable for generating session keys in HTTP cookies, etc. # # You can use this library in your application by requiring it: # # require 'rubygems/vendor/securerandom/lib/securerandom' # # It supports the following secure random number generators: # # * openssl # * /dev/urandom # * Win32 # # Gem::SecureRandom is extended by the Random::Formatter module which # defines the following methods: # # * alphanumeric # * base64 # * choose # * gen_random # * hex # * rand # * random_bytes # * random_number # * urlsafe_base64 # * uuid # # These methods are usable as class methods of Gem::SecureRandom such as # +Gem::SecureRandom.hex+. # # If a secure random number generator is not available, # +NotImplementedError+ is raised. module Gem::SecureRandom # The version VERSION = "0.4.1" class << self # Returns a random binary string containing +size+ bytes. # # See Random.bytes def bytes(n) return gen_random(n) end # Compatibility methods for Ruby 3.2, we can remove this after dropping to support Ruby 3.2 def alphanumeric(n = nil, chars: ALPHANUMERIC) n = 16 if n.nil? choose(chars, n) end if RUBY_VERSION < '3.3' private # :stopdoc: # Implementation using OpenSSL def gen_random_openssl(n) return OpenSSL::Random.random_bytes(n) end # Implementation using system random device def gen_random_urandom(n) ret = Random.urandom(n) unless ret raise NotImplementedError, "No random device" end unless ret.length == n raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes" end ret end begin # Check if Random.urandom is available Random.urandom(1) alias gen_random gen_random_urandom rescue RuntimeError begin require 'openssl' rescue NoMethodError raise NotImplementedError, "No random device" else alias gen_random gen_random_openssl end end # :startdoc: # Generate random data bytes for Random::Formatter public :gen_random end end Gem::SecureRandom.extend(Random::Formatter) PK!2<<security_option.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "../rubygems" # forward-declare module Gem::Security # :nodoc: class Policy # :nodoc: end end ## # Mixin methods for security option for Gem::Commands module Gem::SecurityOption def add_security_option Gem::OptionParser.accept Gem::Security::Policy do |value| require_relative "security" raise Gem::OptionParser::InvalidArgument, "OpenSSL not installed" unless defined?(Gem::Security::HighSecurity) policy = Gem::Security::Policies[value] unless policy valid = Gem::Security::Policies.keys.sort raise Gem::OptionParser::InvalidArgument, "#{value} (#{valid.join ", "} are valid)" end policy end add_option(:"Install/Update", "-P", "--trust-policy POLICY", Gem::Security::Policy, "Specify gem trust policy") do |value, options| options[:security_policy] = value end end end PK!ro*!! dependency.rbnu[# frozen_string_literal: true ## # The Dependency class holds a Gem name and a Gem::Requirement. class Gem::Dependency ## # Valid dependency types. #-- # When this list is updated, be sure to change # Gem::Specification::CURRENT_SPECIFICATION_VERSION as well. # # REFACTOR: This type of constant, TYPES, indicates we might want # two classes, used via inheritance or duck typing. TYPES = [ :development, :runtime, ].freeze ## # Dependency name or regular expression. attr_accessor :name ## # Allows you to force this dependency to be a prerelease. attr_writer :prerelease ## # Constructs a dependency with +name+ and +requirements+. The last # argument can optionally be the dependency type, which defaults to # :runtime. def initialize(name, *requirements) case name when String then # ok when Regexp then msg = ["NOTE: Dependency.new w/ a regexp is deprecated.", "Dependency.new called from #{Gem.location_of_caller.join(":")}"] warn msg.join("\n") unless Gem::Deprecate.skip else raise ArgumentError, "dependency name must be a String, was #{name.inspect}" end type = Symbol === requirements.last ? requirements.pop : :runtime requirements = requirements.first if requirements.length == 1 # unpack unless TYPES.include? type raise ArgumentError, "Valid types are #{TYPES.inspect}, " \ "not #{type.inspect}" end @name = name @requirement = Gem::Requirement.create requirements @type = type @prerelease = false # This is for Marshal backwards compatibility. See the comments in # +requirement+ for the dirty details. @version_requirements = @requirement end ## # A dependency's hash is the XOR of the hashes of +name+, +type+, # and +requirement+. def hash # :nodoc: name.hash ^ type.hash ^ requirement.hash end def inspect # :nodoc: if prerelease? format("<%s type=%p name=%p requirements=%p prerelease=ok>", self.class, type, name, requirement.to_s) else format("<%s type=%p name=%p requirements=%p>", self.class, type, name, requirement.to_s) end end ## # Does this dependency require a prerelease? def prerelease? @prerelease || requirement.prerelease? end ## # Is this dependency simply asking for the latest version # of a gem? def latest_version? @requirement.none? end def pretty_print(q) # :nodoc: q.group 1, "Gem::Dependency.new(", ")" do q.pp name q.text "," q.breakable q.pp requirement q.text "," q.breakable q.pp type end end ## # What does this dependency require? def requirement return @requirement if defined?(@requirement) && @requirement # @version_requirements and @version_requirement are legacy ivar # names, and supported here because older gems need to keep # working and Dependency doesn't implement marshal_dump and # marshal_load. In a happier world, this would be an # attr_accessor. The horrifying instance_variable_get you see # below is also the legacy of some old restructurings. # # Note also that because of backwards compatibility (loading new # gems in an old RubyGems installation), we can't add explicit # marshaling to this class until we want to make a big # break. Maybe 2.0. # # Children, define explicit marshal and unmarshal behavior for # public classes. Marshal formats are part of your public API. # REFACTOR: See above if defined?(@version_requirement) && @version_requirement version = @version_requirement.instance_variable_get :@version @version_requirement = nil @version_requirements = Gem::Requirement.new version end @requirement = @version_requirements if defined?(@version_requirements) end def requirements_list requirement.as_list end def to_s # :nodoc: if type != :runtime "#{name} (#{requirement}, #{type})" else "#{name} (#{requirement})" end end ## # Dependency type. def type @type ||= :runtime end def runtime? @type == :runtime || !@type end def ==(other) # :nodoc: Gem::Dependency === other && name == other.name && type == other.type && requirement == other.requirement end ## # Dependencies are ordered by name. def <=>(other) name <=> other.name end ## # Uses this dependency as a pattern to compare to +other+. This # dependency will match if the name matches the other's name, and # other has only an equal version requirement that satisfies this # dependency. def =~(other) unless Gem::Dependency === other return unless other.respond_to?(:name) && other.respond_to?(:version) other = Gem::Dependency.new other.name, other.version end return false unless name === other.name reqs = other.requirement.requirements return false unless reqs.length == 1 return false unless reqs.first.first == "=" version = reqs.first.last requirement.satisfied_by? version end alias_method :===, :=~ ## # :call-seq: # dep.match? name => true or false # dep.match? name, version => true or false # dep.match? spec => true or false # # Does this dependency match the specification described by +name+ and # +version+ or match +spec+? # # NOTE: Unlike #matches_spec? this method does not return true when the # version is a prerelease version unless this is a prerelease dependency. def match?(obj, version = nil, allow_prerelease = false) if !version name = obj.name version = obj.version else name = obj end return false unless self.name === name version = Gem::Version.new version return true if requirement.none? && !version.prerelease? return false if version.prerelease? && !allow_prerelease && !prerelease? requirement.satisfied_by? version end ## # Does this dependency match +spec+? # # NOTE: This is not a convenience method. Unlike #match? this method # returns true when +spec+ is a prerelease version even if this dependency # is not a prerelease dependency. def matches_spec?(spec) return false unless name === spec.name return true if requirement.none? requirement.satisfied_by?(spec.version) end ## # Merges the requirements of +other+ into this dependency def merge(other) unless name == other.name raise ArgumentError, "#{self} and #{other} have different names" end default = Gem::Requirement.default self_req = requirement other_req = other.requirement return self.class.new name, self_req if other_req == default return self.class.new name, other_req if self_req == default self.class.new name, self_req.as_list.concat(other_req.as_list) end def matching_specs(platform_only = false) matches = Gem::Specification.find_all_by_name(name, requirement) if platform_only matches.reject! do |spec| spec.nil? || !Gem::Platform.match_spec?(spec) end end matches.reject(&:ignored?) end ## # True if the dependency will not always match the latest version. def specific? @requirement.specific? end def to_specs matches = matching_specs true # TODO: check Gem.activated_spec[self.name] in case matches falls outside if matches.empty? specs = Gem::Specification.stubs_for name if specs.empty? raise Gem::MissingSpecError.new name, requirement else raise Gem::MissingSpecVersionError.new name, requirement, specs end end # TODO: any other resolver validations should go here matches end def to_spec matches = to_specs.compact active = matches.find(&:activated?) return active if active unless prerelease? # Consider prereleases only as a fallback pre, matches = matches.partition {|spec| spec.version.prerelease? } matches = pre if matches.empty? end matches.first end def identity if prerelease? if specific? :complete else :abs_latest end elsif latest_version? :latest else :released end end def encode_with(coder) # :nodoc: coder.add "name", @name coder.add "requirement", @requirement coder.add "type", @type coder.add "prerelease", @prerelease coder.add "version_requirements", @version_requirements end end PK!х8zzspecification_record.rbnu[# frozen_string_literal: true module Gem class SpecificationRecord def self.dirs_from(paths) paths.map do |path| File.join(path, "specifications") end end def self.from_path(path) new(dirs_from([path])) end def initialize(dirs) @all = nil @stubs = nil @stubs_by_name = {} @spec_with_requirable_file = {} @active_stub_with_requirable_file = {} @dirs = dirs end # Sentinel object to represent "not found" stubs NOT_FOUND = Struct.new(:to_spec, :this).new private_constant :NOT_FOUND ## # Returns the list of all specifications in the record def all @all ||= stubs.map(&:to_spec) end ## # Returns a Gem::StubSpecification for every specification in the record def stubs @stubs ||= begin pattern = "*.gemspec" stubs = stubs_for_pattern(pattern, false) @stubs_by_name = stubs.select {|s| Gem::Platform.match_spec? s }.group_by(&:name) stubs end end ## # Returns a Gem::StubSpecification for every specification in the record # named +name+ only returns stubs that match Gem.platforms def stubs_for(name) if @stubs @stubs_by_name[name] || [] else @stubs_by_name[name] ||= stubs_for_pattern("#{name}-*.gemspec").select do |s| s.name == name end end end ## # Finds stub specifications matching a pattern in the record, optionally # filtering out specs not matching the current platform def stubs_for_pattern(pattern, match_platform = true) installed_stubs = installed_stubs(pattern) installed_stubs.select! {|s| Gem::Platform.match_spec? s } if match_platform stubs = installed_stubs + Gem::Specification.default_stubs(pattern) Gem::Specification._resort!(stubs) stubs end ## # Adds +spec+ to the record, keeping the collection properly sorted. def add_spec(spec) return if all.include? spec all << spec stubs << spec (@stubs_by_name[spec.name] ||= []) << spec Gem::Specification._resort!(@stubs_by_name[spec.name]) Gem::Specification._resort!(stubs) end ## # Removes +spec+ from the record. def remove_spec(spec) all.delete spec.to_spec stubs.delete spec (@stubs_by_name[spec.name] || []).delete spec end ## # Sets the specs known by the record to +specs+. def all=(specs) @stubs_by_name = specs.group_by(&:name) @all = @stubs = specs end ## # Return full names of all specs in the record in sorted order. def all_names all.map(&:full_name) end include Enumerable ## # Enumerate every known spec. def each return enum_for(:each) unless block_given? all.each do |x| yield x end end ## # Returns every spec in the record that matches +name+ and optional +requirements+. def find_all_by_name(name, *requirements) req = Gem::Requirement.create(*requirements) env_req = Gem.env_requirement(name) matches = stubs_for(name).find_all do |spec| req.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version) end.map(&:to_spec) if name == "bundler" && !req.specific? require_relative "bundler_version_finder" Gem::BundlerVersionFinder.prioritize!(matches) end matches end ## # Return the best specification in the record that contains the file matching +path+. def find_by_path(path) path = path.dup.freeze spec = @spec_with_requirable_file[path] ||= stubs.find do |s| s.contains_requirable_file? path end || NOT_FOUND spec.to_spec end ## # Return the best specification that contains the file matching +path+ # amongst the specs that are not loaded. This method is different than # +find_inactive_by_path+ as it will filter out loaded specs by their name. def find_unloaded_by_path(path) stub = stubs.find do |s| next if Gem.loaded_specs[s.name] s.contains_requirable_file? path end stub&.to_spec end ## # Return the best specification in the record that contains the file # matching +path+ amongst the specs that are not activated. def find_inactive_by_path(path) stub = stubs.find do |s| next if s.activated? s.contains_requirable_file? path end stub&.to_spec end ## # Return the best specification in the record that contains the file # matching +path+, among those already activated. def find_active_stub_by_path(path) stub = @active_stub_with_requirable_file[path] ||= stubs.find do |s| s.activated? && s.contains_requirable_file?(path) end || NOT_FOUND stub.this end ## # Return the latest specs in the record, optionally including prerelease # specs if +prerelease+ is true. def latest_specs(prerelease) Gem::Specification._latest_specs stubs, prerelease end ## # Return the latest installed spec in the record for gem +name+. def latest_spec_for(name) latest_specs(true).find {|installed_spec| installed_spec.name == name } end private def installed_stubs(pattern) map_stubs(pattern) do |path, base_dir, gems_dir| Gem::StubSpecification.gemspec_stub(path, base_dir, gems_dir) end end def map_stubs(pattern) @dirs.flat_map do |dir| base_dir = File.dirname dir gems_dir = File.join base_dir, "gems" Gem::Specification.gemspec_stubs_in(dir, pattern) {|path| yield path, base_dir, gems_dir } end end end end PK!,7WWvendored_securerandom.rbnu[# frozen_string_literal: true require_relative "vendor/securerandom/lib/securerandom" PK!-A4A4user_interaction.rbnu[# frozen_string_literal: true #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require_relative "text" ## # Module that defines the default UserInteraction. Any class including this # module will have access to the +ui+ method that returns the default UI. module Gem::DefaultUserInteraction include Gem::Text ## # The default UI is a class variable of the singleton class for this # module. @ui = nil ## # Return the default UI. def self.ui @ui ||= Gem::ConsoleUI.new end ## # Set the default UI. If the default UI is never explicitly set, a simple # console based UserInteraction will be used automatically. def self.ui=(new_ui) @ui = new_ui end ## # Use +new_ui+ for the duration of +block+. def self.use_ui(new_ui) old_ui = @ui @ui = new_ui yield ensure @ui = old_ui end ## # See DefaultUserInteraction::ui def ui Gem::DefaultUserInteraction.ui end ## # See DefaultUserInteraction::ui= def ui=(new_ui) Gem::DefaultUserInteraction.ui = new_ui end ## # See DefaultUserInteraction::use_ui def use_ui(new_ui, &block) Gem::DefaultUserInteraction.use_ui(new_ui, &block) end end ## # UserInteraction allows RubyGems to interact with the user through standard # methods that can be replaced with more-specific UI methods for different # displays. # # Since UserInteraction dispatches to a concrete UI class you may need to # reference other classes for specific behavior such as Gem::ConsoleUI or # Gem::SilentUI. # # Example: # # class X # include Gem::UserInteraction # # def get_answer # n = ask("What is the meaning of life?") # end # end module Gem::UserInteraction include Gem::DefaultUserInteraction ## # Displays an alert +statement+. Asks a +question+ if given. def alert(statement, question = nil) ui.alert statement, question end ## # Displays an error +statement+ to the error output location. Asks a # +question+ if given. def alert_error(statement, question = nil) ui.alert_error statement, question end ## # Displays a warning +statement+ to the warning output location. Asks a # +question+ if given. def alert_warning(statement, question = nil) ui.alert_warning statement, question end ## # Asks a +question+ and returns the answer. def ask(question) ui.ask question end ## # Asks for a password with a +prompt+ def ask_for_password(prompt) ui.ask_for_password prompt end ## # Asks a yes or no +question+. Returns true for yes, false for no. def ask_yes_no(question, default = nil) ui.ask_yes_no question, default end ## # Asks the user to answer +question+ with an answer from the given +list+. def choose_from_list(question, list) ui.choose_from_list question, list end ## # Displays the given +statement+ on the standard output (or equivalent). def say(statement = "") ui.say statement end ## # Terminates the RubyGems process with the given +exit_code+ def terminate_interaction(exit_code = 0) ui.terminate_interaction exit_code end ## # Calls +say+ with +msg+ or the results of the block if really_verbose # is true. def verbose(msg = nil) say(clean_text(msg || yield)) if Gem.configuration.really_verbose end end ## # Gem::StreamUI implements a simple stream based user interface. class Gem::StreamUI ## # The input stream attr_reader :ins ## # The output stream attr_reader :outs ## # The error stream attr_reader :errs ## # Creates a new StreamUI wrapping +in_stream+ for user input, +out_stream+ # for standard output, +err_stream+ for error output. If +usetty+ is true # then special operations (like asking for passwords) will use the TTY # commands to disable character echo. def initialize(in_stream, out_stream, err_stream = $stderr, usetty = true) @ins = in_stream @outs = out_stream @errs = err_stream @usetty = usetty end ## # Returns true if TTY methods should be used on this StreamUI. def tty? @usetty && @ins.tty? end ## # Prints a formatted backtrace to the errors stream if backtraces are # enabled. def backtrace(exception) return unless Gem.configuration.backtrace @errs.puts "\t#{exception.backtrace.join "\n\t"}" end ## # Choose from a list of options. +question+ is a prompt displayed above # the list. +list+ is a list of option strings. Returns the pair # [option_name, option_index]. def choose_from_list(question, list) @outs.puts question list.each_with_index do |item, index| @outs.puts " #{index + 1}. #{item}" end @outs.print "> " @outs.flush result = @ins.gets return nil, nil unless result result = result.strip.to_i - 1 return nil, nil unless (0...list.size) === result [list[result], result] end ## # Ask a question. Returns a true for yes, false for no. If not connected # to a tty, raises an exception if default is nil, otherwise returns # default. def ask_yes_no(question, default = nil) unless tty? if default.nil? raise Gem::OperationNotSupportedError, "Not connected to a tty and no default specified" else return default end end default_answer = case default when nil "yn" when true "Yn" else "yN" end result = nil while result.nil? do result = case ask "#{question} [#{default_answer}]" when /^y/i then true when /^n/i then false when /^$/ then default end end result end ## # Ask a question. Returns an answer if connected to a tty, nil otherwise. def ask(question) return nil unless tty? @outs.print(question + " ") @outs.flush result = @ins.gets result&.chomp! result end ## # Ask for a password. Does not echo response to terminal. def ask_for_password(question) return nil unless tty? @outs.print(question, " ") @outs.flush password = _gets_noecho @outs.puts password&.chomp! password end def require_io_console @require_io_console ||= begin begin require "io/console" rescue LoadError end true end end def _gets_noecho require_io_console @ins.noecho { @ins.gets } end ## # Display a statement. def say(statement = "") @outs.puts statement end ## # Display an informational alert. Will ask +question+ if it is not nil. def alert(statement, question = nil) @outs.puts "INFO: #{statement}" ask(question) if question end ## # Display a warning on stderr. Will ask +question+ if it is not nil. def alert_warning(statement, question = nil) @errs.puts "WARNING: #{statement}" ask(question) if question end ## # Display an error message in a location expected to get error messages. # Will ask +question+ if it is not nil. def alert_error(statement, question = nil) @errs.puts "ERROR: #{statement}" ask(question) if question end ## # Terminate the application with exit code +status+, running any exit # handlers that might have been defined. def terminate_interaction(status = 0) close raise Gem::SystemExitException, status end def close end ## # Return a progress reporter object chosen from the current verbosity. def progress_reporter(*args) case Gem.configuration.verbose when nil, false SilentProgressReporter.new(@outs, *args) when true SimpleProgressReporter.new(@outs, *args) else VerboseProgressReporter.new(@outs, *args) end end ## # An absolutely silent progress reporter. class SilentProgressReporter ## # The count of items is never updated for the silent progress reporter. attr_reader :count ## # Creates a silent progress reporter that ignores all input arguments. def initialize(out_stream, size, initial_message, terminal_message = nil) end ## # Does not print +message+ when updated as this object has taken a vow of # silence. def updated(message) end ## # Does not print anything when complete as this object has taken a vow of # silence. def done end end ## # A basic dotted progress reporter. class SimpleProgressReporter include Gem::DefaultUserInteraction ## # The number of progress items counted so far. attr_reader :count ## # Creates a new progress reporter that will write to +out_stream+ for # +size+ items. Shows the given +initial_message+ when progress starts # and the +terminal_message+ when it is complete. def initialize(out_stream, size, initial_message, terminal_message = "complete") @out = out_stream @total = size @count = 0 @terminal_message = terminal_message @out.puts initial_message end ## # Prints out a dot and ignores +message+. def updated(message) @count += 1 @out.print "." @out.flush end ## # Prints out the terminal message. def done @out.puts "\n#{@terminal_message}" end end ## # A progress reporter that prints out messages about the current progress. class VerboseProgressReporter include Gem::DefaultUserInteraction ## # The number of progress items counted so far. attr_reader :count ## # Creates a new progress reporter that will write to +out_stream+ for # +size+ items. Shows the given +initial_message+ when progress starts # and the +terminal_message+ when it is complete. def initialize(out_stream, size, initial_message, terminal_message = "complete") @out = out_stream @total = size @count = 0 @terminal_message = terminal_message @out.puts initial_message end ## # Prints out the position relative to the total and the +message+. def updated(message) @count += 1 @out.puts "#{@count}/#{@total}: #{message}" end ## # Prints out the terminal message. def done @out.puts @terminal_message end end ## # Return a download reporter object chosen from the current verbosity def download_reporter(*args) if [nil, false].include?(Gem.configuration.verbose) || !@outs.tty? SilentDownloadReporter.new(@outs, *args) else ThreadedDownloadReporter.new(@outs, *args) end end ## # An absolutely silent download reporter. class SilentDownloadReporter ## # The silent download reporter ignores all arguments def initialize(out_stream, *args) end ## # The silent download reporter does not display +filename+ or care about # +filesize+ because it is silent. def fetch(filename, filesize) end ## # Nothing can update the silent download reporter. def update(current) end ## # The silent download reporter won't tell you when the download is done. # Because it is silent. def done end end ## # A progress reporter that behaves nicely with threaded downloading. class ThreadedDownloadReporter MUTEX = Thread::Mutex.new ## # The current file name being displayed attr_reader :file_name ## # Creates a new threaded download reporter that will display on # +out_stream+. The other arguments are ignored. def initialize(out_stream, *args) @file_name = nil @out = out_stream end ## # Tells the download reporter that the +file_name+ is being fetched. # The other arguments are ignored. def fetch(file_name, *args) if @file_name.nil? @file_name = file_name locked_puts "Fetching #{@file_name}" end end ## # Updates the threaded download reporter for the given number of +bytes+. def update(bytes) # Do nothing. end ## # Indicates the download is complete. def done # Do nothing. end private def locked_puts(message) MUTEX.synchronize do @out.puts message end end end end ## # Subclass of StreamUI that instantiates the user interaction using $stdin, # $stdout, and $stderr. class Gem::ConsoleUI < Gem::StreamUI ## # The Console UI has no arguments as it defaults to reading input from # stdin, output to stdout and warnings or errors to stderr. def initialize super $stdin, $stdout, $stderr, true end end ## # SilentUI is a UI choice that is absolutely silent. class Gem::SilentUI < Gem::StreamUI ## # The SilentUI has no arguments as it does not use any stream. def initialize io = NullIO.new super io, io, io, false end def close end def download_reporter(*args) # :nodoc: SilentDownloadReporter.new(@outs, *args) end def progress_reporter(*args) # :nodoc: SilentProgressReporter.new(@outs, *args) end ## # An absolutely silent IO. class NullIO def puts(*args) end def print(*args) end def flush end def gets(*args) end def tty? false end end end PK!z، bundler_version_finder.rbnu[# frozen_string_literal: true module Gem::BundlerVersionFinder def self.bundler_version return if bundle_config_version == "system" v = ENV["BUNDLER_VERSION"] v = nil if v&.empty? v ||= bundle_update_bundler_version return if v == true v ||= bundle_config_version v ||= lockfile_version return unless v Gem::Version.new(v) end def self.prioritize!(specs) exact_match_index = specs.find_index {|spec| spec.version == bundler_version } return unless exact_match_index specs.unshift(specs.delete_at(exact_match_index)) end def self.bundle_update_bundler_version return unless ["bundle", "bundler"].include? File.basename($0) return unless "update".start_with?(ARGV.first || " ") bundler_version = nil update_index = nil ARGV.each_with_index do |a, i| if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN bundler_version = a end next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ bundler_version = $1 || true update_index = i end bundler_version end private_class_method :bundle_update_bundler_version def self.lockfile_version return unless contents = lockfile_contents regexp = /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ return unless contents =~ regexp $1 end private_class_method :lockfile_version def self.lockfile_contents gemfile = gemfile_path return unless gemfile lockfile = ENV["BUNDLE_LOCKFILE"] lockfile = nil if lockfile&.empty? lockfile ||= case gemfile when "gems.rb" then "gems.locked" else "#{gemfile}.lock" end return unless File.file?(lockfile) File.read(lockfile) end private_class_method :lockfile_contents def self.bundle_config_version version = nil [bundler_local_config_file, bundler_global_config_file].each do |config_file| next unless config_file && File.file?(config_file) contents = File.read(config_file) contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/ version = $1 break if version end version end private_class_method :bundle_config_version def self.bundler_global_config_file # see Bundler::Settings#global_config_file if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? ENV["BUNDLE_CONFIG"] elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty? ENV["BUNDLE_USER_CONFIG"] elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty? ENV["BUNDLE_USER_HOME"] + "config" elsif Gem.user_home && !Gem.user_home.empty? Gem.user_home + ".bundle/config" end end private_class_method :bundler_global_config_file def self.bundler_local_config_file gemfile = gemfile_path return unless gemfile File.join(File.dirname(gemfile), ".bundle", "config") end private_class_method :bundler_local_config_file def self.gemfile_path gemfile = ENV["BUNDLE_GEMFILE"] gemfile = nil if gemfile&.empty? unless gemfile begin Gem::Util.traverse_parents(Dir.pwd) do |directory| next unless gemfile = Gem::GEM_DEP_FILES.find {|f| File.file?(f) } gemfile = File.join directory, gemfile break end rescue Errno::ENOENT return end end gemfile end private_class_method :gemfile_path end PK!]ssl_certs/GlobalSignRootCA.pemnu[-----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp 1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE 38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- PK!44 indexer.rbnu[require 'rubygems' require 'rubygems/package' require 'time' begin gem 'builder' require 'builder/xchar' rescue LoadError end ## # Top level class for building the gem repository index. class Gem::Indexer include Gem::UserInteraction ## # Build indexes for RubyGems 1.2.0 and newer when true attr_accessor :build_modern ## # Index install location attr_reader :dest_directory ## # Specs index install location attr_reader :dest_specs_index ## # Latest specs index install location attr_reader :dest_latest_specs_index ## # Prerelease specs index install location attr_reader :dest_prerelease_specs_index ## # Index build directory attr_reader :directory ## # Create an indexer that will index the gems in +directory+. def initialize(directory, options = {}) require 'fileutils' require 'tmpdir' require 'zlib' unless defined?(Builder::XChar) then raise "Gem::Indexer requires that the XML Builder library be installed:" + "\n\tgem install builder" end options = { :build_modern => true }.merge options @build_modern = options[:build_modern] @dest_directory = directory @directory = File.join(Dir.tmpdir, "gem_generate_index_#{$$}") marshal_name = "Marshal.#{Gem.marshal_version}" @master_index = File.join @directory, 'yaml' @marshal_index = File.join @directory, marshal_name @quick_dir = File.join @directory, 'quick' @quick_marshal_dir = File.join @quick_dir, marshal_name @quick_marshal_dir_base = File.join "quick", marshal_name # FIX: UGH @quick_index = File.join @quick_dir, 'index' @latest_index = File.join @quick_dir, 'latest_index' @specs_index = File.join @directory, "specs.#{Gem.marshal_version}" @latest_specs_index = File.join(@directory, "latest_specs.#{Gem.marshal_version}") @prerelease_specs_index = File.join(@directory, "prerelease_specs.#{Gem.marshal_version}") @dest_specs_index = File.join(@dest_directory, "specs.#{Gem.marshal_version}") @dest_latest_specs_index = File.join(@dest_directory, "latest_specs.#{Gem.marshal_version}") @dest_prerelease_specs_index = File.join(@dest_directory, "prerelease_specs.#{Gem.marshal_version}") @files = [] end ## # Abbreviate the spec for downloading. Abbreviated specs are only used for # searching, downloading and related activities and do not need deployment # specific information (e.g. list of files). So we abbreviate the spec, # making it much smaller for quicker downloads. #-- # TODO move to Gem::Specification def abbreviate(spec) spec.files = [] spec.test_files = [] spec.rdoc_options = [] spec.extra_rdoc_files = [] spec.cert_chain = [] spec end ## # Build various indicies def build_indicies Gem::Specification.dirs = [] Gem::Specification.add_specs(*map_gems_to_specs(gem_file_list)) build_marshal_gemspecs build_modern_indicies if @build_modern compress_indicies end ## # Builds Marshal quick index gemspecs. def build_marshal_gemspecs count = Gem::Specification.count { |s| not s.default_gem? } progress = ui.progress_reporter count, "Generating Marshal quick index gemspecs for #{count} gems", "Complete" files = [] Gem.time 'Generated Marshal quick index gemspecs' do Gem::Specification.each do |spec| next if spec.default_gem? spec_file_name = "#{spec.original_name}.gemspec.rz" marshal_name = File.join @quick_marshal_dir, spec_file_name marshal_zipped = Gem.deflate Marshal.dump(spec) open marshal_name, 'wb' do |io| io.write marshal_zipped end files << marshal_name progress.updated spec.original_name end progress.done end @files << @quick_marshal_dir files end ## # Build a single index for RubyGems 1.2 and newer def build_modern_index(index, file, name) say "Generating #{name} index" Gem.time "Generated #{name} index" do open(file, 'wb') do |io| specs = index.map do |*spec| # We have to splat here because latest_specs is an array, while the # others are hashes. spec = spec.flatten.last platform = spec.original_platform # win32-api-1.0.4-x86-mswin32-60 unless String === platform then alert_warning "Skipping invalid platform in gem: #{spec.full_name}" next end platform = Gem::Platform::RUBY if platform.nil? or platform.empty? [spec.name, spec.version, platform] end specs = compact_specs(specs) Marshal.dump(specs, io) end end end ## # Builds indicies for RubyGems 1.2 and newer. Handles full, latest, prerelease def build_modern_indicies specs = Gem::Specification.reject { |s| s.default_gem? } prerelease, released = specs.partition { |s| s.version.prerelease? } latest_specs = Gem::Specification.latest_specs.reject { |s| s.default_gem? } build_modern_index(released.sort, @specs_index, 'specs') build_modern_index(latest_specs.sort, @latest_specs_index, 'latest specs') build_modern_index(prerelease.sort, @prerelease_specs_index, 'prerelease specs') @files += [@specs_index, "#{@specs_index}.gz", @latest_specs_index, "#{@latest_specs_index}.gz", @prerelease_specs_index, "#{@prerelease_specs_index}.gz"] end def map_gems_to_specs gems gems.map { |gemfile| if File.size(gemfile) == 0 then alert_warning "Skipping zero-length gem: #{gemfile}" next end begin spec = Gem::Package.new(gemfile).spec spec.loaded_from = gemfile # HACK: fuck this shit - borks all tests that use pl1 # if File.basename(gemfile, ".gem") != spec.original_name then # exp = spec.full_name # exp << " (#{spec.original_name})" if # spec.original_name != spec.full_name # msg = "Skipping misnamed gem: #{gemfile} should be named #{exp}" # alert_warning msg # next # end abbreviate spec sanitize spec spec rescue SignalException alert_error "Received signal, exiting" raise rescue Exception => e msg = ["Unable to process #{gemfile}", "#{e.message} (#{e.class})", "\t#{e.backtrace.join "\n\t"}"].join("\n") alert_error msg end }.compact end ## # Compresses indicies on disk #-- # All future files should be compressed using gzip, not deflate def compress_indicies say "Compressing indicies" Gem.time 'Compressed indicies' do if @build_modern then gzip @specs_index gzip @latest_specs_index gzip @prerelease_specs_index end end end ## # Compacts Marshal output for the specs index data source by using identical # objects as much as possible. def compact_specs(specs) names = {} versions = {} platforms = {} specs.map do |(name, version, platform)| names[name] = name unless names.include? name versions[version] = version unless versions.include? version platforms[platform] = platform unless platforms.include? platform [names[name], versions[version], platforms[platform]] end end ## # Compress +filename+ with +extension+. def compress(filename, extension) data = Gem.read_binary filename zipped = Gem.deflate data open "#{filename}.#{extension}", 'wb' do |io| io.write zipped end end ## # List of gem file names to index. def gem_file_list Dir[File.join(@dest_directory, "gems", '*.gem')] end ## # Builds and installs indicies. def generate_index make_temp_directories build_indicies install_indicies rescue SignalException ensure FileUtils.rm_rf @directory end ## # Zlib::GzipWriter wrapper that gzips +filename+ on disk. def gzip(filename) Zlib::GzipWriter.open "#{filename}.gz" do |io| io.write Gem.read_binary(filename) end end ## # Install generated indicies into the destination directory. def install_indicies verbose = Gem.configuration.really_verbose say "Moving index into production dir #{@dest_directory}" if verbose files = @files files.delete @quick_marshal_dir if files.include? @quick_dir if files.include? @quick_marshal_dir and not files.include? @quick_dir then files.delete @quick_marshal_dir dst_name = File.join(@dest_directory, @quick_marshal_dir_base) FileUtils.mkdir_p File.dirname(dst_name), :verbose => verbose FileUtils.rm_rf dst_name, :verbose => verbose FileUtils.mv(@quick_marshal_dir, dst_name, :verbose => verbose, :force => true) end files = files.map do |path| path.sub(/^#{Regexp.escape @directory}\/?/, '') # HACK? end files.each do |file| src_name = File.join @directory, file dst_name = File.join @dest_directory, file FileUtils.rm_rf dst_name, :verbose => verbose FileUtils.mv(src_name, @dest_directory, :verbose => verbose, :force => true) end end ## # Make directories for index generation def make_temp_directories FileUtils.rm_rf @directory FileUtils.mkdir_p @directory, :mode => 0700 FileUtils.mkdir_p @quick_marshal_dir end ## # Ensure +path+ and path with +extension+ are identical. def paranoid(path, extension) data = Gem.read_binary path compressed_data = Gem.read_binary "#{path}.#{extension}" unless data == Gem.inflate(compressed_data) then raise "Compressed file #{compressed_path} does not match uncompressed file #{path}" end end ## # Sanitize the descriptive fields in the spec. Sometimes non-ASCII # characters will garble the site index. Non-ASCII characters will # be replaced by their XML entity equivalent. def sanitize(spec) spec.summary = sanitize_string(spec.summary) spec.description = sanitize_string(spec.description) spec.post_install_message = sanitize_string(spec.post_install_message) spec.authors = spec.authors.collect { |a| sanitize_string(a) } spec end ## # Sanitize a single string. def sanitize_string(string) return string unless string # HACK the #to_s is in here because RSpec has an Array of Arrays of # Strings for authors. Need a way to disallow bad values on gemspec # generation. (Probably won't happen.) string = string.to_s begin Builder::XChar.encode string rescue NameError, NoMethodError string.to_xs end end ## # Perform an in-place update of the repository from newly added gems. def update_index make_temp_directories specs_mtime = File.stat(@dest_specs_index).mtime newest_mtime = Time.at 0 updated_gems = gem_file_list.select do |gem| gem_mtime = File.stat(gem).mtime newest_mtime = gem_mtime if gem_mtime > newest_mtime gem_mtime >= specs_mtime end if updated_gems.empty? then say 'No new gems' terminate_interaction 0 end specs = map_gems_to_specs updated_gems prerelease, released = specs.partition { |s| s.version.prerelease? } Gem::Specification.dirs = [] Gem::Specification.add_specs(*specs) files = build_marshal_gemspecs Gem.time 'Updated indexes' do update_specs_index released, @dest_specs_index, @specs_index update_specs_index released, @dest_latest_specs_index, @latest_specs_index update_specs_index(prerelease, @dest_prerelease_specs_index, @prerelease_specs_index) end compress_indicies verbose = Gem.configuration.really_verbose say "Updating production dir #{@dest_directory}" if verbose files << @specs_index files << "#{@specs_index}.gz" files << @latest_specs_index files << "#{@latest_specs_index}.gz" files << @prerelease_specs_index files << "#{@prerelease_specs_index}.gz" files = files.map do |path| path.sub(/^#{Regexp.escape @directory}\/?/, '') # HACK? end files.each do |file| src_name = File.join @directory, file dst_name = File.join @dest_directory, file # REFACTOR: duped above FileUtils.mv src_name, dst_name, :verbose => verbose, :force => true File.utime newest_mtime, newest_mtime, dst_name end end ## # Combines specs in +index+ and +source+ then writes out a new copy to # +dest+. For a latest index, does not ensure the new file is minimal. def update_specs_index(index, source, dest) specs_index = Marshal.load Gem.read_binary(source) index.each do |spec| platform = spec.original_platform platform = Gem::Platform::RUBY if platform.nil? or platform.empty? specs_index << [spec.name, spec.version, platform] end specs_index = compact_specs specs_index.uniq.sort open dest, 'wb' do |io| Marshal.dump specs_index, io end end end PK!.O test_case.rbnu[# TODO: $SAFE = 1 begin gem 'minitest', '~> 4.0' rescue NoMethodError, Gem::LoadError # for ruby tests end if defined? Gem::QuickLoader Gem::QuickLoader.load_full_rubygems_library else require 'rubygems' end begin gem 'minitest' rescue Gem::LoadError end # We have to load these up front because otherwise we'll try to load # them while we're testing rubygems, and thus we can't actually load them. unless Gem::Dependency.new('rdoc', '>= 3.10').matching_specs.empty? gem 'rdoc' gem 'json' end require 'minitest/autorun' require 'rubygems/deprecate' require 'fileutils' require 'pathname' require 'pp' require 'rubygems/package' require 'shellwords' require 'tmpdir' require 'uri' require 'zlib' Gem.load_yaml require 'rubygems/mock_gem_ui' module Gem ## # Allows setting the gem path searcher. This method is available when # requiring 'rubygems/test_case' def self.searcher=(searcher) @searcher = searcher end ## # Allows toggling Windows behavior. This method is available when requiring # 'rubygems/test_case' def self.win_platform=(val) @@win_platform = val end ## # Allows setting path to Ruby. This method is available when requiring # 'rubygems/test_case' def self.ruby= ruby @ruby = ruby end ## # When rubygems/test_case is required the default user interaction is a # MockGemUi. module DefaultUserInteraction @ui = Gem::MockGemUi.new end end ## # RubyGemTestCase provides a variety of methods for testing rubygems and # gem-related behavior in a sandbox. Through RubyGemTestCase you can install # and uninstall gems, fetch remote gems through a stub fetcher and be assured # your normal set of gems is not affected. # # Tests are always run at a safe level of 1. class Gem::TestCase < MiniTest::Unit::TestCase attr_accessor :fetcher # :nodoc: attr_accessor :gem_repo # :nodoc: attr_accessor :uri # :nodoc: def assert_activate expected, *specs specs.each do |spec| case spec when String then Gem::Specification.find_by_name(spec).activate when Gem::Specification then spec.activate else flunk spec.inspect end end loaded = Gem.loaded_specs.values.map(&:full_name) assert_equal expected.sort, loaded.sort if expected end # TODO: move to minitest def assert_path_exists path, msg = nil msg = message(msg) { "Expected path '#{path}' to exist" } assert File.exist?(path), msg end ## # Sets the ENABLE_SHARED entry in RbConfig::CONFIG to +value+ and restores # the original value when the block ends def enable_shared value enable_shared = RbConfig::CONFIG['ENABLE_SHARED'] RbConfig::CONFIG['ENABLE_SHARED'] = value yield ensure if enable_shared then RbConfig::CONFIG['enable_shared'] = enable_shared else RbConfig::CONFIG.delete 'enable_shared' end end # TODO: move to minitest def refute_path_exists path, msg = nil msg = message(msg) { "Expected path '#{path}' to not exist" } refute File.exist?(path), msg end def scan_make_command_lines(output) output.scan(/^#{Regexp.escape make_command}(?:[[:blank:]].*)?$/) end def parse_make_command_line(line) command, *args = line.shellsplit targets = [] macros = {} args.each do |arg| case arg when /\A(\w+)=/ macros[$1] = $' else targets << arg end end targets << '' if targets.empty? { :command => command, :targets => targets, :macros => macros, } end def assert_contains_make_command(target, output, msg = nil) if output.match(/\n/) msg = message(msg) { 'Expected output containing make command "%s": %s' % [ ('%s %s' % [make_command, target]).rstrip, output.inspect ] } else msg = message(msg) { 'Expected make command "%s": %s' % [ ('%s %s' % [make_command, target]).rstrip, output.inspect ] } end assert scan_make_command_lines(output).any? { |line| make = parse_make_command_line(line) if make[:targets].include?(target) yield make, line if block_given? true else false end }, msg end include Gem::DefaultUserInteraction undef_method :default_test if instance_methods.include? 'default_test' or instance_methods.include? :default_test @@project_dir = Dir.pwd.untaint unless defined?(@@project_dir) @@initial_reset = false ## # #setup prepares a sandboxed location to install gems. All installs are # directed to a temporary directory. All install plugins are removed. # # If the +RUBY+ environment variable is set the given path is used for # Gem::ruby. The local platform is set to i386-mswin32 for Windows # or i686-darwin8.10.1 otherwise. # # If the +KEEP_FILES+ environment variable is set the files will not be # removed from /tmp/test_rubygems_#{$$}.#{Time.now.to_i}. def setup super @orig_gem_home = ENV['GEM_HOME'] @orig_gem_path = ENV['GEM_PATH'] @orig_gem_vendor = ENV['GEM_VENDOR'] ENV['GEM_VENDOR'] = nil @current_dir = Dir.pwd @fetcher = nil @ui = Gem::MockGemUi.new tmpdir = File.expand_path Dir.tmpdir tmpdir.untaint if ENV['KEEP_FILES'] then @tempdir = File.join(tmpdir, "test_rubygems_#{$$}.#{Time.now.to_i}") else @tempdir = File.join(tmpdir, "test_rubygems_#{$$}") end @tempdir.untaint FileUtils.mkdir_p @tempdir # This makes the tempdir consistent on OS X. # File.expand_path Dir.tmpdir #=> "/var/..." # Dir.chdir Dir.tmpdir do File.expand_path '.' end #=> "/private/var/..." # TODO use File#realpath above instead of #expand_path once 1.8 support is # dropped. Dir.chdir @tempdir do @tempdir = File.expand_path '.' @tempdir.untaint end # This makes the tempdir consistent on Windows. # Dir.tmpdir may return short path name, but Dir[Dir.tmpdir] returns long # path name. https://bugs.ruby-lang.org/issues/10819 # File.expand_path or File.realpath doesn't convert path name to long path # name. Only Dir[] (= Dir.glob) works. # Short and long path name is specific to Windows filesystem. if win_platform? @tempdir = Dir[@tempdir][0] @tempdir.untaint end @gemhome = File.join @tempdir, 'gemhome' @userhome = File.join @tempdir, 'userhome' ENV["GEM_SPEC_CACHE"] = File.join @tempdir, 'spec_cache' @orig_ruby = if ENV['RUBY'] then ruby = Gem.ruby Gem.ruby = ENV['RUBY'] ruby end @git = ENV['GIT'] || 'git' Gem.ensure_gem_subdirectories @gemhome @orig_LOAD_PATH = $LOAD_PATH.dup $LOAD_PATH.map! { |s| File.expand_path(s).untaint } Dir.chdir @tempdir @orig_ENV_HOME = ENV['HOME'] ENV['HOME'] = @userhome Gem.instance_variable_set :@user_home, nil Gem.send :remove_instance_variable, :@ruby_version if Gem.instance_variables.include? :@ruby_version FileUtils.mkdir_p @gemhome FileUtils.mkdir_p @userhome @orig_gem_private_key_passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = PRIVATE_KEY_PASSPHRASE @default_dir = File.join @tempdir, 'default' @default_spec_dir = File.join @default_dir, "specifications", "default" Gem.instance_variable_set :@default_dir, @default_dir FileUtils.mkdir_p @default_spec_dir # We use Gem::Specification.reset the first time only so that if there # are unresolved deps that leak into the whole test suite, they're at least # reported once. if @@initial_reset Gem::Specification.unresolved_deps.clear # done to avoid cross-test warnings else @@initial_reset = true Gem::Specification.reset end Gem.use_paths(@gemhome) Gem::Security.reset Gem.loaded_specs.clear Gem.clear_default_specs Gem::Specification.unresolved_deps.clear Gem.configuration.verbose = true Gem.configuration.update_sources = true Gem::RemoteFetcher.fetcher = Gem::FakeFetcher.new @gem_repo = "http://gems.example.com/" @uri = URI.parse @gem_repo Gem.sources.replace [@gem_repo] Gem.searcher = nil Gem::SpecFetcher.fetcher = nil @orig_BASERUBY = RbConfig::CONFIG['BASERUBY'] RbConfig::CONFIG['BASERUBY'] = RbConfig::CONFIG['ruby_install_name'] @orig_arch = RbConfig::CONFIG['arch'] if win_platform? util_set_arch 'i386-mswin32' else util_set_arch 'i686-darwin8.10.1' end @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" end ## # #teardown restores the process to its original state and removes the # tempdir unless the +KEEP_FILES+ environment variable was set. def teardown $LOAD_PATH.replace @orig_LOAD_PATH if @orig_LOAD_PATH if @orig_BASERUBY RbConfig::CONFIG['BASERUBY'] = @orig_BASERUBY else RbConfig::CONFIG.delete('BASERUBY') end RbConfig::CONFIG['arch'] = @orig_arch if defined? Gem::RemoteFetcher then Gem::RemoteFetcher.fetcher = nil end Dir.chdir @current_dir FileUtils.rm_rf @tempdir unless ENV['KEEP_FILES'] ENV['GEM_HOME'] = @orig_gem_home ENV['GEM_PATH'] = @orig_gem_path ENV['GEM_VENDOR'] = @orig_gem_vendor Gem.ruby = @orig_ruby if @orig_ruby if @orig_ENV_HOME then ENV['HOME'] = @orig_ENV_HOME else ENV.delete 'HOME' end Gem.instance_variable_set :@default_dir, nil ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = @orig_gem_private_key_passphrase Gem::Specification._clear_load_cache end def common_installer_setup common_installer_teardown Gem.post_build do |installer| @post_build_hook_arg = installer true end Gem.post_install do |installer| @post_install_hook_arg = installer end Gem.post_uninstall do |uninstaller| @post_uninstall_hook_arg = uninstaller end Gem.pre_install do |installer| @pre_install_hook_arg = installer true end Gem.pre_uninstall do |uninstaller| @pre_uninstall_hook_arg = uninstaller end end def common_installer_teardown Gem.post_build_hooks.clear Gem.post_install_hooks.clear Gem.done_installing_hooks.clear Gem.post_reset_hooks.clear Gem.post_uninstall_hooks.clear Gem.pre_install_hooks.clear Gem.pre_reset_hooks.clear Gem.pre_uninstall_hooks.clear end ## # A git_gem is used with a gem dependencies file. The gem created here # has no files, just a gem specification for the given +name+ and +version+. # # Yields the +specification+ to the block, if given def git_gem name = 'a', version = 1 have_git? directory = File.join 'git', name directory = File.expand_path directory git_spec = Gem::Specification.new name, version do |specification| yield specification if block_given? end FileUtils.mkdir_p directory gemspec = "#{name}.gemspec" open File.join(directory, gemspec), 'w' do |io| io.write git_spec.to_ruby end head = nil Dir.chdir directory do unless File.exist? '.git' then system @git, 'init', '--quiet' system @git, 'config', 'user.name', 'RubyGems Tests' system @git, 'config', 'user.email', 'rubygems@example' end system @git, 'add', gemspec system @git, 'commit', '-a', '-m', 'a non-empty commit message', '--quiet' head = Gem::Util.popen('git', 'rev-parse', 'master').strip end return name, git_spec.version, directory, head end ## # Skips this test unless you have a git executable def have_git? return if in_path? @git skip 'cannot find git executable, use GIT environment variable to set' end def in_path? executable # :nodoc: return true if %r%\A([A-Z]:|/)% =~ executable and File.exist? executable ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory| File.exist? File.join directory, executable end end ## # Builds and installs the Gem::Specification +spec+ def install_gem spec, options = {} require 'rubygems/installer' gem = File.join @tempdir, "gems", "#{spec.full_name}.gem" unless File.exist? gem then use_ui Gem::MockGemUi.new do Dir.chdir @tempdir do Gem::Package.build spec end end gem = File.join(@tempdir, File.basename(spec.cache_file)).untaint end Gem::Installer.new(gem, options.merge({:wrappers => true})).install end ## # Builds and installs the Gem::Specification +spec+ into the user dir def install_gem_user spec install_gem spec, :user_install => true end ## # Uninstalls the Gem::Specification +spec+ def uninstall_gem spec require 'rubygems/uninstaller' Gem::Uninstaller.new(spec.name, :executables => true, :user_install => true).uninstall end ## # creates a temporary directory with hax # TODO: deprecate and remove def create_tmpdir tmpdir = nil Dir.chdir Dir.tmpdir do tmpdir = Dir.pwd end # HACK OSX /private/tmp tmpdir = File.join tmpdir, "test_rubygems_#{$$}" FileUtils.mkdir_p tmpdir return tmpdir end ## # Enables pretty-print for all tests def mu_pp(obj) s = '' s = PP.pp obj, s s = s.force_encoding(Encoding.default_external) if defined? Encoding s.chomp end ## # Reads a Marshal file at +path+ def read_cache(path) open path.dup.untaint, 'rb' do |io| Marshal.load io.read end end ## # Reads a binary file at +path+ def read_binary(path) Gem.read_binary path end ## # Writes a binary file to +path+ which is relative to +@gemhome+ def write_file(path) path = File.join @gemhome, path unless Pathname.new(path).absolute? dir = File.dirname path FileUtils.mkdir_p dir open path, 'wb' do |io| yield io if block_given? end path end def all_spec_names Gem::Specification.map(&:full_name) end ## # Creates a Gem::Specification with a minimum of extra work. +name+ and # +version+ are the gem's name and version, platform, author, email, # homepage, summary and description are defaulted. The specification is # yielded for customization. # # The gem is added to the installed gems in +@gemhome+ and the runtime. # # Use this with #write_file to build an installed gem. def quick_gem(name, version='2') require 'rubygems/specification' spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = name s.version = version s.author = 'A User' s.email = 'example@example.com' s.homepage = 'http://example.com' s.summary = "this is a summary" s.description = "This is a test description" yield(s) if block_given? end Gem::Specification.map # HACK: force specs to (re-)load before we write written_path = write_file spec.spec_file do |io| io.write spec.to_ruby_for_cache end spec.loaded_from = spec.loaded_from = written_path Gem::Specification.add_spec spec.for_cache return spec end ## # TODO: remove in RubyGems 3.0 def quick_spec name, version = '2' # :nodoc: util_spec name, version end ## # Builds a gem from +spec+ and places it in File.join @gemhome, # 'cache'. Automatically creates files based on +spec.files+ def util_build_gem(spec) dir = spec.gem_dir FileUtils.mkdir_p dir Dir.chdir dir do spec.files.each do |file| next if File.exist? file FileUtils.mkdir_p File.dirname(file) File.open file, 'w' do |fp| fp.puts "# #{file}" end end use_ui Gem::MockGemUi.new do Gem::Package.build spec end cache = spec.cache_file FileUtils.mv File.basename(cache), cache end end def util_remove_gem(spec) FileUtils.rm_rf spec.cache_file FileUtils.rm_rf spec.spec_file end ## # Removes all installed gems from +@gemhome+. def util_clear_gems FileUtils.rm_rf File.join(@gemhome, "gems") # TODO: use Gem::Dirs FileUtils.mkdir File.join(@gemhome, "gems") FileUtils.rm_rf File.join(@gemhome, "specifications") FileUtils.mkdir File.join(@gemhome, "specifications") Gem::Specification.reset end ## # Install the provided specs def install_specs(*specs) Gem::Specification.add_specs(*specs) Gem.searcher = nil end ## # Installs the provided default specs including writing the spec file def install_default_gems(*specs) install_default_specs(*specs) specs.each do |spec| open spec.loaded_from, 'w' do |io| io.write spec.to_ruby_for_cache end end end ## # Install the provided default specs def install_default_specs(*specs) install_specs(*specs) specs.each do |spec| Gem.register_default_spec(spec) end end def loaded_spec_names Gem.loaded_specs.values.map(&:full_name).sort end def unresolved_names Gem::Specification.unresolved_deps.values.map(&:to_s).sort end def save_loaded_features old_loaded_features = $LOADED_FEATURES.dup yield ensure $LOADED_FEATURES.replace old_loaded_features end ## # new_spec is deprecated as it is never used. # # TODO: remove in RubyGems 3.0 def new_spec name, version, deps = nil, *files # :nodoc: require 'rubygems/specification' spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = name s.version = version s.author = 'A User' s.email = 'example@example.com' s.homepage = 'http://example.com' s.summary = "this is a summary" s.description = "This is a test description" Array(deps).each do |n, req| s.add_dependency n, (req || '>= 0') end s.files.push(*files) unless files.empty? yield s if block_given? end spec.loaded_from = spec.spec_file unless files.empty? then write_file spec.spec_file do |io| io.write spec.to_ruby_for_cache end util_build_gem spec cache_file = File.join @tempdir, 'gems', "#{spec.full_name}.gem" FileUtils.mkdir_p File.dirname cache_file FileUtils.mv spec.cache_file, cache_file FileUtils.rm spec.spec_file end spec end def new_default_spec(name, version, deps = nil, *files) spec = util_spec name, version, deps spec.loaded_from = File.join(@default_spec_dir, spec.spec_name) spec.files = files lib_dir = File.join(@tempdir, "default_gems", "lib") $LOAD_PATH.unshift(lib_dir) files.each do |file| rb_path = File.join(lib_dir, file) FileUtils.mkdir_p(File.dirname(rb_path)) File.open(rb_path, "w") do |rb| rb << "# #{file}" end end spec end ## # Creates a spec with +name+, +version+. +deps+ can specify the dependency # or a +block+ can be given for full customization of the specification. def util_spec name, version = 2, deps = nil # :yields: specification raise "deps or block, not both" if deps and block_given? spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = name s.version = version s.author = 'A User' s.email = 'example@example.com' s.homepage = 'http://example.com' s.summary = "this is a summary" s.description = "This is a test description" yield s if block_given? end if deps then # Since Hash#each is unordered in 1.8, sort the keys and iterate that # way so the tests are deterministic on all implementations. deps.keys.sort.each do |n| spec.add_dependency n, (deps[n] || '>= 0') end end spec.loaded_from = spec.spec_file Gem::Specification.add_spec spec return spec end ## # Creates a gem with +name+, +version+ and +deps+. The specification will # be yielded before gem creation for customization. The gem will be placed # in File.join @tempdir, 'gems'. The specification and .gem file # location are returned. def util_gem(name, version, deps = nil, &block) # TODO: deprecate raise "deps or block, not both" if deps and block if deps then block = proc do |s| # Since Hash#each is unordered in 1.8, sort # the keys and iterate that way so the tests are # deterministic on all implementations. deps.keys.sort.each do |n| s.add_dependency n, (deps[n] || '>= 0') end end end spec = quick_gem(name, version, &block) util_build_gem spec cache_file = File.join @tempdir, 'gems', "#{spec.original_name}.gem" FileUtils.mkdir_p File.dirname cache_file FileUtils.mv spec.cache_file, cache_file FileUtils.rm spec.spec_file spec.loaded_from = nil [spec, cache_file] end ## # Gzips +data+. def util_gzip(data) out = StringIO.new Zlib::GzipWriter.wrap out do |io| io.write data end out.string end ## # Creates several default gems which all have a lib/code.rb file. The gems # are not installed but are available in the cache dir. # # +@a1+:: gem a version 1, this is the best-described gem. # +@a2+:: gem a version 2 # +@a3a:: gem a version 3.a # +@a_evil9+:: gem a_evil version 9, use this to ensure similarly-named gems # don't collide with a. # +@b2+:: gem b version 2 # +@c1_2+:: gem c version 1.2 # +@pl1+:: gem pl version 1, this gem has a legacy platform of i386-linux. # # Additional +prerelease+ gems may also be created: # # +@a2_pre+:: gem a version 2.a # TODO: nuke this and fix tests. this should speed up a lot def util_make_gems(prerelease = false) @a1 = quick_gem 'a', '1' do |s| s.files = %w[lib/code.rb] s.require_paths = %w[lib] s.date = Gem::Specification::TODAY - 86400 s.homepage = 'http://a.example.com' s.email = %w[example@example.com example2@example.com] s.authors = %w[Example Example2] s.description = <<-DESC This line is really, really long. So long, in fact, that it is more than eighty characters long! The purpose of this line is for testing wrapping behavior because sometimes people don't wrap their text to eighty characters. Without the wrapping, the text might not look good in the RSS feed. Also, a list: * An entry that\'s actually kind of sort * an entry that\'s really long, which will probably get wrapped funny. That's ok, somebody wasn't thinking straight when they made it more than eighty characters. DESC end init = proc do |s| s.files = %w[lib/code.rb] s.require_paths = %w[lib] end @a2 = quick_gem('a', '2', &init) @a3a = quick_gem('a', '3.a', &init) @a_evil9 = quick_gem('a_evil', '9', &init) @b2 = quick_gem('b', '2', &init) @c1_2 = quick_gem('c', '1.2', &init) @x = quick_gem('x', '1', &init) @dep_x = quick_gem('dep_x', '1') do |s| s.files = %w[lib/code.rb] s.require_paths = %w[lib] s.add_dependency 'x', '>= 1' end @pl1 = quick_gem 'pl', '1' do |s| # l for legacy s.files = %w[lib/code.rb] s.require_paths = %w[lib] s.platform = Gem::Platform.new 'i386-linux' s.instance_variable_set :@original_platform, 'i386-linux' end if prerelease @a2_pre = quick_gem('a', '2.a', &init) write_file File.join(*%W[gems #{@a2_pre.original_name} lib code.rb]) util_build_gem @a2_pre end write_file File.join(*%W[gems #{@a1.original_name} lib code.rb]) write_file File.join(*%W[gems #{@a2.original_name} lib code.rb]) write_file File.join(*%W[gems #{@a3a.original_name} lib code.rb]) write_file File.join(*%W[gems #{@a_evil9.original_name} lib code.rb]) write_file File.join(*%W[gems #{@b2.original_name} lib code.rb]) write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb]) write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb]) write_file File.join(*%W[gems #{@x.original_name} lib code.rb]) write_file File.join(*%W[gems #{@dep_x.original_name} lib code.rb]) [@a1, @a2, @a3a, @a_evil9, @b2, @c1_2, @pl1, @x, @dep_x].each do |spec| util_build_gem spec end FileUtils.rm_r File.join(@gemhome, "gems", @pl1.original_name) end ## # Set the platform to +arch+ def util_set_arch(arch) RbConfig::CONFIG['arch'] = arch platform = Gem::Platform.new arch Gem.instance_variable_set :@platforms, nil Gem::Platform.instance_variable_set :@local, nil platform end ## # Sets up a fake fetcher using the gems from #util_make_gems. Optionally # additional +prerelease+ gems may be included. # # Gems created by this method may be fetched using Gem::RemoteFetcher. def util_setup_fake_fetcher(prerelease = false) require 'zlib' require 'socket' require 'rubygems/remote_fetcher' @fetcher = Gem::FakeFetcher.new util_make_gems(prerelease) Gem::Specification.reset @all_gems = [@a1, @a2, @a3a, @a_evil9, @b2, @c1_2].sort @all_gem_names = @all_gems.map { |gem| gem.full_name } gem_names = [@a1.full_name, @a2.full_name, @a3a.full_name, @b2.full_name] @gem_names = gem_names.sort.join("\n") Gem::RemoteFetcher.fetcher = @fetcher end ## # Add +spec+ to +@fetcher+ serving the data in the file +path+. # +repo+ indicates which repo to make +spec+ appear to be in. def add_to_fetcher(spec, path=nil, repo=@gem_repo) path ||= spec.cache_file @fetcher.data["#{@gem_repo}gems/#{spec.file_name}"] = read_binary(path) end ## # Sets up Gem::SpecFetcher to return information from the gems in +specs+. # Best used with +@all_gems+ from #util_setup_fake_fetcher. def util_setup_spec_fetcher(*specs) specs -= Gem::Specification._all Gem::Specification.add_specs(*specs) spec_fetcher = Gem::SpecFetcher.fetcher prerelease, all = Gem::Specification.partition { |spec| spec.version.prerelease? } spec_fetcher.specs[@uri] = [] all.each do |spec| spec_fetcher.specs[@uri] << spec.name_tuple end spec_fetcher.latest_specs[@uri] = [] Gem::Specification.latest_specs.each do |spec| spec_fetcher.latest_specs[@uri] << spec.name_tuple end spec_fetcher.prerelease_specs[@uri] = [] prerelease.each do |spec| spec_fetcher.prerelease_specs[@uri] << spec.name_tuple end # HACK for test_download_to_cache unless Gem::RemoteFetcher === @fetcher then v = Gem.marshal_version specs = all.map { |spec| spec.name_tuple } s_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic specs latest_specs = Gem::Specification.latest_specs.map do |spec| spec.name_tuple end l_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic latest_specs prerelease_specs = prerelease.map { |spec| spec.name_tuple } p_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic prerelease_specs @fetcher.data["#{@gem_repo}specs.#{v}.gz"] = s_zip @fetcher.data["#{@gem_repo}latest_specs.#{v}.gz"] = l_zip @fetcher.data["#{@gem_repo}prerelease_specs.#{v}.gz"] = p_zip v = Gem.marshal_version Gem::Specification.each do |spec| path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz" data = Marshal.dump spec data_deflate = Zlib::Deflate.deflate data @fetcher.data[path] = data_deflate end end nil # force errors end ## # Deflates +data+ def util_zip(data) Zlib::Deflate.deflate data end def util_set_RUBY_VERSION(version, patchlevel = nil, revision = nil) if Gem.instance_variables.include? :@ruby_version or Gem.instance_variables.include? '@ruby_version' then Gem.send :remove_instance_variable, :@ruby_version end @RUBY_VERSION = RUBY_VERSION @RUBY_PATCHLEVEL = RUBY_PATCHLEVEL if defined?(RUBY_PATCHLEVEL) @RUBY_REVISION = RUBY_REVISION if defined?(RUBY_REVISION) Object.send :remove_const, :RUBY_VERSION Object.send :remove_const, :RUBY_PATCHLEVEL if defined?(RUBY_PATCHLEVEL) Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION) Object.const_set :RUBY_VERSION, version Object.const_set :RUBY_PATCHLEVEL, patchlevel if patchlevel Object.const_set :RUBY_REVISION, revision if revision end def util_restore_RUBY_VERSION Object.send :remove_const, :RUBY_VERSION Object.send :remove_const, :RUBY_PATCHLEVEL if defined?(RUBY_PATCHLEVEL) Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION) Object.const_set :RUBY_VERSION, @RUBY_VERSION Object.const_set :RUBY_PATCHLEVEL, @RUBY_PATCHLEVEL if defined?(@RUBY_PATCHLEVEL) Object.const_set :RUBY_REVISION, @RUBY_REVISION if defined?(@RUBY_REVISION) end ## # Is this test being run on a Windows platform? def self.win_platform? Gem.win_platform? end ## # Is this test being run on a Windows platform? def win_platform? Gem.win_platform? end ## # Returns whether or not we're on a version of Ruby built with VC++ (or # Borland) versus Cygwin, Mingw, etc. def self.vc_windows? RUBY_PLATFORM.match('mswin') end ## # Returns whether or not we're on a version of Ruby built with VC++ (or # Borland) versus Cygwin, Mingw, etc. def vc_windows? RUBY_PLATFORM.match('mswin') end ## # Returns the make command for the current platform. For versions of Ruby # built on MS Windows with VC++ or Borland it will return 'nmake'. On all # other platforms, including Cygwin, it will return 'make'. def self.make_command ENV["make"] || (vc_windows? ? 'nmake' : 'make') end ## # Returns the make command for the current platform. For versions of Ruby # built on MS Windows with VC++ or Borland it will return 'nmake'. On all # other platforms, including Cygwin, it will return 'make'. def make_command ENV["make"] || (vc_windows? ? 'nmake' : 'make') end ## # Returns whether or not the nmake command could be found. def nmake_found? system('nmake /? 1>NUL 2>&1') end # In case we're building docs in a background process, this method waits for # that process to exit (or if it's already been reaped, or never happened, # swallows the Errno::ECHILD error). def wait_for_child_process_to_exit Process.wait if Process.respond_to?(:fork) rescue Errno::ECHILD end ## # Allows tests to use a random (but controlled) port number instead of # a hardcoded one. This helps CI tools when running parallels builds on # the same builder slave. def self.process_based_port @@process_based_port ||= 8000 + $$ % 1000 end ## # See ::process_based_port def process_based_port self.class.process_based_port end ## # Allows the proper version of +rake+ to be used for the test. def build_rake_in(good=true) gem_ruby = Gem.ruby Gem.ruby = @@ruby env_rake = ENV["rake"] rake = (good ? @@good_rake : @@bad_rake) ENV["rake"] = rake yield rake ensure Gem.ruby = gem_ruby if env_rake ENV["rake"] = env_rake else ENV.delete("rake") end end ## # Finds the path to the Ruby executable def self.rubybin ruby = ENV["RUBY"] return ruby if ruby ruby = "ruby" rubyexe = "#{ruby}.exe" 3.times do if File.exist? ruby and File.executable? ruby and !File.directory? ruby return File.expand_path(ruby) end if File.exist? rubyexe and File.executable? rubyexe return File.expand_path(rubyexe) end ruby = File.join("..", ruby) end begin require "rbconfig" File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]) rescue LoadError "ruby" end end @@ruby = rubybin @@good_rake = "#{rubybin} #{File.expand_path('../../../test/rubygems/good_rake.rb', __FILE__)}" @@bad_rake = "#{rubybin} #{File.expand_path('../../../test/rubygems/bad_rake.rb', __FILE__)}" ## # Construct a new Gem::Dependency. def dep name, *requirements Gem::Dependency.new name, *requirements end ## # Constructs a Gem::Resolver::DependencyRequest from a # Gem::Dependency +dep+, a +from_name+ and +from_version+ requesting the # dependency and a +parent+ DependencyRequest def dependency_request dep, from_name, from_version, parent = nil remote = Gem::Source.new @uri unless parent then parent_dep = dep from_name, from_version parent = Gem::Resolver::DependencyRequest.new parent_dep, nil end spec = Gem::Resolver::IndexSpecification.new \ nil, from_name, from_version, remote, Gem::Platform::RUBY activation = Gem::Resolver::ActivationRequest.new spec, parent Gem::Resolver::DependencyRequest.new dep, activation end ## # Constructs a new Gem::Requirement. def req *requirements return requirements.first if Gem::Requirement === requirements.first Gem::Requirement.create requirements end ## # Constructs a new Gem::Specification. def spec name, version, &block Gem::Specification.new name, v(version), &block end ## # Creates a SpecFetcher pre-filled with the gems or specs defined in the # block. # # Yields a +fetcher+ object that responds to +spec+ and +gem+. +spec+ adds # a specification to the SpecFetcher while +gem+ adds both a specification # and the gem data to the RemoteFetcher so the built gem can be downloaded. # # If only the a-3 gem is supposed to be downloaded you can save setup # time by creating only specs for the other versions: # # spec_fetcher do |fetcher| # fetcher.spec 'a', 1 # fetcher.spec 'a', 2, 'b' => 3 # dependency on b = 3 # fetcher.gem 'a', 3 do |spec| # # spec is a Gem::Specification # # ... # end # end def spec_fetcher repository = @gem_repo Gem::TestCase::SpecFetcherSetup.declare self, repository do |spec_fetcher_setup| yield spec_fetcher_setup if block_given? end end ## # Construct a new Gem::Version. def v string Gem::Version.create string end ## # A vendor_gem is used with a gem dependencies file. The gem created here # has no files, just a gem specification for the given +name+ and +version+. # # Yields the +specification+ to the block, if given def vendor_gem name = 'a', version = 1 directory = File.join 'vendor', name vendor_spec = Gem::Specification.new name, version do |specification| yield specification if block_given? end FileUtils.mkdir_p directory open File.join(directory, "#{name}.gemspec"), 'w' do |io| io.write vendor_spec.to_ruby end return name, vendor_spec.version, directory end ## # The StaticSet is a static set of gem specifications used for testing only. # It is available by requiring Gem::TestCase. class StaticSet < Gem::Resolver::Set ## # A StaticSet ignores remote because it has a fixed set of gems. attr_accessor :remote ## # Creates a new StaticSet for the given +specs+ def initialize(specs) super() @specs = specs @remote = true end ## # Adds +spec+ to this set. def add spec @specs << spec end ## # Finds +dep+ in this set. def find_spec(dep) @specs.reverse_each do |s| return s if dep.matches_spec? s end end ## # Finds all gems matching +dep+ in this set. def find_all(dep) @specs.find_all { |s| dep.match? s, @prerelease } end ## # Loads a Gem::Specification from this set which has the given +name+, # version +ver+, +platform+. The +source+ is ignored. def load_spec name, ver, platform, source dep = Gem::Dependency.new name, ver spec = find_spec dep Gem::Specification.new spec.name, spec.version do |s| s.platform = spec.platform end end def prefetch reqs # :nodoc: end end ## # Loads certificate named +cert_name+ from test/rubygems/. def self.load_cert cert_name cert_file = cert_path cert_name cert = File.read cert_file OpenSSL::X509::Certificate.new cert end ## # Returns the path to the certificate named +cert_name+ from # test/rubygems/. def self.cert_path cert_name if 32 == (Time.at(2**32) rescue 32) then cert_file = File.expand_path "../../../test/rubygems/#{cert_name}_cert_32.pem", __FILE__ return cert_file if File.exist? cert_file end File.expand_path "../../../test/rubygems/#{cert_name}_cert.pem", __FILE__ end ## # Loads an RSA private key named +key_name+ with +passphrase+ in test/rubygems/ def self.load_key key_name, passphrase = nil key_file = key_path key_name key = File.read key_file OpenSSL::PKey::RSA.new key, passphrase end ## # Returns the path to the key named +key_name+ from test/rubygems def self.key_path key_name File.expand_path "../../../test/rubygems/#{key_name}_key.pem", __FILE__ end # :stopdoc: # only available in RubyGems tests PRIVATE_KEY_PASSPHRASE = 'Foo bar' begin PRIVATE_KEY = load_key 'private' PRIVATE_KEY_PATH = key_path 'private' # ENCRYPTED_PRIVATE_KEY is PRIVATE_KEY encrypted with PRIVATE_KEY_PASSPHRASE ENCRYPTED_PRIVATE_KEY = load_key 'encrypted_private', PRIVATE_KEY_PASSPHRASE ENCRYPTED_PRIVATE_KEY_PATH = key_path 'encrypted_private' PUBLIC_KEY = PRIVATE_KEY.public_key PUBLIC_CERT = load_cert 'public' PUBLIC_CERT_PATH = cert_path 'public' rescue Errno::ENOENT PRIVATE_KEY = nil PUBLIC_KEY = nil PUBLIC_CERT = nil end if defined?(OpenSSL::SSL) end require 'rubygems/test_utilities' PK!Ԑ [[ server.rbnu[require 'webrick' require 'zlib' require 'erb' require 'rubygems' require 'rubygems/rdoc' ## # Gem::Server and allows users to serve gems for consumption by # `gem --remote-install`. # # gem_server starts an HTTP server on the given port and serves the following: # * "/" - Browsing of gem spec files for installed gems # * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index # * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs # name/version/platform index # * "/quick/" - Individual gemspecs # * "/gems" - Direct access to download the installable gems # * "/rdoc?q=" - Search for installed rdoc documentation # # == Usage # # gem_server = Gem::Server.new Gem.dir, 8089, false # gem_server.run # #-- # TODO Refactor into a real WEBrick servlet to remove code duplication. class Gem::Server attr_reader :spec_dirs include ERB::Util include Gem::UserInteraction SEARCH = <<-SEARCH
SEARCH DOC_TEMPLATE = <<-'DOC_TEMPLATE' RubyGems Documentation Index
<%= SEARCH %>

RubyGems Documentation Index

Summary

There are <%=values["gem_count"]%> gems installed:

<%= values["specs"].map { |v| "#{v["name"]}" }.join ', ' %>.

Gems

<% values["specs"].each do |spec| %>
<% if spec["first_name_entry"] then %> "> <% end %> <%=spec["name"]%> <%=spec["version"]%> <% if spec["ri_installed"] then %> ">[rdoc] <% elsif spec["rdoc_installed"] then %> ">[rdoc] <% else %> [rdoc] <% end %> <% if spec["homepage"] then %> " title="<%=spec["homepage"]%>">[www] <% else %> [www] <% end %> <% if spec["has_deps"] then %> - depends on <%= spec["dependencies"].map { |v| "#{v["name"]}" }.join ', ' %>. <% end %>
<%=spec["summary"]%> <% if spec["executables"] then %>
<% if spec["only_one_executable"] then %> Executable is <% else %> Executables are <%end%> <%= spec["executables"].map { |v| "#{v["executable"]}"}.join ', ' %>. <%end%>

<% end %>
DOC_TEMPLATE # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108 RDOC_CSS = <<-RDOC_CSS body { font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 90%; margin: 0; margin-left: 40px; padding: 0; background: white; } h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } h1 { font-size: 150%; } h2,h3,h4 { margin-top: 1em; } a { background: #eef; color: #039; text-decoration: none; } a:hover { background: #039; color: #eef; } /* Override the base stylesheets Anchor inside a table cell */ td > a { background: transparent; color: #039; text-decoration: none; } /* and inside a section title */ .section-title > a { background: transparent; color: #eee; text-decoration: none; } /* === Structural elements =================================== */ div#index { margin: 0; margin-left: -40px; padding: 0; font-size: 90%; } div#index a { margin-left: 0.7em; } div#index .section-bar { margin-left: 0px; padding-left: 0.7em; background: #ccc; font-size: small; } div#classHeader, div#fileHeader { width: auto; color: white; padding: 0.5em 1.5em 0.5em 1.5em; margin: 0; margin-left: -40px; border-bottom: 3px solid #006; } div#classHeader a, div#fileHeader a { background: inherit; color: white; } div#classHeader td, div#fileHeader td { background: inherit; color: white; } div#fileHeader { background: #057; } div#classHeader { background: #048; } .class-name-in-header { font-size: 180%; font-weight: bold; } div#bodyContent { padding: 0 1.5em 0 1.5em; } div#description { padding: 0.5em 1.5em; background: #efefef; border: 1px dotted #999; } div#description h1,h2,h3,h4,h5,h6 { color: #125;; background: transparent; } div#validator-badges { text-align: center; } div#validator-badges img { border: 0; } div#copyright { color: #333; background: #efefef; font: 0.75em sans-serif; margin-top: 5em; margin-bottom: 0; padding: 0.5em 2em; } /* === Classes =================================== */ table.header-table { color: white; font-size: small; } .type-note { font-size: small; color: #DEDEDE; } .xxsection-bar { background: #eee; color: #333; padding: 3px; } .section-bar { color: #333; border-bottom: 1px solid #999; margin-left: -20px; } .section-title { background: #79a; color: #eee; padding: 3px; margin-top: 2em; margin-left: -30px; border: 1px solid #999; } .top-aligned-row { vertical-align: top } .bottom-aligned-row { vertical-align: bottom } /* --- Context section classes ----------------------- */ .context-row { } .context-item-name { font-family: monospace; font-weight: bold; color: black; } .context-item-value { font-size: small; color: #448; } .context-item-desc { color: #333; padding-left: 2em; } /* --- Method classes -------------------------- */ .method-detail { background: #efefef; padding: 0; margin-top: 0.5em; margin-bottom: 1em; border: 1px dotted #ccc; } .method-heading { color: black; background: #ccc; border-bottom: 1px solid #666; padding: 0.2em 0.5em 0 0.5em; } .method-signature { color: black; background: inherit; } .method-name { font-weight: bold; } .method-args { font-style: italic; } .method-description { padding: 0 0.5em 0 0.5em; } /* --- Source code sections -------------------- */ a.source-toggle { font-size: 90%; } div.method-source-code { background: #262626; color: #ffdead; margin: 1em; padding: 0.5em; border: 1px dashed #999; overflow: hidden; } div.method-source-code pre { color: #ffdead; overflow: hidden; } /* --- Ruby keyword styles --------------------- */ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; } .ruby-constant { color: #7fffd4; background: transparent; } .ruby-keyword { color: #00ffff; background: transparent; } .ruby-ivar { color: #eedd82; background: transparent; } .ruby-operator { color: #00ffee; background: transparent; } .ruby-identifier { color: #ffdead; background: transparent; } .ruby-node { color: #ffa07a; background: transparent; } .ruby-comment { color: #b22222; font-weight: bold; background: transparent; } .ruby-regexp { color: #ffa07a; background: transparent; } .ruby-value { color: #7fffd4; background: transparent; } RDOC_CSS RDOC_NO_DOCUMENTATION = <<-'NO_DOC' Found documentation
<%= SEARCH %>

No documentation found

No gems matched <%= h query.inspect %>

Back to complete gem index

NO_DOC RDOC_SEARCH_TEMPLATE = <<-'RDOC_SEARCH' Found documentation
<%= SEARCH %>

Found documentation

Summary

<%=doc_items.length%> documentation topics found.

Topics

<% doc_items.each do |doc_item| %>
<%=doc_item[:name]%> [rdoc]
<%=doc_item[:summary]%>

<% end %>

Back to complete gem index

RDOC_SEARCH def self.run(options) new(options[:gemdir], options[:port], options[:daemon], options[:launch], options[:addresses]).run end def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil) Gem::RDoc.load_rdoc Socket.do_not_reverse_lookup = true @gem_dirs = Array gem_dirs @port = port @daemon = daemon @launch = launch @addresses = addresses logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger @spec_dirs = @gem_dirs.map { |gem_dir| File.join gem_dir, 'specifications' } @spec_dirs.reject! { |spec_dir| !File.directory? spec_dir } reset_gems @have_rdoc_4_plus = nil end def add_date res res['date'] = @spec_dirs.map do |spec_dir| File.stat(spec_dir).mtime end.max end def doc_root gem_name if have_rdoc_4_plus? then "/doc_root/#{gem_name}/" else "/doc_root/#{gem_name}/rdoc/index.html" end end def have_rdoc_4_plus? @have_rdoc_4_plus ||= Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version end def latest_specs(req, res) reset_gems res['content-type'] = 'application/x-gzip' add_date res latest_specs = Gem::Specification.latest_specs specs = latest_specs.sort.map do |spec| platform = spec.original_platform || Gem::Platform::RUBY [spec.name, spec.version, platform] end specs = Marshal.dump specs if req.path =~ /\.gz$/ then specs = Gem.gzip specs res['content-type'] = 'application/x-gzip' else res['content-type'] = 'application/octet-stream' end if req.request_method == 'HEAD' then res['content-length'] = specs.length else res.body << specs end end ## # Creates server sockets based on the addresses option. If no addresses # were given a server socket for all interfaces is created. def listen addresses = @addresses addresses = [nil] unless addresses listeners = 0 addresses.each do |address| begin @server.listen address, @port @server.listeners[listeners..-1].each do |listener| host, port = listener.addr.values_at 2, 1 host = "[#{host}]" if host =~ /:/ # we don't reverse lookup say "Server started at http://#{host}:#{port}" end listeners = @server.listeners.length rescue SystemCallError next end end if @server.listeners.empty? then say "Unable to start a server." say "Check for running servers or your --bind and --port arguments" terminate_interaction 1 end end def prerelease_specs req, res reset_gems res['content-type'] = 'application/x-gzip' add_date res specs = Gem::Specification.select do |spec| spec.version.prerelease? end.sort.map do |spec| platform = spec.original_platform || Gem::Platform::RUBY [spec.name, spec.version, platform] end specs = Marshal.dump specs if req.path =~ /\.gz$/ then specs = Gem.gzip specs res['content-type'] = 'application/x-gzip' else res['content-type'] = 'application/octet-stream' end if req.request_method == 'HEAD' then res['content-length'] = specs.length else res.body << specs end end def quick(req, res) reset_gems res['content-type'] = 'text/plain' add_date res case req.request_uri.path when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+[^-]*?)(-.*?)?\.gemspec\.rz$| then marshal_format, name, version, platform = $1, $2, $3, $4 specs = Gem::Specification.find_all_by_name name, version selector = [name, version, platform].map(&:inspect).join ' ' platform = if platform then Gem::Platform.new platform.sub(/^-/, '') else Gem::Platform::RUBY end specs = specs.select { |s| s.platform == platform } if specs.empty? then res.status = 404 res.body = "No gems found matching #{selector}" elsif specs.length > 1 then res.status = 500 res.body = "Multiple gems found matching #{selector}" elsif marshal_format then res['content-type'] = 'application/x-deflate' res.body << Gem.deflate(Marshal.dump(specs.first)) end else raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." end end def root(req, res) reset_gems add_date res raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless req.path == '/' specs = [] total_file_count = 0 Gem::Specification.each do |spec| total_file_count += spec.files.size deps = spec.dependencies.map { |dep| { "name" => dep.name, "type" => dep.type, "version" => dep.requirement.to_s, } } deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] } deps.last["is_last"] = true unless deps.empty? # executables executables = spec.executables.sort.collect { |exec| {"executable" => exec} } executables = nil if executables.empty? executables.last["is_last"] = true if executables # Pre-process spec homepage for safety reasons begin homepage_uri = URI.parse(spec.homepage) if [URI::HTTP, URI::HTTPS].member? homepage_uri.class homepage_uri = spec.homepage else homepage_uri = "." end rescue URI::InvalidURIError homepage_uri = "." end specs << { "authors" => spec.authors.sort.join(", "), "date" => spec.date.to_s, "dependencies" => deps, "doc_path" => doc_root(spec.full_name), "executables" => executables, "only_one_executable" => (executables && executables.size == 1), "full_name" => spec.full_name, "has_deps" => !deps.empty?, "homepage" => homepage_uri, "name" => spec.name, "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?, "ri_installed" => Gem::RDoc.new(spec).ri_installed?, "summary" => spec.summary, "version" => spec.version.to_s, } end specs << { "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", "dependencies" => [], "doc_path" => doc_root("rubygems-#{Gem::VERSION}"), "executables" => [{"executable" => 'gem', "is_last" => true}], "only_one_executable" => true, "full_name" => "rubygems-#{Gem::VERSION}", "has_deps" => false, "homepage" => "http://docs.rubygems.org/", "name" => 'rubygems', "ri_installed" => true, "summary" => "RubyGems itself", "version" => Gem::VERSION, } specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] } specs.last["is_last"] = true # tag all specs with first_name_entry last_spec = nil specs.each do |spec| is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) spec["first_name_entry"] = is_first last_spec = spec end # create page from template template = ERB.new(DOC_TEMPLATE) res['content-type'] = 'text/html' values = { "gem_count" => specs.size.to_s, "specs" => specs, "total_file_count" => total_file_count.to_s } # suppress 1.9.3dev warning about unused variable values = values result = template.result binding res.body = result end ## # Can be used for quick navigation to the rdoc documentation. You can then # define a search shortcut for your browser. E.g. in Firefox connect # 'shortcut:rdoc' to http://localhost:8808/rdoc?q=%s template. Then you can # directly open the ActionPack documentation by typing 'rdoc actionp'. If # there are multiple hits for the search term, they are presented as a list # with links. # # Search algorithm aims for an intuitive search: # 1. first try to find the gems and documentation folders which name # starts with the search term # 2. search for entries, that *contain* the search term # 3. show all the gems # # If there is only one search hit, user is immediately redirected to the # documentation for the particular gem, otherwise a list with results is # shown. # # === Additional trick - install documentation for Ruby core # # Note: please adjust paths accordingly use for example 'locate yaml.rb' and # 'gem environment' to identify directories, that are specific for your # local installation # # 1. install Ruby sources # cd /usr/src # sudo apt-get source ruby # # 2. generate documentation # rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc \ # /usr/lib/ruby/1.8 ruby1.8-1.8.7.72 # # By typing 'rdoc core' you can now access the core documentation def rdoc(req, res) query = req.query['q'] show_rdoc_for_pattern("#{query}*", res) && return show_rdoc_for_pattern("*#{query}*", res) && return template = ERB.new RDOC_NO_DOCUMENTATION res['content-type'] = 'text/html' res.body = template.result binding end ## # Updates the server to use the latest installed gems. def reset_gems # :nodoc: Gem::Specification.dirs = @gem_dirs end ## # Returns true and prepares http response, if rdoc for the requested gem # name pattern was found. # # The search is based on the file system content, not on the gems metadata. # This allows additional documentation folders like 'core' for the Ruby core # documentation - just put it underneath the main doc folder. def show_rdoc_for_pattern(pattern, res) found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path| File.exist? File.join(path, 'rdoc/index.html') } case found_gems.length when 0 return false when 1 new_path = File.basename(found_gems[0]) res.status = 302 res['Location'] = doc_root new_path return true else doc_items = [] found_gems.each do |file_name| base_name = File.basename(file_name) doc_items << { :name => base_name, :url => doc_root(new_path), :summary => '' } end template = ERB.new(RDOC_SEARCH_TEMPLATE) res['content-type'] = 'text/html' result = template.result binding res.body = result return true end end def run listen WEBrick::Daemon.start if @daemon @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) @server.mount_proc "/latest_specs.#{Gem.marshal_version}", method(:latest_specs) @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", method(:latest_specs) @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}", method(:prerelease_specs) @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}.gz", method(:prerelease_specs) @server.mount_proc "/quick/", method(:quick) @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| res['content-type'] = 'text/css' add_date res res.body << RDOC_CSS end @server.mount_proc "/", method(:root) @server.mount_proc "/rdoc", method(:rdoc) file_handlers = { '/gems' => '/cache/', } if have_rdoc_4_plus? then @server.mount '/doc_root', RDoc::Servlet, '/doc_root' else file_handlers['/doc_root'] = '/doc/' end @gem_dirs.each do |gem_dir| file_handlers.each do |mount_point, mount_dir| @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, File.join(gem_dir, mount_dir), true) end end trap("INT") { @server.shutdown; exit! } trap("TERM") { @server.shutdown; exit! } launch if @launch @server.start end def specs(req, res) reset_gems add_date res specs = Gem::Specification.sort_by(&:sort_obj).map do |spec| platform = spec.original_platform || Gem::Platform::RUBY [spec.name, spec.version, platform] end specs = Marshal.dump specs if req.path =~ /\.gz$/ then specs = Gem.gzip specs res['content-type'] = 'application/x-gzip' else res['content-type'] = 'application/octet-stream' end if req.request_method == 'HEAD' then res['content-length'] = specs.length else res.body << specs end end def launch listeners = @server.listeners.map{|l| l.addr[2] } # TODO: 0.0.0.0 == any, not localhost. host = listeners.any?{|l| l == '0.0.0.0'} ? 'localhost' : listeners.first say "Launching browser to http://#{host}:#{@port}" system("#{@launch} http://#{host}:#{@port}") end end PK!util/stringio.rbnu[class Gem::StringSink def initialize @string = "" end attr_reader :string def write(s) @string += s s.size end def set_encoding(enc) @string.force_encoding enc end end class Gem::StringSource def initialize(str) @string = str.dup end def read(count=nil) if count @string.slice!(0,count) else s = @string @string = "" s end end alias_method :readpartial, :read end PK!(  psych_additions.rbnu[# This exists just to satify bugs in marshal'd gemspecs that # contain a reference to YAML::PrivateType. We prune these out # in Specification._load, but if we don't have the constant, Marshal # blows up. module Psych # :nodoc: class PrivateType # :nodoc: end end PK!FsF package/tar_test_case.rbnu[require 'rubygems/test_case' require 'rubygems/package' ## # A test case for Gem::Package::Tar* classes class Gem::Package::TarTestCase < Gem::TestCase def ASCIIZ(str, length) str + "\0" * (length - str.length) end def SP(s) s + " " end def SP_Z(s) s + " \0" end def Z(s) s + "\0" end def assert_headers_equal(expected, actual) expected = expected.to_s unless String === expected actual = actual.to_s unless String === actual fields = %w[ name 100 mode 8 uid 8 gid 8 size 12 mtime 12 checksum 8 typeflag 1 linkname 100 magic 6 version 2 uname 32 gname 32 devmajor 8 devminor 8 prefix 155 ] offset = 0 until fields.empty? do name = fields.shift length = fields.shift.to_i if name == "checksum" then chksum_off = offset offset += length next end assert_equal expected[offset, length], actual[offset, length], "Field #{name} of the tar header differs." offset += length end assert_equal expected[chksum_off, 8], actual[chksum_off, 8] end def calc_checksum(header) sum = header.unpack("C*").inject{|s,a| s + a} SP(Z(to_oct(sum, 6))) end def header(type, fname, dname, length, mode, mtime, checksum = nil) checksum ||= " " * 8 arr = [ # struct tarfile_entry_posix ASCIIZ(fname, 100), # char name[100]; ASCII + (Z unless filled) Z(to_oct(mode, 7)), # char mode[8]; 0 padded, octal null Z(to_oct(0, 7)), # char uid[8]; ditto Z(to_oct(0, 7)), # char gid[8]; ditto Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null Z(to_oct(mtime, 11)), # char mtime[12]; 0 padded, octal, null checksum, # char checksum[8]; 0 padded, octal, null, space type, # char typeflag[1]; file: "0" dir: "5" "\0" * 100, # char linkname[100]; ASCII + (Z unless filled) "ustar\0", # char magic[6]; "ustar\0" "00", # char version[2]; "00" ASCIIZ("wheel", 32), # char uname[32]; ASCIIZ ASCIIZ("wheel", 32), # char gname[32]; ASCIIZ Z(to_oct(0, 7)), # char devmajor[8]; 0 padded, octal, null Z(to_oct(0, 7)), # char devminor[8]; 0 padded, octal, null ASCIIZ(dname, 155) # char prefix[155]; ASCII + (Z unless filled) ] format = "C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155" h = if RUBY_VERSION >= "1.9" then arr.join else arr = arr.join("").split(//).map{|x| x[0]} arr.pack format end ret = h + "\0" * (512 - h.size) assert_equal(512, ret.size) ret end def tar_dir_header(name, prefix, mode, mtime) h = header("5", name, prefix, 0, mode, mtime) checksum = calc_checksum(h) header("5", name, prefix, 0, mode, mtime, checksum) end def tar_file_header(fname, dname, mode, length, mtime) h = header("0", fname, dname, length, mode, mtime) checksum = calc_checksum(h) header("0", fname, dname, length, mode, mtime, checksum) end def to_oct(n, pad_size) "%0#{pad_size}o" % n end def util_entry(tar) io = TempIO.new tar header = Gem::Package::TarHeader.from io Gem::Package::TarReader::Entry.new header, io end def util_dir_entry util_entry tar_dir_header("foo", "bar", 0, Time.now) end end PK!)ccmock_gem_ui.rbnu[require 'stringio' require 'rubygems/user_interaction' ## # This Gem::StreamUI subclass records input and output to StringIO for # retrieval during tests. class Gem::MockGemUi < Gem::StreamUI ## # Raised when you haven't provided enough input to your MockGemUi class InputEOFError < RuntimeError def initialize question super "Out of input for MockGemUi on #{question.inspect}" end end class TermError < RuntimeError attr_reader :exit_code def initialize exit_code super @exit_code = exit_code end end class SystemExitException < RuntimeError; end module TTY attr_accessor :tty def tty?() @tty = true unless defined?(@tty) @tty end def noecho yield self end end def initialize(input = "") ins = StringIO.new input outs = StringIO.new errs = StringIO.new ins.extend TTY outs.extend TTY errs.extend TTY super ins, outs, errs, true @terminated = false end def ask question raise InputEOFError, question if @ins.eof? super end def input @ins.string end def output @outs.string end def error @errs.string end def terminated? @terminated end def terminate_interaction(status=0) @terminated = true raise TermError, status if status != 0 raise SystemExitException end end PK!installer_test_case.rbnu[require 'rubygems/test_case' require 'rubygems/installer' class Gem::Installer ## # Available through requiring rubygems/installer_test_case attr_writer :bin_dir ## # Available through requiring rubygems/installer_test_case attr_writer :build_args ## # Available through requiring rubygems/installer_test_case attr_writer :gem_dir ## # Available through requiring rubygems/installer_test_case attr_writer :force ## # Available through requiring rubygems/installer_test_case attr_writer :format ## # Available through requiring rubygems/installer_test_case attr_writer :gem_home ## # Available through requiring rubygems/installer_test_case attr_writer :env_shebang ## # Available through requiring rubygems/installer_test_case attr_writer :ignore_dependencies ## # Available through requiring rubygems/installer_test_case attr_writer :format_executable ## # Available through requiring rubygems/installer_test_case attr_writer :security_policy ## # Available through requiring rubygems/installer_test_case attr_writer :wrappers end ## # A test case for Gem::Installer. class Gem::InstallerTestCase < Gem::TestCase ## # Creates the following instance variables: # # @spec:: # a spec named 'a', intended for regular installs # @user_spec:: # a spec named 'b', intended for user installs # @gem:: # the path to a built gem from @spec # @user_spec:: # the path to a built gem from @user_spec # # @installer:: # a Gem::Installer for the @spec that installs into @gemhome # @user_installer:: # a Gem::Installer for the @user_spec that installs into Gem.user_dir def setup super @spec = quick_gem 'a' do |spec| util_make_exec spec end @user_spec = quick_gem 'b' do |spec| util_make_exec spec end util_build_gem @spec util_build_gem @user_spec @gem = @spec.cache_file @user_gem = @user_spec.cache_file @installer = util_installer @spec, @gemhome @user_installer = util_installer @user_spec, Gem.user_dir, :user Gem::Installer.path_warning = false end def util_gem_bindir spec = @spec # :nodoc: # TODO: deprecate spec.bin_dir end def util_gem_dir spec = @spec # :nodoc: # TODO: deprecate spec.gem_dir end ## # The path where installed executables live def util_inst_bindir File.join @gemhome, "bin" end ## # Adds an executable named "executable" to +spec+ with the given +shebang+. # # The executable is also written to the bin dir in @tmpdir and the installed # gem directory for +spec+. def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby") spec.executables = %w[executable] spec.files << 'bin/executable' exec_path = spec.bin_file "executable" write_file exec_path do |io| io.puts shebang end bin_path = File.join @tempdir, "bin", "executable" write_file bin_path do |io| io.puts shebang end end ## # Builds the @spec gem and returns an installer for it. The built gem # includes: # # bin/executable # lib/code.rb # ext/a/mkrf_conf.rb def util_setup_gem(ui = @ui) # HACK fix use_ui to make this automatic @spec.files << File.join('lib', 'code.rb') @spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb') Dir.chdir @tempdir do FileUtils.mkdir_p 'bin' FileUtils.mkdir_p 'lib' FileUtils.mkdir_p File.join('ext', 'a') File.open File.join('bin', 'executable'), 'w' do |f| f.puts "raise 'ran executable'" end File.open File.join('lib', 'code.rb'), 'w' do |f| f.puts '1' end File.open File.join('ext', 'a', 'mkrf_conf.rb'), 'w' do |f| f << <<-EOF File.open 'Rakefile', 'w' do |rf| rf.puts "task :default" end EOF end use_ui ui do FileUtils.rm_f @gem @gem = Gem::Package.build @spec end end @installer = Gem::Installer.new @gem end ## # Creates an installer for +spec+ that will install into +gem_home+. If # +user+ is true a user-install will be performed. def util_installer(spec, gem_home, user=false) Gem::Installer.new(spec.cache_file, :install_dir => gem_home, :user_install => user) end end PK!ZZ syck_hack.rbnu[# :stopdoc: # Hack to handle syck's DefaultKey bug # # This file is always loaded AFTER either syck or psych are already # loaded. It then looks at what constants are available and creates # a consistent view on all rubys. # # All this is so that there is always a YAML::Syck::DefaultKey # class no matter if the full yaml library has loaded or not. # module YAML # :nodoc: # In newer 1.9.2, there is a Syck toplevel constant instead of it # being underneath YAML. If so, reference it back under YAML as # well. if defined? ::Syck # for tests that change YAML::ENGINE # 1.8 does not support the second argument to const_defined? remove_const :Syck rescue nil Syck = ::Syck # JRuby's "Syck" is called "Yecht" elsif defined? YAML::Yecht Syck = YAML::Yecht # Otherwise, if there is no YAML::Syck, then we've got just psych # loaded, so lets define a stub for DefaultKey. elsif !defined? YAML::Syck module Syck class DefaultKey # :nodoc: end end end # Now that we've got something that is always here, define #to_s # so when code tries to use this, it at least just shows up like it # should. module Syck class DefaultKey remove_method :to_s rescue nil def to_s '=' end end end SyntaxError = Error unless defined? SyntaxError end # Sometime in the 1.9 dev cycle, the Syck constant was moved from under YAML # to be a toplevel constant. So gemspecs created under these versions of Syck # will have references to Syck::DefaultKey. # # So we need to be sure that we reference Syck at the toplevel too so that # we can always load these kind of gemspecs. # if !defined?(Syck) Syck = YAML::Syck end # Now that we've got Syck setup in all the right places, store # a reference to the DefaultKey class inside Gem. We do this so that # if later on YAML, etc are redefined, we've still got a consistent # place to find the DefaultKey class for comparison. module Gem # for tests that change YAML::ENGINE remove_const :SyckDefaultKey if const_defined? :SyckDefaultKey SyckDefaultKey = YAML::Syck::DefaultKey end # :startdoc: PK!Sg]]source_specific_file.rbnu[require 'rubygems/source/specific_file' # TODO warn upon require, this file is deprecated. PK!"oosource_local.rbnu[require 'rubygems/source' require 'rubygems/source_local' # TODO warn upon require, this file is deprecated. PK!`!!test_utilities.rbnu[require 'tempfile' require 'rubygems' require 'rubygems/remote_fetcher' ## # A fake Gem::RemoteFetcher for use in tests or to avoid real live HTTP # requests when testing code that uses RubyGems. # # Example: # # @fetcher = Gem::FakeFetcher.new # @fetcher.data['http://gems.example.com/yaml'] = source_index.to_yaml # Gem::RemoteFetcher.fetcher = @fetcher # # # invoke RubyGems code # # paths = @fetcher.paths # assert_equal 'http://gems.example.com/yaml', paths.shift # assert paths.empty?, paths.join(', ') # # See RubyGems' tests for more examples of FakeFetcher. class Gem::FakeFetcher attr_reader :data attr_reader :last_request attr_reader :api_endpoints attr_accessor :paths def initialize @data = {} @paths = [] @api_endpoints = {} end def api_endpoint(uri) @api_endpoints[uri] || uri end def find_data(path) return File.read path.path if URI === path and 'file' == path.scheme if URI === path and "URI::#{path.scheme.upcase}" != path.class.name then raise ArgumentError, "mismatch for scheme #{path.scheme} and class #{path.class}" end path = path.to_s @paths << path raise ArgumentError, 'need full URI' unless path =~ %r'^https?://' unless @data.key? path then raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path) end @data[path] end def fetch_path path, mtime = nil, head = false data = find_data(path) if data.respond_to?(:call) then data.call else if path.to_s =~ /gz$/ and not data.nil? and not data.empty? then data = Gem.gunzip data end data end end def cache_update_path uri, path = nil, update = true if data = fetch_path(uri) open(path, 'wb') { |io| io.write data } if path and update data else Gem.read_binary(path) if path end end # Thanks, FakeWeb! def open_uri_or_path(path) data = find_data(path) body, code, msg = data response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg) response.instance_variable_set(:@body, body) response.instance_variable_set(:@read, true) response end def request(uri, request_class, last_modified = nil) data = find_data(uri) body, code, msg = data @last_request = request_class.new uri.request_uri yield @last_request if block_given? response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg) response.instance_variable_set(:@body, body) response.instance_variable_set(:@read, true) response end def pretty_print q # :nodoc: q.group 2, '[FakeFetcher', ']' do q.breakable q.text 'URIs:' q.breakable q.pp @data.keys unless @api_endpoints.empty? then q.breakable q.text 'API endpoints:' q.breakable q.pp @api_endpoints.keys end end end def fetch_size(path) path = path.to_s @paths << path raise ArgumentError, 'need full URI' unless path =~ %r'^http://' unless @data.key? path then raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path) end data = @data[path] data.respond_to?(:call) ? data.call : data.length end def download spec, source_uri, install_dir = Gem.dir name = File.basename spec.cache_file path = if Dir.pwd == install_dir then # see fetch_command install_dir else File.join install_dir, "cache" end path = File.join path, name if source_uri =~ /^http/ then File.open(path, "wb") do |f| f.write fetch_path(File.join(source_uri, "gems", name)) end else FileUtils.cp source_uri, path end path end def download_to_cache dependency found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency return if found.empty? spec, source = found.first download spec, source.uri.to_s end end # :stopdoc: class Gem::RemoteFetcher def self.fetcher=(fetcher) @fetcher = fetcher end end # :startdoc: ## # The SpecFetcherSetup allows easy setup of a remote source in RubyGems tests: # # spec_fetcher do |f| # f.gem 'a', 1 # f.spec 'a', 2 # f.gem 'b', 1' 'a' => '~> 1.0' # f.clear # end # # The above declaration creates two gems, a-1 and b-1, with a dependency from # b to a. The declaration creates an additional spec a-2, but no gem for it # (so it cannot be installed). # # After the gems are created they are removed from Gem.dir. class Gem::TestCase::SpecFetcherSetup ## # Executes a SpecFetcher setup block. Yields an instance then creates the # gems and specifications defined in the instance. def self.declare test, repository setup = new test, repository yield setup setup.execute end def initialize test, repository # :nodoc: @test = test @repository = repository @gems = {} @installed = [] @operations = [] end ## # Removes any created gems or specifications from Gem.dir (the default # install location). def clear @operations << [:clear] end ## # Returns a Hash of created Specification full names and the corresponding # Specification. def created_specs created = {} @gems.keys.each do |spec| created[spec.full_name] = spec end created end ## # Creates any defined gems or specifications def execute # :nodoc: execute_operations setup_fetcher created_specs end def execute_operations # :nodoc: @operations.each do |operation, *arguments| case operation when :clear then @test.util_clear_gems @installed.clear when :gem then spec, gem = @test.util_gem(*arguments, &arguments.pop) write_spec spec @gems[spec] = gem @installed << spec when :spec then spec = @test.util_spec(*arguments, &arguments.pop) write_spec spec @gems[spec] = nil @installed << spec end end end ## # Creates a gem with +name+, +version+ and +deps+. The created gem can be # downloaded and installed. # # The specification will be yielded before gem creation for customization, # but only the block or the dependencies may be set, not both. def gem name, version, dependencies = nil, &block @operations << [:gem, name, version, dependencies, block] end ## # Creates a legacy platform spec with the name 'pl' and version 1 def legacy_platform spec 'pl', 1 do |s| s.platform = Gem::Platform.new 'i386-linux' s.instance_variable_set :@original_platform, 'i386-linux' end end def setup_fetcher # :nodoc: require 'zlib' require 'socket' require 'rubygems/remote_fetcher' unless @test.fetcher then @test.fetcher = Gem::FakeFetcher.new Gem::RemoteFetcher.fetcher = @test.fetcher end Gem::Specification.reset begin gem_repo, @test.gem_repo = @test.gem_repo, @repository @test.uri = URI @repository @test.util_setup_spec_fetcher(*@gems.keys) ensure @test.gem_repo = gem_repo @test.uri = URI gem_repo end # This works around util_setup_spec_fetcher adding all created gems to the # installed set. Gem::Specification.reset Gem::Specification.add_specs(*@installed) @gems.each do |spec, gem| next unless gem @test.fetcher.data["#{@repository}gems/#{spec.file_name}"] = Gem.read_binary(gem) FileUtils.cp gem, spec.cache_file end end ## # Creates a spec with +name+, +version+ and +deps+. The created gem can be # downloaded and installed. # # The specification will be yielded before creation for customization, # but only the block or the dependencies may be set, not both. def spec name, version, dependencies = nil, &block @operations << [:spec, name, version, dependencies, block] end def write_spec spec # :nodoc: open spec.spec_file, 'w' do |io| io.write spec.to_ruby_for_cache end end end ## # A StringIO duck-typed class that uses Tempfile instead of String as the # backing store. # # This is available when rubygems/test_utilities is required. #-- # This class was added to flush out problems in Rubinius' IO implementation. class TempIO < Tempfile ## # Creates a new TempIO that will be initialized to contain +string+. def initialize(string = '') super "TempIO" binmode write string rewind end ## # The content of the TempIO as a String. def string flush Gem.read_binary path end end PK!.rwin_platform.rbnu[# frozen_string_literal: true require "rbconfig" module Gem ## # An Array of Regexps that match windows Ruby platforms. WIN_PATTERNS = [ /bccwin/i, /cygwin/i, /djgpp/i, /mingw/i, /mswin/i, /wince/i, ].freeze @@win_platform = nil ## # Is this a windows platform? def self.win_platform? if @@win_platform.nil? ruby_platform = RbConfig::CONFIG["host_os"] @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil? end @@win_platform end end PK!kRFtarget_rbconfig.rbnu[# frozen_string_literal: true require "rbconfig" ## # A TargetConfig is a wrapper around an RbConfig object that provides a # consistent interface for querying configuration for *deployment target # platform*, where the gem being installed is intended to run on. # # The TargetConfig is typically created from the RbConfig of the running Ruby # process, but can also be created from an RbConfig file on disk for cross- # compiling gems. class Gem::TargetRbConfig attr_reader :path def initialize(rbconfig, path) @rbconfig = rbconfig @path = path end ## # Creates a TargetRbConfig for the platform that RubyGems is running on. def self.for_running_ruby new(::RbConfig, nil) end ## # Creates a TargetRbConfig from the RbConfig file at the given path. # Typically used for cross-compiling gems. def self.from_path(rbconfig_path) namespace = Module.new do |m| # Load the rbconfig.rb file within a new anonymous module to avoid # conflicts with the rbconfig for the running platform. Kernel.load rbconfig_path, m end rbconfig = namespace.const_get(:RbConfig) new(rbconfig, rbconfig_path) end ## # Queries the configuration for the given key. def [](key) @rbconfig::CONFIG[key] end end PK!Udd rubygems.rbnu[PK!5{y&-&-rubygems/request_set.rbnu[PK!}33 rubygems/version.rbnu[PK!t BB:rubygems/text.rbnu[PK!Z_ $rubygems/request/connection_pools.rbnu[PK!fޟ rubygems/request/https_pool.rbnu[PK! @ rubygems/request/http_pool.rbnu[PK!*g!g!+rubygems/platform.rbnu[PK!8p!!2rubygems/query_utils.rbnu[PK!BUrubygems/errors.rbnu[PK!(ղ,,ugrubygems/gemcutter_utilities.rbnu[PK!<$'' vrubygems/dependency_installer.rbnu[PK!tOQQrubygems/vendored_molinillo.rbnu[PK!ђSÛ)rubygems/unknown_command_spell_checker.rbnu[PK!^ 1QKQK rubygems/package.rbnu[PK!]Y(BUrubygems/version_option.rbnu[PK!U''?^rubygems/deprecate.rbnu[PK!ݯgrrubygems/security/signer.rbnu[PK!@D rubygems/security/trust_dir.rbnu[PK!T11rubygems/security/policy.rbnu[PK!ҽ? ? }rubygems/security/policies.rbnu[PK!֖i= rubygems/validator.rbnu[PK!ǀrubygems/ext.rbnu[PK!|+44rubygems/dependency_list.rbnu[PK!Brubygems/spec_fetcher.rbnu[PK!-horubygems/ci_detector.rbnu[PK!rubygems/resolver/lock_set.rbnu[PK!h  '{rubygems/resolver/dependency_request.rbnu[PK!3=##( rubygems/resolver/local_specification.rbnu[PK! &O$rubygems/resolver/api_specification.rbnu[PK!Kj/rubygems/resolver/stats.rbnu[PK!Mr3rubygems/resolver/set.rbnu[PK!w w (8rubygems/resolver/index_specification.rbnu[PK!:ĨBrubygems/resolver/index_set.rbnu[PK!1۔'Hrubygems/resolver/api_set/gem_parser.rbnu[PK!Ϙ 'Krubygems/resolver/activation_request.rbnu[PK!NBB'Wrubygems/resolver/lock_specification.rbnu[PK!*i _rubygems/resolver/git_set.rbnu[PK!NY'ܳ![krubygems/resolver/composed_set.rbnu[PK!){Y%_prubygems/resolver/best_set.rbnu[PK!^&Ztrubygems/resolver/git_specification.rbnu[PK!޻ڢ yrubygems/resolver/api_set.rbnu[PK! @rubygems/resolver/source_set.rbnu[PK!͞AA)Չrubygems/resolver/vendor_specification.rbnu[PK!qXX%orubygems/resolver/requirement_list.rbnu[PK!֚rubygems/resolver/vendor_set.rbnu[PK!Vå "rubygems/resolver/specification.rbnu[PK!eh' rubygems/resolver/spec_specification.rbnu[PK!Qh9,rubygems/resolver/installed_specification.rbnu[PK!]Z^^";rubygems/resolver/installer_set.rbnu[PK!Nh rubygems/resolver/conflict.rbnu[PK!F0T rubygems/resolver/current_set.rbnu[PK!p)%erubygems/defaults/operating_system.rbnu[PK![Wrubygems/ext/build_error.rbnu[PK!ԅVrubygems/ext/cmake_builder.rbnu[PK!v''rubygems/ext/cargo_builder.rbnu[PK!_j rubygems/ext/rake_builder.rbnu[PK!>1rubygems/ext/cargo_builder/link_flag_converter.rbnu[PK!eLrubygems/ext/builder.rbnu[PK! KM77!w8rubygems/ext/configure_builder.rbnu[PK!z :rubygems/ext/ext_conf_builder.rbnu[PK! 0 1Crubygems/gemcutter_utilities/webauthn_listener.rbnu[PK!L] /YPrubygems/gemcutter_utilities/webauthn_poller.rbnu[PK!I :K[rubygems/gemcutter_utilities/webauthn_listener/response.rbnu[PK!U౤#Nirubygems/commands/unpack_command.rbnu[PK!R"E{rubygems/commands/stale_command.rbnu[PK!X9ll&\rubygems/commands/uninstall_command.rbnu[PK! !rubygems/commands/rdoc_command.rbnu[PK!]h&< "@rubygems/commands/build_command.rbnu[PK!D^dl "Jrubygems/commands/fetch_command.rbnu[PK!&&$:rubygems/commands/install_command.rbnu[PK!w=FF%rubygems/commands/contents_command.rbnu[PK!}$Orubygems/commands/cleanup_command.rbnu[PK!L$$!rubygems/commands/cert_command.rbnu[PK!v((!rubygems/commands/help_command.rbnu[PK! 6}VV'Erubygems/commands/dependency_command.rbnu[PK!*d%/Zrubygems/commands/pristine_command.rbnu[PK!V[(Kurubygems/commands/environment_command.rbnu[PK!LTb#rubygems/commands/signin_command.rbnu[PK!9:4"\rubygems/commands/query_command.rbnu[PK!gpY *rubygems/commands/specification_command.rbnu[PK!Dp p "rubygems/commands/owner_command.rbnu[PK!ZAQ #{rubygems/commands/search_command.rbnu[PK!])[["rubygems/commands/which_command.rbnu[PK!ؿd d !=rubygems/commands/yank_command.rbnu[PK!Q BB!rubygems/commands/info_command.rbnu[PK!A !rubygems/commands/lock_command.rbnu[PK! U1$rubygems/commands/signout_command.rbnu[PK!L!nrubygems/commands/list_command.rbnu[PK!d+rubygems/commands/generate_index_command.rbnu[PK!ss#rubygems/commands/mirror_command.rbnu[PK!V- - "rubygems/commands/check_command.rbnu[PK!4``%-rubygems/commands/outdated_command.rbnu[PK!ʉ\> rubygems/specification_policy.rbnu[PK!X00)frubygems/safe_marshal/visitors/visitor.rbnu[PK!Im0rubygems/safe_marshal/visitors/stream_printer.rbnu[PK!G#,#,)rubygems/safe_marshal/visitors/to_ruby.rbnu[PK!1/ ""rubygems/safe_marshal/reader.rbnu[PK!HJ8 !rubygems/safe_marshal/elements.rbnu[PK!?%rubygems/gemspec_helpers.rbnu[PK! 7!++'rubygems/uninstaller.rbnu[PK!VSrubygems/uri_formatter.rbnu[PK!T$Vrubygems/package/tar_reader/entry.rbnu[PK!|@hrubygems/package/tar_reader.rbnu[PK!prubygems/package/tar_writer.rbnu[PK!^x{HHErubygems/package/source.rbnu[PK!bb׏rubygems/package/file_source.rbnu[PK!*Csrubygems/package/tar_header.rbnu[PK!ϫrubygems/package/old.rbnu[PK!䌸..rubygems/package/io_source.rbnu[PK!c_MMrubygems/package/digest_io.rbnu[PK!I7bbrubygems/defaults.rbnu[PK!$_l  Prubygems/request_set/lockfile.rbnu[PK!_cVV*rubygems/request_set/gem_dependency_api.rbnu[PK!D3 *M rubygems/request_set/lockfile/tokenizer.rbnu[PK!/W2'/Y rubygems/request_set/lockfile/parser.rbnu[PK!J'""~v rubygems/source.rbnu[PK!:>  rubygems/source_list.rbnu[PK!vۗ rubygems/vendored_timeout.rbnu[PK!>T%% rubygems/resolver.rbnu[PK!Fߺ̎# rubygems/core_ext/tcpsocket_init.rbnu[PK!SS# rubygems/core_ext/kernel_require.rbnu[PK!i rubygems/core_ext/kernel_gem.rbnu[PK!WW  rubygems/core_ext/kernel_warn.rbnu[PK!z   O rubygems/source/specific_file.rbnu[PK!''rr rubygems/source/git.rbnu[PK!¨y-  b rubygems/source/local.rbnu[PK!_t'+ rubygems/source/vendor.rbnu[PK!Gc rubygems/source/installed.rbnu[PK!_N rubygems/source/lock.rbnu[PK!ڄ(?(? rubygems/config_file.rbnu[PK!Qe%%T rubygems/remote_fetcher.rbnu[PK!j;y rubygems/requirement.rbnu[PK!j rubygems/rdoc.rbnu[PK!C  ? rubygems/available_set.rbnu[PK!GyII rubygems/vendored_tsort.rbnu[PK!r  rubygems/yaml_serializer.rbnu[PK!б rubygems/safe_marshal.rbnu[PK!{' rubygems/installer_uninstaller_utils.rbnu[PK!jJQQf rubygems/exceptions.rbnu[PK!Znn rubygems/installer.rbnu[PK!{]]#E rubygems/install_default_message.rbnu[PK!v4kVVG rubygems/security.rbnu[PK!H rubygems/stub_specification.rbnu[PK!!!۲ rubygems/vendored_net_http.rbnu[PK!z!ee rubygems/specification.rbnu[PK!Duq rubygems/psych_tree.rbnu[PK!(++ rubygems/safe_yaml.rbnu[PK!': rubygems/compatibility.rbnu[PK!`  rubygems/name_tuple.rbnu[PK!^i&?? rubygems/command.rbnu[PK!#Y rubygems/s3_uri_signer.rbnu[PK!~O&6 rubygems/vendor/timeout/lib/timeout.rbnu[PK!L;;,^N rubygems/vendor/optparse/lib/optionparser.rbnu[PK!#d0N rubygems/vendor/optparse/lib/optparse/version.rbnu[PK!ý''/lW rubygems/vendor/optparse/lib/optparse/kwargs.rbnu[PK!lV,Y rubygems/vendor/optparse/lib/optparse/uri.rbnu[PK!"j3[ rubygems/vendor/optparse/lib/optparse/shellwords.rbnu[PK!O-\ rubygems/vendor/optparse/lib/optparse/date.rbnu[PK!.U-] rubygems/vendor/optparse/lib/optparse/time.rbnu[PK!X$$+A_ rubygems/vendor/optparse/lib/optparse/ac.rbnu[PK!gydd(e rubygems/vendor/optparse/lib/optparse.rbnu[PK!_k#"")|_rubygems/vendor/net-http/lib/net/https.rbnu[PK!$SS(arubygems/vendor/net-http/lib/net/http.rbnu[PK!  0rubygems/vendor/net-http/lib/net/http/request.rbnu[PK!?sDII1frubygems/vendor/net-http/lib/net/http/backward.rbnu[PK!M?i4rubygems/vendor/net-http/lib/net/http/proxy_delta.rbnu[PK!!.1.18rubygems/vendor/net-http/lib/net/http/generic_request.rbnu[PK!ebo7o71rubygems/vendor/net-http/lib/net/http/requests.rbnu[PK!`9d /1rubygems/vendor/net-http/lib/net/http/status.rbnu[PK! TT3W;rubygems/vendor/net-http/lib/net/http/exceptions.rbnu[PK!qt!a/?rubygems/vendor/net-http/lib/net/http/header.rbnu[PK!sNtt2`rubygems/vendor/net-http/lib/net/http/responses.rbnu[PK!T,u`NN16arubygems/vendor/net-http/lib/net/http/response.rbnu[PK!,QQ*{rubygems/vendor/molinillo/lib/molinillo.rbnu[PK!ww7&rubygems/vendor/molinillo/lib/molinillo/gem_metadata.rbnu[PK!?pp1rubygems/vendor/molinillo/lib/molinillo/errors.rbnu[PK!N讟?rubygems/vendor/molinillo/lib/molinillo/dependency_graph/tag.rbnu[PK!19DDBrubygems/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rbnu[PK!?__Frubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rbnu[PK!J NBnrubygems/vendor/molinillo/lib/molinillo/dependency_graph/action.rbnu[PK!8Oxrubygems/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rbnu[PK!U=Grubygems/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rbnu[PK!N kkPrubygems/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rbnu[PK!fg?qrubygems/vendor/molinillo/lib/molinillo/dependency_graph/log.rbnu[PK!]SSGrubygems/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rbnu[PK! 5b b Krubygems/vendor/molinillo/lib/molinillo/delegates/specification_provider.rbnu[PK!#:1VE$rubygems/vendor/molinillo/lib/molinillo/delegates/resolution_state.rbnu[PK!,3,rubygems/vendor/molinillo/lib/molinillo/resolver.rbnu[PK!Eã52rubygems/vendor/molinillo/lib/molinillo/modules/ui.rbnu[PK!օSSI9rubygems/vendor/molinillo/lib/molinillo/modules/specification_provider.rbnu[PK!JT܂5Jrubygems/vendor/molinillo/lib/molinillo/resolution.rbnu[PK!$$0krubygems/vendor/molinillo/lib/molinillo/state.rbnu[PK!~R ;rubygems/vendor/molinillo/lib/molinillo/dependency_graph.rbnu[PK!^ژY.Y.0rubygems/vendor/net-protocol/lib/net/protocol.rbnu[PK!"b  (rubygems/vendor/uri/lib/uri.rbnu[PK!)vn& 6rubygems/vendor/uri/lib/uri/version.rbnu[PK! BB$6rubygems/vendor/uri/lib/uri/https.rbnu[PK!ްlbEE-9rubygems/vendor/uri/lib/uri/rfc2396_parser.rbnu[PK!_$~rubygems/vendor/uri/lib/uri/ldaps.rbnu[PK! e-Yrubygems/vendor/uri/lib/uri/rfc3986_parser.rbnu[PK! %rubygems/vendor/uri/lib/uri/mailto.rbnu[PK!KT#ĵrubygems/vendor/uri/lib/uri/http.rbnu[PK!R""rubygems/vendor/uri/lib/uri/ftp.rbnu[PK!r ; ; !rubygems/vendor/uri/lib/uri/ws.rbnu[PK!p;vgg%rubygems/vendor/uri/lib/uri/common.rbnu[PK!H)<''"Srubygems/vendor/uri/lib/uri/wss.rbnu[PK!~Cf` ` #Vrubygems/vendor/uri/lib/uri/file.rbnu[PK!Pyjj#_rubygems/vendor/uri/lib/uri/ldap.rbnu[PK!AhS&wrubygems/vendor/uri/lib/uri/generic.rbnu[PK!]ZZ$rubygems/vendor/resolv/lib/resolv.rbnu[PK!.V : :"lrubygems/vendor/tsort/lib/tsort.rbnu[PK!224rubygems/vendor/securerandom/lib/random/formatter.rbnu[PK!Fhs0rubygems/vendor/securerandom/lib/securerandom.rbnu[PK!2<<rubygems/security_option.rbnu[PK!O!!xrubygems/dependency.rbnu[PK!]B#  rubygems/specification_record.rbnu[PK! oo!rubygems/vendored_securerandom.rbnu[PK!ai4i4nrubygems/user_interaction.rbnu[PK!,8e"#Trubygems/bundler_version_finder.rbnu[PK!IR - -N\request_set.rbnu[PK!ov66 version.rbnu[PK!wDD~text.rbnu[PK!`W request/connection_pools.rbnu[PK!fޟ request/https_pool.rbnu[PK!R~~<request/http_pool.rbnu[PK!&k22 platform.rbnu[PK!6 !!I query_utils.rbnu[PK!bi -errors.rbnu[PK!8'--?gemcutter_utilities.rbnu[PK!Ղ &&mdependency_installer.rbnu[PK!"2.command_manager.rbnu[PK!(CCinstall_message.rbnu[PK!Vbnn igem_runner.rbnu[PK!path_support.rbnu[PK!mo"" krequest.rbnu[PK!">OQQvendored_molinillo.rbnu[PK!ђSÛ *unknown_command_spell_checker.rbnu[PK!HNN package.rbnu[PK!]Y(U)version_option.rbnu[PK!= I2deprecate.rbnu[PK!<Gsecurity/signer.rbnu[PK!@D _security/trust_dir.rbnu[PK!Qhz==isecurity/policy.rbnu[PK!ҽ? ? Lsecurity/policies.rbnu[PK!('}݇ ϕvalidator.rbnu[PK!ǀext.rbnu[PK!aGudependency_list.rbnu[PK!Uspec_fetcher.rbnu[PK!-hci_detector.rbnu[PK!/resolver/lock_set.rbnu[PK!h  resolver/dependency_request.rbnu[PK!3=##bresolver/local_specification.rbnu[PK!v_ resolver/api_specification.rbnu[PK!Kj resolver/stats.rbnu[PK!Mrresolver/set.rbnu[PK!w w 7resolver/index_specification.rbnu[PK!"~resolver/index_set.rbnu[PK!A="resolver/api_set/gem_parser.rbnu[PK!Ϙ &resolver/activation_request.rbnu[PK!NBB1resolver/lock_specification.rbnu[PK!jaMG^ ^ y9resolver/git_set.rbnu[PK!Eresolver/composed_set.rbnu[PK!_resolver/source_set.rbnu[PK!͞AA cresolver/vendor_specification.rbnu[PK!qXX#fresolver/requirement_list.rbnu[PK!֚kresolver/vendor_set.rbnu[PK!Vå sresolver/specification.rbnu[PK!eh~resolver/spec_specification.rbnu[PK!Qh9#resolver/installed_specification.rbnu[PK!]Z^^‰resolver/installer_set.rbnu[PK!(ext/cargo_builder/link_flag_converter.rbnu[PK!+Ѻqq;ext/builder.rbnu[PK!*))!ext/configure_builder.rbnu[PK!V& & [%ext/ext_conf_builder.rbnu[PK!b (0gemcutter_utilities/webauthn_listener.rbnu[PK!L] &>gemcutter_utilities/webauthn_poller.rbnu[PK!I 1Hgemcutter_utilities/webauthn_listener/response.rbnu[PK!NXVcommands/unpack_command.rbnu[PK!Rgcommands/stale_command.rbnu[PK!X9llkcommands/uninstall_command.rbnu[PK!ߘ|commands/rdoc_command.rbnu[PK!̅NV V commands/build_command.rbnu[PK!D^dl ;commands/fetch_command.rbnu[PK!~"commands/install_command.rbnu[PK!K=@BB(commands/contents_command.rbnu[PK!Xi^commands/cleanup_command.rbnu[PK!|TL$$commands/cert_command.rbnu[PK!v((commands/help_command.rbnu[PK! 6}VV/commands/dependency_command.rbnu[PK!smDcommands/pristine_command.rbnu[PK!U+`commands/environment_command.rbnu[PK!LTbvcommands/signin_command.rbnu[PK!##zcommands/query_command.rbnu[PK!gpY !commands/specification_command.rbnu[PK!]g commands/owner_command.rbnu[PK!ZAQ commands/search_command.rbnu[PK!])[[commands/which_command.rbnu[PK!ؿd d commands/yank_command.rbnu[PK!Q BBVcommands/info_command.rbnu[PK!A commands/lock_command.rbnu[PK! U1commands/signout_command.rbnu[PK!Lcommands/list_command.rbnu[PK!d"commands/generate_index_command.rbnu[PK!sscommands/mirror_command.rbnu[PK!V- - commands/check_command.rbnu[PK!4``Rcommands/outdated_command.rbnu[PK! commands/rebuild_command.rbnu[PK! [commands/exec_command.rbnu[PK!GGr7commands/setup_command.rbnu[PK!commands/server_command.rbnu[PK!r2!2!commands/update_command.rbnu[PK!NBk  commands/push_command.rbnu[PK!bF葝Icommands/open_command.rbnu[PK!W9P'P'.commands/sources_command.rbnu[PK!JNBg uri.rbnu[PK![(" doctor.rbnu[PK!;}} >openssl.rbnu[PK!9BB shellwords.rbnu[PK!Fj''tpackage_task.rbnu[PK!7XOOvendored_optparse.rbnu[PK![!lkkmupdate_suggestion.rbnu[PK!<$Jinstall_update_options.rbnu[PK!p߅559,util.rbnu[PK!/ 4basic_specification.rbnu[PK!f Uutil/list.rbnu[PK!͵`==Xutil/licenses.rbnu[PK!jK[[)local_remote_options.rbnu[PK!::ˤspecification_policy.rbnu[PK!X00 safe_marshal/visitors/visitor.rbnu[PK!Im'!safe_marshal/visitors/stream_printer.rbnu[PK!!source/lock.rbnu[PK!N:A:AwB!config_file.rbnu[PK!&&!remote_fetcher.rbnu[PK!s!,ނE!requirement.rbnu[PK!""5!rdoc.rbnu[PK!C  !available_set.rbnu[PK!GyII!vendored_tsort.rbnu[PK!r !yaml_serializer.rbnu[PK!б!safe_marshal.rbnu[PK!{y!installer_uninstaller_utils.rbnu[PK!ߚww !exceptions.rbnu[PK!/aslsl ~"installer.rbnu[PK!O33-t"install_default_message.rbnu[PK!v4kVV u"security.rbnu[PK!"stub_specification.rbnu[PK!!!"vendored_net_http.rbnu[PK!"specification.rbnu[PK!7jj |#psych_tree.rbnu[PK!(++ ##safe_yaml.rbnu[PK!巹#compatibility.rbnu[PK!,4  K#name_tuple.rbnu[PK!S7?7?  $command.rbnu[PK!`.R I$s3_uri_signer.rbnu[PK!f$vendor/timeout/lib/timeout.rbnu[PK!L;;#+$vendor/optparse/lib/optionparser.rbnu[PK!S?'$vendor/optparse/lib/optparse/version.rbnu[PK!7&$vendor/optparse/lib/optparse/kwargs.rbnu[PK!lV#ϋ$vendor/optparse/lib/optparse/uri.rbnu[PK!"j*ڌ$vendor/optparse/lib/optparse/shellwords.rbnu[PK!O$ݍ$vendor/optparse/lib/optparse/date.rbnu[PK!.U$$vendor/optparse/lib/optparse/time.rbnu[PK!ua"$vendor/optparse/lib/optparse/ac.rbnu[PK!B Y??H$vendor/optparse/lib/optparse.rbnu[PK!_k#"" ֡%vendor/net-http/lib/net/https.rbnu[PK!<)vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rbnu[PK!U=>D)vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rbnu[PK!N kkGL)vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rbnu[PK!fg6S)vendor/molinillo/lib/molinillo/dependency_graph/log.rbnu[PK!]SS>b)vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rbnu[PK! 5b b BAg)vendor/molinillo/lib/molinillo/delegates/specification_provider.rbnu[PK!#:1V<t)vendor/molinillo/lib/molinillo/delegates/resolution_state.rbnu[PK!,*x{)vendor/molinillo/lib/molinillo/resolver.rbnu[PK!Eã,)vendor/molinillo/lib/molinillo/modules/ui.rbnu[PK!օSS@)vendor/molinillo/lib/molinillo/modules/specification_provider.rbnu[PK!JT܂,ә)vendor/molinillo/lib/molinillo/resolution.rbnu[PK!$$' *vendor/molinillo/lib/molinillo/state.rbnu[PK!~R 2,(*vendor/molinillo/lib/molinillo/dependency_graph.rbnu[PK!^ژY.Y.'7I*vendor/net-protocol/lib/net/protocol.rbnu[PK!k & & w*vendor/uri/lib/uri.rbnu[PK!r>cR*vendor/uri/lib/uri/version.rbnu[PK! BB4*vendor/uri/lib/uri/https.rbnu[PK!HxMFMF$*vendor/uri/lib/uri/rfc2396_parser.rbnu[PK!_b*vendor/uri/lib/uri/ldaps.rbnu[PK!lqq$*vendor/uri/lib/uri/rfc3986_parser.rbnu[PK! *vendor/uri/lib/uri/mailto.rbnu[PK!h +vendor/uri/lib/uri/http.rbnu[PK!u2+vendor/uri/lib/uri/ftp.rbnu[PK!r ; ; 7+vendor/uri/lib/uri/ws.rbnu[PK!zcrprpzA+vendor/uri/lib/uri/common.rbnu[PK!H)<''8+vendor/uri/lib/uri/wss.rbnu[PK!z!l l +vendor/uri/lib/uri/file.rbnu[PK!Pyjj^+vendor/uri/lib/uri/ldap.rbnu[PK!' יי+vendor/uri/lib/uri/generic.rbnu[PK!Ncc6p,vendor/resolv/lib/resolv.rbnu[PK!.V : :-vendor/tsort/lib/tsort.rbnu[PK!22+Y.vendor/securerandom/lib/random/formatter.rbnu[PK!7OV V 'WA.vendor/securerandom/lib/securerandom.rbnu[PK!2<<K.security_option.rbnu[PK!ro*!! O.dependency.rbnu[PK!х8zzq.specification_record.rbnu[PK!,7WWP.vendored_securerandom.rbnu[PK!-A4A4.user_interaction.rbnu[PK!z، s.bundler_version_finder.rbnu[PK!]H.ssl_certs/GlobalSignRootCA.pemnu[PK!44 .indexer.rbnu[PK!.O /test_case.rbnu[PK!Ԑ [[ /server.rbnu[PK!/util/stringio.rbnu[PK!(  /psych_additions.rbnu[PK!FsF F/package/tar_test_case.rbnu[PK!)ccx0mock_gem_ui.rbnu[PK!0installer_test_case.rbnu[PK!ZZ N0syck_hack.rbnu[PK!Sg]]'0source_specific_file.rbnu[PK!"oo(0source_local.rbnu[PK!`!!6)0test_utilities.rbnu[PK!.r7K0win_platform.rbnu[PK!kRF}M0target_rbconfig.rbnu[PK R0