summaryrefslogtreecommitdiff
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.rb164
1 files changed, 164 insertions, 0 deletions
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
new file mode 100644
index 0000000000..eac4ccaf01
--- /dev/null
+++ b/lib/rubygems/remote_fetcher.rb
@@ -0,0 +1,164 @@
+require 'net/http'
+require 'uri'
+
+require 'rubygems'
+require 'rubygems/gem_open_uri'
+
+##
+# RemoteFetcher handles the details of fetching gems and gem information from
+# a remote source.
+
+class Gem::RemoteFetcher
+
+ class FetchError < Gem::Exception; end
+
+ @fetcher = nil
+
+ # Cached RemoteFetcher instance.
+ def self.fetcher
+ @fetcher ||= new Gem.configuration[:http_proxy]
+ end
+
+ # Initialize a remote fetcher using the source URI and possible proxy
+ # information.
+ #
+ # +proxy+
+ # * [String]: explicit specification of proxy; overrides any environment
+ # variable setting
+ # * 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)
+ @proxy_uri =
+ case proxy
+ when :no_proxy then nil
+ when nil then get_proxy_from_env
+ when URI::HTTP then proxy
+ else URI.parse(proxy)
+ end
+ 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 OpenURI::HTTPError, IOError, SocketError, SystemCallError => e
+ raise FetchError, "#{e.class}: #{e} reading #{uri}"
+ end
+
+ # Returns the size of +uri+ in bytes.
+ def fetch_size(uri)
+ return File.size(get_file_uri_path(uri)) if file_uri? uri
+
+ uri = URI.parse uri unless URI::Generic === uri
+
+ 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 FetchError, "#{e.message} (#{e.class})"
+ end
+
+ private
+
+ def escape(str)
+ return unless str
+ URI.escape(str)
+ end
+
+ def unescape(str)
+ return unless str
+ 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']
+
+ return nil if env_proxy.nil? or env_proxy.empty?
+
+ uri = URI.parse env_proxy
+
+ if uri and uri.user.nil? and uri.password.nil? then
+ # Probably we have http_proxy_* variables?
+ uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'])
+ uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'])
+ end
+
+ 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, &block)
+ if file_uri?(uri)
+ open(get_file_uri_path(uri), &block)
+ else
+ connection_options = {
+ "User-Agent" => "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
+ }
+
+ 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)||'']
+ end
+
+ uri = URI.parse uri unless URI::Generic === uri
+ unless uri.nil? || uri.user.nil? || uri.user.empty? then
+ connection_options[:http_basic_authentication] =
+ [unescape(uri.user), unescape(uri.password)]
+ end
+
+ open(uri, connection_options, &block)
+ 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
+
+end
+