diff options
Diffstat (limited to 'lib/rubygems/config_file.rb')
-rw-r--r-- | lib/rubygems/config_file.rb | 161 |
1 files changed, 133 insertions, 28 deletions
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index c53e209ae8..7874ad0dc9 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. @@ -46,6 +47,8 @@ class Gem::ConfigFile 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 @@ -142,6 +145,11 @@ class Gem::ConfigFile attr_accessor :cert_expiration_length_days ## + # Install extensions into lib as well as into the extension directory. + + attr_accessor :install_extension_in_lib + + ## # == Experimental == # Fallback to IPv4 when IPv6 is not reachable or slow (default: false) @@ -182,15 +190,16 @@ class Gem::ConfigFile @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.dup.tap(&Gem::UNTAINT) + user_config = load_file config_file_name - environment_config = (ENV["GEMRC"] || "") - .split(File::PATH_SEPARATOR).inject({}) do |result, file| + environment_config = (ENV["GEMRC"] || ""). + split(File::PATH_SEPARATOR).inject({}) do |result, file| result.merge load_file file end @@ -201,21 +210,34 @@ class Gem::ConfigFile @hash = @hash.merge environment_config end - # HACK these override command-line args, which is bad + @hash.transform_keys! do |k| + # gemhome and gempath are not working with symbol keys + if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days + install_extension_in_lib ipv4_fallback_enabled sources disable_default_gem_server + ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k) + k.to_sym + else + k + end + end + + # HACK: these override command-line args, which is bad @backtrace = @hash[:backtrace] if @hash.key? :backtrace @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold - @home = @hash[:gemhome] if @hash.key? :gemhome - @path = @hash[:gempath] if @hash.key? :gempath - @update_sources = @hash[:update_sources] if @hash.key? :update_sources @verbose = @hash[:verbose] if @hash.key? :verbose - @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server - @sources = @hash[:sources] if @hash.key? :sources + @update_sources = @hash[:update_sources] if @hash.key? :update_sources + # TODO: We should handle concurrent_downloads same as other options @cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days + @install_extension_in_lib = @hash[:install_extension_in_lib] if @hash.key? :install_extension_in_lib @ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled - @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode - @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert - @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert + @home = @hash[:gemhome] if @hash.key? :gemhome + @path = @hash[:gempath] if @hash.key? :gempath + @sources = @hash[:sources] if @hash.key? :sources + @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server + @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode + @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert + @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert @api_keys = nil @rubygems_api_key = nil @@ -240,9 +262,9 @@ class Gem::ConfigFile return if Gem.win_platform? # windows doesn't write 0600 as 0600 return unless File.exist? credentials_path - existing_permissions = File.stat(credentials_path).mode & 0777 + existing_permissions = File.stat(credentials_path).mode & 0o777 - return if existing_permissions == 0600 + return if existing_permissions == 0o600 alert_error <<-ERROR Your gem push credentials file located at: @@ -323,11 +345,9 @@ if you believe they were disclosed to a third party. require "fileutils" FileUtils.mkdir_p(dirname) - Gem.load_yaml - - permissions = 0600 & (~File.umask) + permissions = 0o600 & (~File.umask) File.open(credentials_path, "w", permissions) do |f| - f.write config.to_yaml + f.write self.class.dump_with_rubygems_yaml(config) end load_api_keys # reload @@ -343,20 +363,18 @@ if you believe they were disclosed to a third party. end def load_file(filename) - Gem.load_yaml - yaml_errors = [ArgumentError] - yaml_errors << Psych::SyntaxError if defined?(Psych::SyntaxError) return {} unless filename && !filename.empty? && File.exist?(filename) begin - content = Gem::SafeYAML.load(File.read(filename)) - unless content.kind_of? Hash + config = self.class.load_with_rubygems_config_hash(File.read(filename)) + if config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") } warn "Failed to load #{filename} because it doesn't contain valid YAML hash" return {} + else + return config end - return content rescue *yaml_errors => e warn "Failed to load #{filename}, #{e}" rescue Errno::EACCES @@ -371,11 +389,44 @@ if you believe they were disclosed to a third party. @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 @@ -434,6 +485,9 @@ if you believe they were disclosed to a third party. yaml_hash[:concurrent_downloads] = @hash.fetch(:concurrent_downloads, DEFAULT_CONCURRENT_DOWNLOADS) + yaml_hash[:install_extension_in_lib] = + @hash.fetch(:install_extension_in_lib, DEFAULT_INSTALL_EXTENSION_IN_LIB) + yaml_hash[:ssl_verify_mode] = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode @@ -443,17 +497,17 @@ if you believe they were disclosed to a third party. yaml_hash[:ssl_client_cert] = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert - keys = yaml_hash.keys.map {|key| key.to_s } + keys = yaml_hash.keys.map(&:to_s) keys << "debug" re = Regexp.union(*keys) @hash.each do |key, value| key = key.to_s - next if key =~ re + next if key&.match?(re) yaml_hash[key.to_s] = value end - yaml_hash.to_yaml + self.class.dump_with_rubygems_yaml(yaml_hash) end # Writes out this config file, replacing its source. @@ -488,6 +542,57 @@ if you believe they were disclosed to a third party. attr_reader :hash protected :hash + def self.dump_with_rubygems_yaml(content) + content.transform_keys! do |k| + k.is_a?(Symbol) ? ":#{k}" : k + end + + require_relative "yaml_serializer" + Gem::YAMLSerializer.dump(content) + end + + def self.load_with_rubygems_config_hash(yaml) + require_relative "yaml_serializer" + + content = Gem::YAMLSerializer.load(yaml) + + content.transform_keys! do |k| + if k.match?(/\A:(.*)\Z/) + k[1..-1].to_sym + elsif k.include?("__") || k.match?(%r{/\Z}) + if k.is_a?(Symbol) + k.to_s.gsub(/__/,".").gsub(%r{/\Z}, "").to_sym + else + k.dup.gsub(/__/,".").gsub(%r{/\Z}, "") + end + else + k + end + end + + content.transform_values! do |v| + if v.is_a?(String) + if v.match?(/\A:(.*)\Z/) + v[1..-1].to_sym + elsif v.match?(/\A[+-]?\d+\Z/) + v.to_i + elsif v.match?(/\Atrue|false\Z/) + v == "true" + elsif v.empty? + nil + else + v + end + elsif v.is_a?(Hash) && v.empty? + nil + else + v + end + end + + content + end + private def set_config_file_name(args) @@ -500,7 +605,7 @@ if you believe they were disclosed to a third party. need_config_file_name = false elsif arg =~ /^--config-file=(.*)/ @config_file_name = $1 - elsif arg =~ /^--config-file$/ + elsif /^--config-file$/.match?(arg) need_config_file_name = true end end |