diff options
-rw-r--r-- | lib/rubygems/remote_fetcher.rb | 230 |
1 files changed, 140 insertions, 90 deletions
@@ -1,4 +1,5 @@ require 'net/http' require 'uri' require 'rubygems' @@ -11,15 +12,38 @@ class Gem::RemoteFetcher include Gem::UserInteraction - class FetchError < Gem::Exception; end @fetcher = nil # Cached RemoteFetcher instance. def self.fetcher @fetcher ||= self.new Gem.configuration[:http_proxy] end # Initialize a remote fetcher using the source URI and possible proxy # information. # @@ -29,6 +53,7 @@ class Gem::RemoteFetcher # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, # 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 @@ -47,11 +72,13 @@ class Gem::RemoteFetcher # 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 @@ -102,21 +129,26 @@ class Gem::RemoteFetcher local_gem_path end - # Downloads +uri+. def fetch_path(uri) open_uri_or_path(uri) do |input| input.read end rescue Timeout::Error - raise FetchError, "timed out fetching #{uri}" rescue IOError, SocketError, SystemCallError => e - raise FetchError, "#{e.class}: #{e} reading #{uri}" rescue => e - message = "#{e.class}: #{e} reading #{uri}" - raise FetchError, message end # Returns the size of +uri+ in bytes. def fetch_size(uri) return File.size(get_file_uri_path(uri)) if file_uri? uri @@ -124,30 +156,21 @@ class Gem::RemoteFetcher raise ArgumentError, 'uri is not an HTTP URI' unless URI::HTTP === uri - http = connect_to uri.host, uri.port - - request = Net::HTTP::Head.new uri.request_uri - - request.basic_auth unescape(uri.user), unescape(uri.password) unless - uri.user.nil? or uri.user.empty? - resp = http.request request - - if resp.code !~ /^2/ then - raise Gem::RemoteSourceException, - "HTTP Response #{resp.code} fetching #{uri}" end - if resp['content-length'] then - return resp['content-length'].to_i else - resp = http.get uri.request_uri - return resp.body.size end rescue SocketError, SystemCallError, Timeout::Error => e - raise Gem::RemoteFetcher::FetchError, - "#{e.message} (#{e.class})\n\tgetting size of #{uri}" end private @@ -162,7 +185,9 @@ class Gem::RemoteFetcher URI.unescape(str) end # Returns an HTTP proxy URI if one is set in the environment variables. def get_proxy_from_env env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] @@ -179,104 +204,129 @@ class Gem::RemoteFetcher uri end # Normalize the URI by adding "http://" if it is missing. def normalize_uri(uri) (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}" end - # Connect to the source host/port, using a proxy if needed. - def connect_to(host, port) - if @proxy_uri - Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, unescape(@proxy_uri.user), unescape(@proxy_uri.password)).new(host, port) - else - Net::HTTP.new(host, port) end end # 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, depth = 0, &block) if file_uri?(uri) open(get_file_uri_path(uri), &block) else 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 uri.scheme == 'https' && ! connection.started? - http_obj.use_ssl = true - http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - - connection.start unless connection.started? - - request = Net::HTTP::Get.new(uri.request_uri) - unless uri.nil? || uri.user.nil? || uri.user.empty? then - request.basic_auth(uri.user, uri.password) - end - - ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}" - ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" - ua << " level #{RUBY_LEVEL}" if defined? RUBY_LEVEL - 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 - # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible - # to install gems. - retried = false - begin - @requests[connection_id] += 1 - response = connection.request(request) - rescue EOFError, Errno::ECONNABORTED - 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 # Checks if the provided string is a file:// URI. def file_uri?(uri) uri =~ %r{\Afile://} end # Given a file:// URI, returns its local path. def get_file_uri_path(uri) uri.sub(%r{\Afile://}, '') end |