carat 1.9.9.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +24 -0
  5. data/CHANGELOG.md +2006 -0
  6. data/CODE_OF_CONDUCT.md +40 -0
  7. data/CONTRIBUTING.md +23 -0
  8. data/DEVELOPMENT.md +119 -0
  9. data/ISSUES.md +96 -0
  10. data/LICENSE.md +23 -0
  11. data/README.md +32 -0
  12. data/Rakefile +308 -0
  13. data/bin/carat +21 -0
  14. data/bin/carat_ruby +56 -0
  15. data/carat.gemspec +32 -0
  16. data/lib/carat.rb +446 -0
  17. data/lib/carat/anonymizable_uri.rb +32 -0
  18. data/lib/carat/capistrano.rb +16 -0
  19. data/lib/carat/cli.rb +407 -0
  20. data/lib/carat/cli/binstubs.rb +38 -0
  21. data/lib/carat/cli/cache.rb +35 -0
  22. data/lib/carat/cli/check.rb +35 -0
  23. data/lib/carat/cli/clean.rb +26 -0
  24. data/lib/carat/cli/common.rb +56 -0
  25. data/lib/carat/cli/config.rb +84 -0
  26. data/lib/carat/cli/console.rb +38 -0
  27. data/lib/carat/cli/exec.rb +44 -0
  28. data/lib/carat/cli/gem.rb +195 -0
  29. data/lib/carat/cli/init.rb +33 -0
  30. data/lib/carat/cli/inject.rb +33 -0
  31. data/lib/carat/cli/install.rb +156 -0
  32. data/lib/carat/cli/open.rb +23 -0
  33. data/lib/carat/cli/outdated.rb +80 -0
  34. data/lib/carat/cli/package.rb +45 -0
  35. data/lib/carat/cli/platform.rb +43 -0
  36. data/lib/carat/cli/show.rb +74 -0
  37. data/lib/carat/cli/update.rb +73 -0
  38. data/lib/carat/cli/viz.rb +27 -0
  39. data/lib/carat/constants.rb +5 -0
  40. data/lib/carat/current_ruby.rb +183 -0
  41. data/lib/carat/definition.rb +628 -0
  42. data/lib/carat/dep_proxy.rb +43 -0
  43. data/lib/carat/dependency.rb +110 -0
  44. data/lib/carat/deployment.rb +59 -0
  45. data/lib/carat/deprecate.rb +15 -0
  46. data/lib/carat/dsl.rb +331 -0
  47. data/lib/carat/endpoint_specification.rb +76 -0
  48. data/lib/carat/env.rb +75 -0
  49. data/lib/carat/environment.rb +42 -0
  50. data/lib/carat/fetcher.rb +423 -0
  51. data/lib/carat/friendly_errors.rb +85 -0
  52. data/lib/carat/gem_helper.rb +180 -0
  53. data/lib/carat/gem_helpers.rb +26 -0
  54. data/lib/carat/gem_installer.rb +9 -0
  55. data/lib/carat/gem_path_manipulation.rb +8 -0
  56. data/lib/carat/gem_tasks.rb +2 -0
  57. data/lib/carat/graph.rb +169 -0
  58. data/lib/carat/index.rb +197 -0
  59. data/lib/carat/injector.rb +64 -0
  60. data/lib/carat/installer.rb +339 -0
  61. data/lib/carat/lazy_specification.rb +83 -0
  62. data/lib/carat/lockfile_parser.rb +167 -0
  63. data/lib/carat/match_platform.rb +13 -0
  64. data/lib/carat/psyched_yaml.rb +26 -0
  65. data/lib/carat/remote_specification.rb +57 -0
  66. data/lib/carat/resolver.rb +334 -0
  67. data/lib/carat/retry.rb +60 -0
  68. data/lib/carat/ruby_dsl.rb +11 -0
  69. data/lib/carat/ruby_version.rb +117 -0
  70. data/lib/carat/rubygems_ext.rb +170 -0
  71. data/lib/carat/rubygems_integration.rb +619 -0
  72. data/lib/carat/runtime.rb +289 -0
  73. data/lib/carat/settings.rb +208 -0
  74. data/lib/carat/setup.rb +24 -0
  75. data/lib/carat/shared_helpers.rb +149 -0
  76. data/lib/carat/similarity_detector.rb +63 -0
  77. data/lib/carat/source.rb +46 -0
  78. data/lib/carat/source/git.rb +294 -0
  79. data/lib/carat/source/git/git_proxy.rb +162 -0
  80. data/lib/carat/source/path.rb +226 -0
  81. data/lib/carat/source/path/installer.rb +43 -0
  82. data/lib/carat/source/rubygems.rb +381 -0
  83. data/lib/carat/source_list.rb +101 -0
  84. data/lib/carat/spec_set.rb +154 -0
  85. data/lib/carat/ssl_certs/.document +1 -0
  86. data/lib/carat/ssl_certs/AddTrustExternalCARoot-2048.pem +25 -0
  87. data/lib/carat/ssl_certs/AddTrustExternalCARoot.pem +32 -0
  88. data/lib/carat/ssl_certs/Class3PublicPrimaryCertificationAuthority.pem +14 -0
  89. data/lib/carat/ssl_certs/DigiCertHighAssuranceEVRootCA.pem +23 -0
  90. data/lib/carat/ssl_certs/EntrustnetSecureServerCertificationAuthority.pem +28 -0
  91. data/lib/carat/ssl_certs/GeoTrustGlobalCA.pem +20 -0
  92. data/lib/carat/ssl_certs/certificate_manager.rb +66 -0
  93. data/lib/carat/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem +21 -0
  94. data/lib/carat/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem +23 -0
  95. data/lib/carat/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem +25 -0
  96. data/lib/carat/templates/Executable +16 -0
  97. data/lib/carat/templates/Executable.standalone +12 -0
  98. data/lib/carat/templates/Gemfile +4 -0
  99. data/lib/carat/templates/newgem/.travis.yml.tt +3 -0
  100. data/lib/carat/templates/newgem/CODE_OF_CONDUCT.md.tt +13 -0
  101. data/lib/carat/templates/newgem/Gemfile.tt +4 -0
  102. data/lib/carat/templates/newgem/LICENSE.txt.tt +21 -0
  103. data/lib/carat/templates/newgem/README.md.tt +39 -0
  104. data/lib/carat/templates/newgem/Rakefile.tt +25 -0
  105. data/lib/carat/templates/newgem/bin/console.tt +14 -0
  106. data/lib/carat/templates/newgem/bin/setup.tt +7 -0
  107. data/lib/carat/templates/newgem/exe/newgem.tt +3 -0
  108. data/lib/carat/templates/newgem/ext/newgem/extconf.rb.tt +3 -0
  109. data/lib/carat/templates/newgem/ext/newgem/newgem.c.tt +9 -0
  110. data/lib/carat/templates/newgem/ext/newgem/newgem.h.tt +6 -0
  111. data/lib/carat/templates/newgem/gitignore.tt +16 -0
  112. data/lib/carat/templates/newgem/lib/newgem.rb.tt +12 -0
  113. data/lib/carat/templates/newgem/lib/newgem/version.rb.tt +7 -0
  114. data/lib/carat/templates/newgem/newgem.gemspec.tt +43 -0
  115. data/lib/carat/templates/newgem/rspec.tt +2 -0
  116. data/lib/carat/templates/newgem/spec/newgem_spec.rb.tt +11 -0
  117. data/lib/carat/templates/newgem/spec/spec_helper.rb.tt +2 -0
  118. data/lib/carat/templates/newgem/test/minitest_helper.rb.tt +4 -0
  119. data/lib/carat/templates/newgem/test/test_newgem.rb.tt +11 -0
  120. data/lib/carat/ui.rb +7 -0
  121. data/lib/carat/ui/rg_proxy.rb +21 -0
  122. data/lib/carat/ui/shell.rb +103 -0
  123. data/lib/carat/ui/silent.rb +44 -0
  124. data/lib/carat/vendor/molinillo/lib/molinillo.rb +5 -0
  125. data/lib/carat/vendor/molinillo/lib/molinillo/dependency_graph.rb +266 -0
  126. data/lib/carat/vendor/molinillo/lib/molinillo/errors.rb +69 -0
  127. data/lib/carat/vendor/molinillo/lib/molinillo/gem_metadata.rb +3 -0
  128. data/lib/carat/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +90 -0
  129. data/lib/carat/vendor/molinillo/lib/molinillo/modules/ui.rb +63 -0
  130. data/lib/carat/vendor/molinillo/lib/molinillo/resolution.rb +415 -0
  131. data/lib/carat/vendor/molinillo/lib/molinillo/resolver.rb +43 -0
  132. data/lib/carat/vendor/molinillo/lib/molinillo/state.rb +43 -0
  133. data/lib/carat/vendor/net/http/faster.rb +26 -0
  134. data/lib/carat/vendor/net/http/persistent.rb +1230 -0
  135. data/lib/carat/vendor/net/http/persistent/ssl_reuse.rb +128 -0
  136. data/lib/carat/vendor/thor/lib/thor.rb +484 -0
  137. data/lib/carat/vendor/thor/lib/thor/actions.rb +319 -0
  138. data/lib/carat/vendor/thor/lib/thor/actions/create_file.rb +103 -0
  139. data/lib/carat/vendor/thor/lib/thor/actions/create_link.rb +59 -0
  140. data/lib/carat/vendor/thor/lib/thor/actions/directory.rb +118 -0
  141. data/lib/carat/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
  142. data/lib/carat/vendor/thor/lib/thor/actions/file_manipulation.rb +316 -0
  143. data/lib/carat/vendor/thor/lib/thor/actions/inject_into_file.rb +107 -0
  144. data/lib/carat/vendor/thor/lib/thor/base.rb +656 -0
  145. data/lib/carat/vendor/thor/lib/thor/command.rb +133 -0
  146. data/lib/carat/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +77 -0
  147. data/lib/carat/vendor/thor/lib/thor/core_ext/io_binary_read.rb +10 -0
  148. data/lib/carat/vendor/thor/lib/thor/core_ext/ordered_hash.rb +98 -0
  149. data/lib/carat/vendor/thor/lib/thor/error.rb +32 -0
  150. data/lib/carat/vendor/thor/lib/thor/group.rb +281 -0
  151. data/lib/carat/vendor/thor/lib/thor/invocation.rb +178 -0
  152. data/lib/carat/vendor/thor/lib/thor/line_editor.rb +17 -0
  153. data/lib/carat/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
  154. data/lib/carat/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
  155. data/lib/carat/vendor/thor/lib/thor/parser.rb +4 -0
  156. data/lib/carat/vendor/thor/lib/thor/parser/argument.rb +73 -0
  157. data/lib/carat/vendor/thor/lib/thor/parser/arguments.rb +175 -0
  158. data/lib/carat/vendor/thor/lib/thor/parser/option.rb +125 -0
  159. data/lib/carat/vendor/thor/lib/thor/parser/options.rb +218 -0
  160. data/lib/carat/vendor/thor/lib/thor/rake_compat.rb +71 -0
  161. data/lib/carat/vendor/thor/lib/thor/runner.rb +322 -0
  162. data/lib/carat/vendor/thor/lib/thor/shell.rb +81 -0
  163. data/lib/carat/vendor/thor/lib/thor/shell/basic.rb +421 -0
  164. data/lib/carat/vendor/thor/lib/thor/shell/color.rb +149 -0
  165. data/lib/carat/vendor/thor/lib/thor/shell/html.rb +126 -0
  166. data/lib/carat/vendor/thor/lib/thor/util.rb +267 -0
  167. data/lib/carat/vendor/thor/lib/thor/version.rb +3 -0
  168. data/lib/carat/vendored_fileutils.rb +9 -0
  169. data/lib/carat/vendored_molinillo.rb +2 -0
  170. data/lib/carat/vendored_persistent.rb +11 -0
  171. data/lib/carat/vendored_thor.rb +3 -0
  172. data/lib/carat/version.rb +6 -0
  173. data/lib/carat/vlad.rb +11 -0
  174. data/lib/carat/worker.rb +73 -0
  175. data/man/carat-config.ronn +178 -0
  176. data/man/carat-exec.ronn +136 -0
  177. data/man/carat-install.ronn +383 -0
  178. data/man/carat-package.ronn +66 -0
  179. data/man/carat-platform.ronn +42 -0
  180. data/man/carat-update.ronn +188 -0
  181. data/man/carat.ronn +98 -0
  182. data/man/gemfile.5.ronn +473 -0
  183. data/man/index.txt +7 -0
  184. metadata +321 -0
@@ -0,0 +1,76 @@
1
+ module Carat
2
+ # used for Creating Specifications from the Gemcutter Endpoint
3
+ class EndpointSpecification < Gem::Specification
4
+ include MatchPlatform
5
+
6
+ attr_reader :name, :version, :platform, :dependencies
7
+ attr_accessor :source, :source_uri
8
+
9
+ def initialize(name, version, platform, dependencies)
10
+ @name = name
11
+ @version = version
12
+ @platform = platform
13
+ @dependencies = dependencies
14
+ end
15
+
16
+ def fetch_platform
17
+ @platform
18
+ end
19
+
20
+ # needed for standalone, load required_paths from local gemspec
21
+ # after the gem is installed
22
+ def require_paths
23
+ if @remote_specification
24
+ @remote_specification.require_paths
25
+ elsif _local_specification
26
+ _local_specification.require_paths
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ # needed for binstubs
33
+ def executables
34
+ if @remote_specification
35
+ @remote_specification.executables
36
+ elsif _local_specification
37
+ _local_specification.executables
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ # needed for carat clean
44
+ def bindir
45
+ if @remote_specification
46
+ @remote_specification.bindir
47
+ elsif _local_specification
48
+ _local_specification.bindir
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ # needed for post_install_messages during install
55
+ def post_install_message
56
+ if @remote_specification
57
+ @remote_specification.post_install_message
58
+ elsif _local_specification
59
+ _local_specification.post_install_message
60
+ end
61
+ end
62
+
63
+ def _local_specification
64
+ eval(File.read(local_specification_path)) if @loaded_from && File.exist?(local_specification_path)
65
+ end
66
+
67
+ def __swap__(spec)
68
+ @remote_specification = spec
69
+ end
70
+
71
+ private
72
+ def local_specification_path
73
+ "#{base_dir}/specifications/#{full_name}.gemspec"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,75 @@
1
+ require 'carat/rubygems_integration'
2
+ require 'carat/source/git/git_proxy'
3
+
4
+ module Carat
5
+ class Env
6
+
7
+ def write(io)
8
+ io.write report(:print_gemfile => true)
9
+ end
10
+
11
+ def report(options = {})
12
+ print_gemfile = options.delete(:print_gemfile)
13
+
14
+ out = "Environment\n\n"
15
+ out << " Carat #{Carat::VERSION}\n"
16
+ out << " Rubygems #{Gem::VERSION}\n"
17
+ out << " Ruby #{ruby_version}"
18
+ out << " GEM_HOME #{ENV['GEM_HOME']}\n" unless ENV['GEM_HOME'].nil? || ENV['GEM_HOME'].empty?
19
+ out << " GEM_PATH #{ENV['GEM_PATH']}\n" unless ENV['GEM_PATH'] == ENV['GEM_HOME']
20
+ out << " RVM #{ENV['rvm_version']}\n" if ENV['rvm_version']
21
+ out << " Git #{git_version}\n"
22
+ %w(rubygems-carat open_gem).each do |name|
23
+ specs = Carat.rubygems.find_name(name)
24
+ out << " #{name} (#{specs.map(&:version).join(',')})\n" unless specs.empty?
25
+ end
26
+
27
+ out << "\nCarat settings\n\n" unless Carat.settings.all.empty?
28
+ Carat.settings.all.each do |setting|
29
+ out << " " << setting << "\n"
30
+ Carat.settings.pretty_values_for(setting).each do |line|
31
+ out << " " << line << "\n"
32
+ end
33
+ end
34
+
35
+ if print_gemfile
36
+ out << "\nGemfile\n\n"
37
+ out << " " << read_file(Carat.default_gemfile).gsub(/\n/, "\n ") << "\n"
38
+
39
+ out << "\n" << "Gemfile.lock\n\n"
40
+ out << " " << read_file(Carat.default_lockfile).gsub(/\n/, "\n ") << "\n"
41
+ end
42
+
43
+ out
44
+ end
45
+
46
+ private
47
+
48
+ def read_file(filename)
49
+ File.read(filename.to_s).strip
50
+ rescue Errno::ENOENT
51
+ "<No #{filename} found>"
52
+ rescue => e
53
+ "#{e.class}: #{e.message}"
54
+ end
55
+
56
+ def ruby_version
57
+ str = "#{RUBY_VERSION}"
58
+ if RUBY_VERSION < '1.9'
59
+ str << " (#{RUBY_RELEASE_DATE}"
60
+ str << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
61
+ str << ") [#{RUBY_PLATFORM}]\n"
62
+ else
63
+ str << "p#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
64
+ str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]\n"
65
+ end
66
+ end
67
+
68
+ def git_version
69
+ Carat::Source::Git::GitProxy.new(nil, nil, nil).version
70
+ rescue Carat::Source::Git::GitNotInstalledError
71
+ "not installed"
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,42 @@
1
+ module Carat
2
+ class Environment
3
+ attr_reader :root
4
+
5
+ def initialize(root, definition)
6
+ @root = root
7
+ @definition = definition
8
+
9
+ env_file = Carat.app_config_path.join('environment.rb')
10
+ env_file.rmtree if env_file.exist?
11
+ end
12
+
13
+ def inspect
14
+ @definition.to_lock.inspect
15
+ end
16
+
17
+ def requested_specs
18
+ @definition.requested_specs
19
+ end
20
+
21
+ def specs
22
+ @definition.specs
23
+ end
24
+
25
+ def dependencies
26
+ @definition.dependencies
27
+ end
28
+
29
+ def current_dependencies
30
+ @definition.current_dependencies
31
+ end
32
+
33
+ def lock
34
+ @definition.lock(Carat.default_lockfile)
35
+ end
36
+
37
+ def update(*gems)
38
+ # Nothing
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,423 @@
1
+ require 'carat/vendored_persistent'
2
+ require 'securerandom'
3
+ require 'cgi'
4
+
5
+ module Carat
6
+
7
+ # Handles all the fetching with the rubygems server
8
+ class Fetcher
9
+ # This error is raised when it looks like the network is down
10
+ class NetworkDownError < HTTPError; end
11
+ # This error is raised if the API returns a 413 (only printed in verbose)
12
+ class FallbackError < HTTPError; end
13
+ # This is the error raised if OpenSSL fails the cert verification
14
+ class CertificateFailureError < HTTPError
15
+ def initialize(remote_uri)
16
+ super "Could not verify the SSL certificate for #{remote_uri}.\nThere" \
17
+ " is a chance you are experiencing a man-in-the-middle attack, but" \
18
+ " most likely your system doesn't have the CA certificates needed" \
19
+ " for verification. For information about OpenSSL certificates, see" \
20
+ " bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile" \
21
+ " sources and change 'https' to 'http'."
22
+ end
23
+ end
24
+ # This is the error raised when a source is HTTPS and OpenSSL didn't load
25
+ class SSLError < HTTPError
26
+ def initialize(msg = nil)
27
+ super msg || "Could not load OpenSSL.\n" \
28
+ "You must recompile Ruby with OpenSSL support or change the sources in your " \
29
+ "Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL " \
30
+ "using RVM are available at rvm.io/packages/openssl."
31
+ end
32
+ end
33
+ # This error is raised if HTTP authentication is required, but not provided.
34
+ class AuthenticationRequiredError < HTTPError
35
+ def initialize(remote_uri)
36
+ super "Authentication is required for #{remote_uri}.\n" \
37
+ "Please supply credentials for this source. You can do this by running:\n" \
38
+ " carat config #{remote_uri} username:password"
39
+ end
40
+ end
41
+ # This error is raised if HTTP authentication is provided, but incorrect.
42
+ class BadAuthenticationError < HTTPError
43
+ def initialize(remote_uri)
44
+ super "Bad username or password for #{remote_uri}.\n" \
45
+ "Please double-check your credentials and correct them."
46
+ end
47
+ end
48
+
49
+ # Exceptions classes that should bypass retry attempts. If your password didn't work the
50
+ # first time, it's not going to the third time.
51
+ AUTH_ERRORS = [AuthenticationRequiredError, BadAuthenticationError]
52
+
53
+ class << self
54
+ attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
55
+
56
+ def download_gem_from_uri(spec, uri)
57
+ spec.fetch_platform
58
+
59
+ download_path = Carat.requires_sudo? ? Carat.tmp(spec.full_name) : Carat.rubygems.gem_dir
60
+ gem_path = "#{Carat.rubygems.gem_dir}/cache/#{spec.full_name}.gem"
61
+
62
+ FileUtils.mkdir_p("#{download_path}/cache")
63
+ Carat.rubygems.download_gem(spec, uri, download_path)
64
+
65
+ if Carat.requires_sudo?
66
+ Carat.mkdir_p "#{Carat.rubygems.gem_dir}/cache"
67
+ Carat.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}"
68
+ end
69
+
70
+ gem_path
71
+ ensure
72
+ Carat.rm_rf(download_path) if Carat.requires_sudo?
73
+ end
74
+
75
+ def user_agent
76
+ @user_agent ||= begin
77
+ ruby = Carat.ruby_version
78
+
79
+ agent = "carat/#{Carat::VERSION}"
80
+ agent << " rubygems/#{Gem::VERSION}"
81
+ agent << " ruby/#{ruby.version}"
82
+ agent << " (#{ruby.host})"
83
+ agent << " command/#{ARGV.first}"
84
+
85
+ if ruby.engine != "ruby"
86
+ # engine_version raises on unknown engines
87
+ engine_version = ruby.engine_version rescue "???"
88
+ agent << " #{ruby.engine}/#{engine_version}"
89
+ end
90
+
91
+ agent << " options/#{Carat.settings.all.join(",")}"
92
+
93
+ # add a random ID so we can consolidate runs server-side
94
+ agent << " " << SecureRandom.hex(8)
95
+
96
+ # add any user agent strings set in the config
97
+ extra_ua = Carat.settings[:user_agent]
98
+ agent << " " << extra_ua if extra_ua
99
+
100
+ agent
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ def initialize(remote_uri)
107
+ @redirect_limit = 5 # How many redirects to allow in one request
108
+ @api_timeout = 10 # How long to wait for each API call
109
+ @max_retries = 3 # How many retries for the API call
110
+
111
+ @anonymizable_uri = configured_uri_for(remote_uri)
112
+
113
+ Socket.do_not_reverse_lookup = true
114
+ connection # create persistent connection
115
+ end
116
+
117
+ def connection
118
+ @connection ||= begin
119
+ needs_ssl = remote_uri.scheme == "https" ||
120
+ Carat.settings[:ssl_verify_mode] ||
121
+ Carat.settings[:ssl_client_cert]
122
+ raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
123
+
124
+ con = Net::HTTP::Persistent.new 'carat', :ENV
125
+
126
+ if remote_uri.scheme == "https"
127
+ con.verify_mode = (Carat.settings[:ssl_verify_mode] ||
128
+ OpenSSL::SSL::VERIFY_PEER)
129
+ con.cert_store = carat_cert_store
130
+ end
131
+
132
+ if Carat.settings[:ssl_client_cert]
133
+ pem = File.read(Carat.settings[:ssl_client_cert])
134
+ con.cert = OpenSSL::X509::Certificate.new(pem)
135
+ con.key = OpenSSL::PKey::RSA.new(pem)
136
+ end
137
+
138
+ con.read_timeout = @api_timeout
139
+ con.override_headers["User-Agent"] = self.class.user_agent
140
+ con
141
+ end
142
+ end
143
+
144
+ def uri
145
+ @anonymizable_uri.without_credentials
146
+ end
147
+
148
+ # fetch a gem specification
149
+ def fetch_spec(spec)
150
+ spec = spec - [nil, 'ruby', '']
151
+ spec_file_name = "#{spec.join '-'}.gemspec"
152
+
153
+ uri = URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
154
+ if uri.scheme == 'file'
155
+ Carat.load_marshal Gem.inflate(Gem.read_binary(uri.path))
156
+ elsif cached_spec_path = gemspec_cached_path(spec_file_name)
157
+ Carat.load_gemspec(cached_spec_path)
158
+ else
159
+ Carat.load_marshal Gem.inflate(fetch(uri))
160
+ end
161
+ rescue MarshalError
162
+ raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
163
+ "Your network or your gem server is probably having issues right now."
164
+ end
165
+
166
+ # cached gem specification path, if one exists
167
+ def gemspec_cached_path spec_file_name
168
+ paths = Carat.rubygems.spec_cache_dirs.map { |dir| File.join(dir, spec_file_name) }
169
+ paths = paths.select {|path| File.file? path }
170
+ paths.first
171
+ end
172
+
173
+ # return the specs in the carat format as an index
174
+ def specs(gem_names, source)
175
+ old = Carat.rubygems.sources
176
+ index = Index.new
177
+
178
+ if gem_names && use_api
179
+ specs = fetch_remote_specs(gem_names)
180
+ end
181
+
182
+ if specs.nil?
183
+ # API errors mean we should treat this as a non-API source
184
+ @use_api = false
185
+
186
+ specs = Carat::Retry.new("source fetch", AUTH_ERRORS).attempts do
187
+ fetch_all_remote_specs
188
+ end
189
+ end
190
+
191
+ specs[remote_uri].each do |name, version, platform, dependencies|
192
+ next if name == 'carat'
193
+ spec = nil
194
+ if dependencies
195
+ spec = EndpointSpecification.new(name, version, platform, dependencies)
196
+ else
197
+ spec = RemoteSpecification.new(name, version, platform, self)
198
+ end
199
+ spec.source = source
200
+ spec.source_uri = @anonymizable_uri
201
+ index << spec
202
+ end
203
+
204
+ index
205
+ rescue CertificateFailureError => e
206
+ Carat.ui.info "" if gem_names && use_api # newline after dots
207
+ raise e
208
+ ensure
209
+ Carat.rubygems.sources = old
210
+ end
211
+
212
+ # fetch index
213
+ def fetch_remote_specs(gem_names, full_dependency_list = [], last_spec_list = [])
214
+ query_list = gem_names - full_dependency_list
215
+
216
+ # only display the message on the first run
217
+ if Carat.ui.debug?
218
+ Carat.ui.debug "Query List: #{query_list.inspect}"
219
+ else
220
+ Carat.ui.info ".", false
221
+ end
222
+
223
+ return {remote_uri => last_spec_list} if query_list.empty?
224
+
225
+ remote_specs = Carat::Retry.new("dependency api", AUTH_ERRORS).attempts do
226
+ fetch_dependency_remote_specs(query_list)
227
+ end
228
+
229
+ spec_list, deps_list = remote_specs
230
+ returned_gems = spec_list.map {|spec| spec.first }.uniq
231
+ fetch_remote_specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list)
232
+ rescue HTTPError, MarshalError, GemspecError
233
+ Carat.ui.info "" unless Carat.ui.debug? # new line now that the dots are over
234
+ Carat.ui.debug "could not fetch from the dependency API, trying the full index"
235
+ @use_api = false
236
+ return nil
237
+ end
238
+
239
+ def use_api
240
+ return @use_api if defined?(@use_api)
241
+
242
+ if remote_uri.scheme == "file" || Carat::Fetcher.disable_endpoint
243
+ @use_api = false
244
+ elsif fetch(dependency_api_uri)
245
+ @use_api = true
246
+ end
247
+ rescue NetworkDownError => e
248
+ raise HTTPError, e.message
249
+ rescue AuthenticationRequiredError
250
+ # We got a 401 from the server. Don't fall back to the full index, just fail.
251
+ raise
252
+ rescue HTTPError
253
+ @use_api = false
254
+ end
255
+
256
+ def inspect
257
+ "#<#{self.class}:0x#{object_id} uri=#{uri}>"
258
+ end
259
+
260
+ private
261
+
262
+ HTTP_ERRORS = [
263
+ Timeout::Error, EOFError, SocketError, Errno::ENETDOWN,
264
+ Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
265
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
266
+ Net::HTTP::Persistent::Error
267
+ ]
268
+
269
+ def fetch(uri, counter = 0)
270
+ raise HTTPError, "Too many redirects" if counter >= @redirect_limit
271
+
272
+ response = request(uri)
273
+ Carat.ui.debug("HTTP #{response.code} #{response.message}")
274
+
275
+ case response
276
+ when Net::HTTPRedirection
277
+ new_uri = URI.parse(response["location"])
278
+ if new_uri.host == uri.host
279
+ new_uri.user = uri.user
280
+ new_uri.password = uri.password
281
+ end
282
+ fetch(new_uri, counter + 1)
283
+ when Net::HTTPSuccess
284
+ response.body
285
+ when Net::HTTPRequestEntityTooLarge
286
+ raise FallbackError, response.body
287
+ when Net::HTTPUnauthorized
288
+ raise AuthenticationRequiredError, remote_uri.host
289
+ else
290
+ raise HTTPError, "#{response.class}: #{response.body}"
291
+ end
292
+ end
293
+
294
+ def request(uri)
295
+ Carat.ui.debug "HTTP GET #{uri}"
296
+ req = Net::HTTP::Get.new uri.request_uri
297
+ if uri.user
298
+ user = CGI.unescape(uri.user)
299
+ password = uri.password ? CGI.unescape(uri.password) : nil
300
+ req.basic_auth(user, password)
301
+ end
302
+ connection.request(uri, req)
303
+ rescue OpenSSL::SSL::SSLError
304
+ raise CertificateFailureError.new(uri)
305
+ rescue *HTTP_ERRORS => e
306
+ Carat.ui.trace e
307
+ case e.message
308
+ when /host down:/, /getaddrinfo: nodename nor servname provided/
309
+ raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \
310
+ "connection and try again."
311
+ else
312
+ raise HTTPError, "Network error while fetching #{uri}"
313
+ end
314
+ end
315
+
316
+ def dependency_api_uri(gem_names = [])
317
+ uri = fetch_uri + "api/v1/dependencies"
318
+ uri.query = "gems=#{URI.encode(gem_names.join(","))}" if gem_names.any?
319
+ uri
320
+ end
321
+
322
+ # fetch from Gemcutter Dependency Endpoint API
323
+ def fetch_dependency_remote_specs(gem_names)
324
+ Carat.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(',')}"
325
+ gem_list = []
326
+ deps_list = []
327
+
328
+ gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names|
329
+ marshalled_deps = fetch dependency_api_uri(names)
330
+ gem_list += Carat.load_marshal(marshalled_deps)
331
+ end
332
+
333
+ spec_list = gem_list.map do |s|
334
+ dependencies = s[:dependencies].map do |name, requirement|
335
+ dep = well_formed_dependency(name, requirement.split(", "))
336
+ deps_list << dep.name
337
+ dep
338
+ end
339
+
340
+ [s[:name], Gem::Version.new(s[:number]), s[:platform], dependencies]
341
+ end
342
+
343
+ [spec_list, deps_list.uniq]
344
+ end
345
+
346
+ # fetch from modern index: specs.4.8.gz
347
+ def fetch_all_remote_specs
348
+ old_sources = Carat.rubygems.sources
349
+ Carat.rubygems.sources = [remote_uri.to_s]
350
+ Carat.rubygems.fetch_all_remote_specs
351
+ rescue Gem::RemoteFetcher::FetchError, OpenSSL::SSL::SSLError => e
352
+ case e.message
353
+ when /certificate verify failed/
354
+ raise CertificateFailureError.new(uri)
355
+ when /401/
356
+ raise AuthenticationRequiredError, remote_uri
357
+ when /403/
358
+ if remote_uri.userinfo
359
+ raise BadAuthenticationError, remote_uri
360
+ else
361
+ raise AuthenticationRequiredError, remote_uri
362
+ end
363
+ else
364
+ Carat.ui.trace e
365
+ raise HTTPError, "Could not fetch specs from #{uri}"
366
+ end
367
+ ensure
368
+ Carat.rubygems.sources = old_sources
369
+ end
370
+
371
+ def well_formed_dependency(name, *requirements)
372
+ Gem::Dependency.new(name, *requirements)
373
+ rescue ArgumentError => e
374
+ illformed = 'Ill-formed requirement ["#<YAML::Syck::DefaultKey'
375
+ raise e unless e.message.include?(illformed)
376
+ puts # we shouldn't print the error message on the "fetching info" status line
377
+ raise GemspecError,
378
+ "Unfortunately, the gem #{s[:name]} (#{s[:number]}) has an invalid " \
379
+ "gemspec. \nPlease ask the gem author to yank the bad version to fix " \
380
+ "this issue. For more information, see http://bit.ly/syck-defaultkey."
381
+ end
382
+
383
+ def carat_cert_store
384
+ store = OpenSSL::X509::Store.new
385
+ if Carat.settings[:ssl_ca_cert]
386
+ if File.directory? Carat.settings[:ssl_ca_cert]
387
+ store.add_path Carat.settings[:ssl_ca_cert]
388
+ else
389
+ store.add_file Carat.settings[:ssl_ca_cert]
390
+ end
391
+ else
392
+ store.set_default_paths
393
+ certs = File.expand_path("../ssl_certs/*.pem", __FILE__)
394
+ Dir.glob(certs).each { |c| store.add_file c }
395
+ end
396
+ store
397
+ end
398
+
399
+ private
400
+
401
+ def configured_uri_for(uri)
402
+ uri = Carat::Source.mirror_for(uri)
403
+ config_auth = Carat.settings[uri.to_s] || Carat.settings[uri.host]
404
+ AnonymizableURI.new(uri, config_auth)
405
+ end
406
+
407
+ def fetch_uri
408
+ @fetch_uri ||= begin
409
+ if remote_uri.host == "rubygems.org"
410
+ uri = remote_uri.dup
411
+ uri.host = "bundler.rubygems.org"
412
+ uri
413
+ else
414
+ remote_uri
415
+ end
416
+ end
417
+ end
418
+
419
+ def remote_uri
420
+ @anonymizable_uri.original_uri
421
+ end
422
+ end
423
+ end