1#-- 2# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. 3# All rights reserved. 4# See LICENSE.txt for permissions. 5#++ 6 7require 'rubygems/user_interaction' 8require 'rbconfig' 9 10## 11# Gem::ConfigFile RubyGems options and gem command options from gemrc. 12# 13# gemrc is a YAML file that uses strings to match gem command arguments and 14# symbols to match RubyGems options. 15# 16# Gem command arguments use a String key that matches the command name and 17# allow you to specify default arguments: 18# 19# install: --no-rdoc --no-ri 20# update: --no-rdoc --no-ri 21# 22# You can use <tt>gem:</tt> to set default arguments for all commands. 23# 24# RubyGems options use symbol keys. Valid options are: 25# 26# +:backtrace+:: See #backtrace 27# +:sources+:: Sets Gem::sources 28# +:verbose+:: See #verbose 29# 30# gemrc files may exist in various locations and are read and merged in 31# the following order: 32# 33# - system wide (/etc/gemrc) 34# - per user (~/.gemrc) 35# - per environment (gemrc files listed in the GEMRC environment variable) 36 37class Gem::ConfigFile 38 39 include Gem::UserInteraction 40 41 DEFAULT_BACKTRACE = false 42 DEFAULT_BULK_THRESHOLD = 1000 43 DEFAULT_VERBOSITY = true 44 DEFAULT_UPDATE_SOURCES = true 45 46 ## 47 # For Ruby packagers to set configuration defaults. Set in 48 # rubygems/defaults/operating_system.rb 49 50 OPERATING_SYSTEM_DEFAULTS = {} 51 52 ## 53 # For Ruby implementers to set configuration defaults. Set in 54 # rubygems/defaults/#{RUBY_ENGINE}.rb 55 56 PLATFORM_DEFAULTS = {} 57 58 # :stopdoc: 59 60 system_config_path = 61 begin 62 require "etc" 63 Etc.sysconfdir 64 rescue LoadError, NoMethodError 65 begin 66 # TODO: remove after we drop 1.8.7 and 1.9.1 67 require 'Win32API' 68 69 CSIDL_COMMON_APPDATA = 0x0023 70 path = 0.chr * 260 71 if RUBY_VERSION > '1.9' then 72 SHGetFolderPath = Win32API.new 'shell32', 'SHGetFolderPath', 'PLPLP', 73 'L', :stdcall 74 SHGetFolderPath.call nil, CSIDL_COMMON_APPDATA, nil, 1, path 75 else 76 SHGetFolderPath = Win32API.new 'shell32', 'SHGetFolderPath', 'LLLLP', 77 'L' 78 SHGetFolderPath.call 0, CSIDL_COMMON_APPDATA, 0, 1, path 79 end 80 81 path.strip 82 rescue LoadError 83 RbConfig::CONFIG["sysconfdir"] || "/etc" 84 end 85 end 86 87 # :startdoc: 88 89 SYSTEM_WIDE_CONFIG_FILE = File.join system_config_path, 'gemrc' 90 91 ## 92 # List of arguments supplied to the config file object. 93 94 attr_reader :args 95 96 ## 97 # Where to look for gems (deprecated) 98 99 attr_accessor :path 100 101 ## 102 # Where to install gems (deprecated) 103 104 attr_accessor :home 105 106 ## 107 # True if we print backtraces on errors. 108 109 attr_writer :backtrace 110 111 ## 112 # Bulk threshold value. If the number of missing gems are above this 113 # threshold value, then a bulk download technique is used. (deprecated) 114 115 attr_accessor :bulk_threshold 116 117 ## 118 # Verbose level of output: 119 # * false -- No output 120 # * true -- Normal output 121 # * :loud -- Extra output 122 123 attr_accessor :verbose 124 125 ## 126 # True if we want to update the SourceInfoCache every time, false otherwise 127 128 attr_accessor :update_sources 129 130 ## 131 # True if we want to force specification of gem server when pushing a gem 132 133 attr_accessor :disable_default_gem_server 134 135 # openssl verify mode value, used for remote https connection 136 137 attr_reader :ssl_verify_mode 138 139 ## 140 # Path name of directory or file of openssl CA certificate, used for remote https connection 141 142 attr_reader :ssl_ca_cert 143 144 ## 145 # Create the config file object. +args+ is the list of arguments 146 # from the command line. 147 # 148 # The following command line options are handled early here rather 149 # than later at the time most command options are processed. 150 # 151 # <tt>--config-file</tt>, <tt>--config-file==NAME</tt>:: 152 # Obviously these need to be handled by the ConfigFile object to ensure we 153 # get the right config file. 154 # 155 # <tt>--backtrace</tt>:: 156 # Backtrace needs to be turned on early so that errors before normal 157 # option parsing can be properly handled. 158 # 159 # <tt>--debug</tt>:: 160 # Enable Ruby level debug messages. Handled early for the same reason as 161 # --backtrace. 162 #-- 163 # TODO: parse options upstream, pass in options directly 164 165 def initialize(args) 166 @config_file_name = nil 167 need_config_file_name = false 168 169 arg_list = [] 170 171 args.each do |arg| 172 if need_config_file_name then 173 @config_file_name = arg 174 need_config_file_name = false 175 elsif arg =~ /^--config-file=(.*)/ then 176 @config_file_name = $1 177 elsif arg =~ /^--config-file$/ then 178 need_config_file_name = true 179 else 180 arg_list << arg 181 end 182 end 183 184 @backtrace = DEFAULT_BACKTRACE 185 @bulk_threshold = DEFAULT_BULK_THRESHOLD 186 @verbose = DEFAULT_VERBOSITY 187 @update_sources = DEFAULT_UPDATE_SOURCES 188 189 operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS) 190 platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS) 191 system_config = load_file SYSTEM_WIDE_CONFIG_FILE 192 user_config = load_file config_file_name.dup.untaint 193 environment_config = (ENV['GEMRC'] || '').split(/[:;]/).inject({}) do |result, file| 194 result.merge load_file file 195 end 196 197 198 @hash = operating_system_config.merge platform_config 199 @hash = @hash.merge system_config 200 @hash = @hash.merge user_config 201 @hash = @hash.merge environment_config 202 203 # HACK these override command-line args, which is bad 204 @backtrace = @hash[:backtrace] if @hash.key? :backtrace 205 @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold 206 @home = @hash[:gemhome] if @hash.key? :gemhome 207 @path = @hash[:gempath] if @hash.key? :gempath 208 @update_sources = @hash[:update_sources] if @hash.key? :update_sources 209 @verbose = @hash[:verbose] if @hash.key? :verbose 210 @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server 211 212 @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode 213 @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert 214 215 @api_keys = nil 216 @rubygems_api_key = nil 217 218 Gem.sources = @hash[:sources] if @hash.key? :sources 219 handle_arguments arg_list 220 end 221 222 ## 223 # Hash of RubyGems.org and alternate API keys 224 225 def api_keys 226 load_api_keys unless @api_keys 227 228 @api_keys 229 end 230 231 ## 232 # Checks the permissions of the credentials file. If they are not 0600 an 233 # error message is displayed and RubyGems aborts. 234 235 def check_credentials_permissions 236 return if Gem.win_platform? # windows doesn't write 0600 as 0600 237 return unless File.exist? credentials_path 238 239 existing_permissions = File.stat(credentials_path).mode & 0777 240 241 return if existing_permissions == 0600 242 243 alert_error <<-ERROR 244Your gem push credentials file located at: 245 246\t#{credentials_path} 247 248has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required. 249 250You should reset your credentials at: 251 252\thttps://rubygems.org/profile/edit 253 254if you believe they were disclosed to a third party. 255 ERROR 256 257 terminate_interaction 1 258 end 259 260 ## 261 # Location of RubyGems.org credentials 262 263 def credentials_path 264 File.join Gem.user_home, '.gem', 'credentials' 265 end 266 267 def load_api_keys 268 check_credentials_permissions 269 270 @api_keys = if File.exist? credentials_path then 271 load_file(credentials_path) 272 else 273 @hash 274 end 275 276 if @api_keys.key? :rubygems_api_key then 277 @rubygems_api_key = @api_keys[:rubygems_api_key] 278 @api_keys[:rubygems] = @api_keys.delete :rubygems_api_key unless 279 @api_keys.key? :rubygems 280 end 281 end 282 283 ## 284 # Returns the RubyGems.org API key 285 286 def rubygems_api_key 287 load_api_keys unless @rubygems_api_key 288 289 @rubygems_api_key 290 end 291 292 ## 293 # Sets the RubyGems.org API key to +api_key+ 294 295 def rubygems_api_key= api_key 296 check_credentials_permissions 297 298 config = load_file(credentials_path).merge(:rubygems_api_key => api_key) 299 300 dirname = File.dirname credentials_path 301 Dir.mkdir(dirname) unless File.exist? dirname 302 303 Gem.load_yaml 304 305 permissions = 0600 & (~File.umask) 306 File.open(credentials_path, 'w', permissions) do |f| 307 f.write config.to_yaml 308 end 309 310 @rubygems_api_key = api_key 311 end 312 313 def load_file(filename) 314 Gem.load_yaml 315 316 return {} unless filename and File.exist? filename 317 318 begin 319 content = YAML.load(File.read(filename)) 320 unless content.kind_of? Hash 321 warn "Failed to load #{filename} because it doesn't contain valid YAML hash" 322 return {} 323 end 324 return content 325 rescue ArgumentError 326 warn "Failed to load #{filename}" 327 rescue Errno::EACCES 328 warn "Failed to load #{filename} due to permissions problem." 329 end 330 331 {} 332 end 333 334 # True if the backtrace option has been specified, or debug is on. 335 def backtrace 336 @backtrace or $DEBUG 337 end 338 339 # The name of the configuration file. 340 def config_file_name 341 @config_file_name || Gem.config_file 342 end 343 344 # Delegates to @hash 345 def each(&block) 346 hash = @hash.dup 347 hash.delete :update_sources 348 hash.delete :verbose 349 hash.delete :backtrace 350 hash.delete :bulk_threshold 351 352 yield :update_sources, @update_sources 353 yield :verbose, @verbose 354 yield :backtrace, @backtrace 355 yield :bulk_threshold, @bulk_threshold 356 357 yield 'config_file_name', @config_file_name if @config_file_name 358 359 hash.each(&block) 360 end 361 362 # Handle the command arguments. 363 def handle_arguments(arg_list) 364 @args = [] 365 366 arg_list.each do |arg| 367 case arg 368 when /^--(backtrace|traceback)$/ then 369 @backtrace = true 370 when /^--debug$/ then 371 $DEBUG = true 372 else 373 @args << arg 374 end 375 end 376 end 377 378 # Really verbose mode gives you extra output. 379 def really_verbose 380 case verbose 381 when true, false, nil then 382 false 383 else 384 true 385 end 386 end 387 388 # to_yaml only overwrites things you can't override on the command line. 389 def to_yaml # :nodoc: 390 yaml_hash = {} 391 yaml_hash[:backtrace] = if @hash.key?(:backtrace) 392 @hash[:backtrace] 393 else 394 DEFAULT_BACKTRACE 395 end 396 397 yaml_hash[:bulk_threshold] = if @hash.key?(:bulk_threshold) 398 @hash[:bulk_threshold] 399 else 400 DEFAULT_BULK_THRESHOLD 401 end 402 403 yaml_hash[:sources] = Gem.sources.to_a 404 405 yaml_hash[:update_sources] = if @hash.key?(:update_sources) 406 @hash[:update_sources] 407 else 408 DEFAULT_UPDATE_SOURCES 409 end 410 411 yaml_hash[:verbose] = if @hash.key?(:verbose) 412 @hash[:verbose] 413 else 414 DEFAULT_VERBOSITY 415 end 416 417 keys = yaml_hash.keys.map { |key| key.to_s } 418 keys << 'debug' 419 re = Regexp.union(*keys) 420 421 @hash.each do |key, value| 422 key = key.to_s 423 next if key =~ re 424 yaml_hash[key.to_s] = value 425 end 426 427 yaml_hash.to_yaml 428 end 429 430 # Writes out this config file, replacing its source. 431 def write 432 open config_file_name, 'w' do |io| 433 io.write to_yaml 434 end 435 end 436 437 # Return the configuration information for +key+. 438 def [](key) 439 @hash[key.to_s] 440 end 441 442 # Set configuration option +key+ to +value+. 443 def []=(key, value) 444 @hash[key.to_s] = value 445 end 446 447 def ==(other) # :nodoc: 448 self.class === other and 449 @backtrace == other.backtrace and 450 @bulk_threshold == other.bulk_threshold and 451 @verbose == other.verbose and 452 @update_sources == other.update_sources and 453 @hash == other.hash 454 end 455 456 attr_reader :hash 457 protected :hash 458end 459