aboutsummaryrefslogtreecommitdiffstats
path: root/lib/rubygems/remote_fetcher.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/remote_fetcher.rb')
-rw-r--r--lib/rubygems/remote_fetcher.rb147
1 files changed, 131 insertions, 16 deletions
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index cb22e1f1b1..f49ee2f4a1 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -2,7 +2,6 @@ require 'net/http'
require 'uri'
require 'rubygems'
-require 'rubygems/gem_open_uri'
##
# RemoteFetcher handles the details of fetching gems and gem information from
@@ -10,6 +9,8 @@ require 'rubygems/gem_open_uri'
class Gem::RemoteFetcher
+ include Gem::UserInteraction
+
class FetchError < Gem::Exception; end
@fetcher = nil
@@ -29,6 +30,10 @@ class Gem::RemoteFetcher
# HTTP_PROXY_PASS)
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy
def initialize(proxy)
+ Socket.do_not_reverse_lookup = true
+
+ @connections = {}
+ @requests = Hash.new 0
@proxy_uri =
case proxy
when :no_proxy then nil
@@ -38,6 +43,65 @@ class Gem::RemoteFetcher
end
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)
+ gem_file_name = "#{spec.full_name}.gem"
+ local_gem_path = File.join install_dir, 'cache', gem_file_name
+
+ Gem.ensure_gem_subdirectories install_dir
+
+ source_uri = URI.parse source_uri unless URI::Generic === source_uri
+ scheme = source_uri.scheme
+
+ # URI.parse gets confused by MS Windows paths with forward slashes.
+ scheme = nil if scheme =~ /^[a-z]$/i
+
+ case scheme
+ when 'http' then
+ unless File.exist? local_gem_path then
+ begin
+ say "Downloading gem #{gem_file_name}" if
+ Gem.configuration.really_verbose
+
+ remote_gem_path = source_uri + "gems/#{gem_file_name}"
+
+ gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
+ rescue Gem::RemoteFetcher::FetchError
+ raise if spec.original_platform == spec.platform
+
+ alternate_name = "#{spec.original_name}.gem"
+
+ say "Failed, downloading gem #{alternate_name}" if
+ Gem.configuration.really_verbose
+
+ remote_gem_path = source_uri + "gems/#{alternate_name}"
+
+ gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
+ end
+
+ File.open local_gem_path, 'wb' do |fp|
+ fp.write gem
+ end
+ end
+ when nil, 'file' then # TODO test for local overriding cache
+ begin
+ FileUtils.cp source_uri.to_s, local_gem_path
+ rescue Errno::EACCES
+ local_gem_path = source_uri.to_s
+ end
+
+ say "Using local gem #{local_gem_path}" if
+ Gem.configuration.really_verbose
+ else
+ raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}"
+ end
+
+ local_gem_path
+ end
+
# Downloads +uri+.
def fetch_path(uri)
open_uri_or_path(uri) do |input|
@@ -47,9 +111,8 @@ class Gem::RemoteFetcher
raise FetchError, "timed out fetching #{uri}"
rescue IOError, SocketError, SystemCallError => e
raise FetchError, "#{e.class}: #{e} reading #{uri}"
- rescue OpenURI::HTTPError => e
- body = e.io.readlines.join "\n\t"
- message = "#{e.class}: #{e} reading #{uri}\n\t#{body}"
+ rescue => e
+ message = "#{e.class}: #{e} reading #{uri}"
raise FetchError, message
end
@@ -83,7 +146,8 @@ class Gem::RemoteFetcher
end
rescue SocketError, SystemCallError, Timeout::Error => e
- raise FetchError, "#{e.message} (#{e.class})\n\tgetting size of #{uri}"
+ raise Gem::RemoteFetcher::FetchError,
+ "#{e.message} (#{e.class})\n\tgetting size of #{uri}"
end
private
@@ -131,26 +195,77 @@ class Gem::RemoteFetcher
# Read the data from the (source based) URI, but if it is a file:// URI,
# read from the filesystem instead.
- def open_uri_or_path(uri, &block)
+ def open_uri_or_path(uri, depth = 0, &block)
if file_uri?(uri)
open(get_file_uri_path(uri), &block)
else
- connection_options = {
- "User-Agent" => "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
- }
+ uri = URI.parse uri unless URI::Generic === uri
+ net_http_args = [uri.host, uri.port]
+
+ if @proxy_uri then
+ net_http_args += [ @proxy_uri.host,
+ @proxy_uri.port,
+ @proxy_uri.user,
+ @proxy_uri.password
+ ]
+ end
+
+ connection_id = net_http_args.join ':'
+ @connections[connection_id] ||= Net::HTTP.new(*net_http_args)
+ connection = @connections[connection_id]
- if @proxy_uri
- http_proxy_url = "#{@proxy_uri.scheme}://#{@proxy_uri.host}:#{@proxy_uri.port}"
- connection_options[:proxy_http_basic_authentication] = [http_proxy_url, unescape(@proxy_uri.user)||'', unescape(@proxy_uri.password)||'']
+ if uri.scheme == 'https' && ! connection.started?
+ http_obj.use_ssl = true
+ http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
- uri = URI.parse uri unless URI::Generic === uri
+ connection.start unless connection.started?
+
+ request = Net::HTTP::Get.new(uri.request_uri)
unless uri.nil? || uri.user.nil? || uri.user.empty? then
- connection_options[:http_basic_authentication] =
- [unescape(uri.user), unescape(uri.password)]
+ request.basic_auth(uri.user, uri.password)
end
- open(uri, connection_options, &block)
+ ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
+ ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
+ ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
+ ua << ")"
+
+ request.add_field 'User-Agent', ua
+ request.add_field 'Connection', 'keep-alive'
+ request.add_field 'Keep-Alive', '30'
+
+ # HACK work around EOFError bug in Net::HTTP
+ retried = false
+ begin
+ @requests[connection_id] += 1
+ response = connection.request(request)
+ rescue EOFError
+ requests = @requests[connection_id]
+ say "connection reset after #{requests} requests, retrying" if
+ Gem.configuration.really_verbose
+
+ raise Gem::RemoteFetcher::FetchError, 'too many connection resets' if
+ retried
+
+ @requests[connection_id] = 0
+
+ connection.finish
+ connection.start
+ retried = true
+ retry
+ end
+
+ case response
+ when Net::HTTPOK then
+ block.call(StringIO.new(response.body)) if block
+ when Net::HTTPRedirection then
+ raise Gem::RemoteFetcher::FetchError, "too many redirects" if depth > 10
+ open_uri_or_path(response['Location'], depth + 1, &block)
+ else
+ raise Gem::RemoteFetcher::FetchError,
+ "bad response #{response.message} #{response.code}"
+ end
end
end