PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Languages/Ruby/StdLib/ruby/1.9.1/rubygems/remote_fetcher.rb

https://github.com/jdhardy/ironpython
Ruby | 387 lines | 237 code | 96 blank | 54 comment | 35 complexity | 1e1ce913bf3237cb64f7c78b79705694 MD5 | raw file
  1. require 'net/http'
  2. require 'stringio'
  3. require 'time'
  4. require 'uri'
  5. require 'rubygems'
  6. ##
  7. # RemoteFetcher handles the details of fetching gems and gem information from
  8. # a remote source.
  9. class Gem::RemoteFetcher
  10. include Gem::UserInteraction
  11. ##
  12. # A FetchError exception wraps up the various possible IO and HTTP failures
  13. # that could happen while downloading from the internet.
  14. class FetchError < Gem::Exception
  15. ##
  16. # The URI which was being accessed when the exception happened.
  17. attr_accessor :uri
  18. def initialize(message, uri)
  19. super message
  20. @uri = uri
  21. end
  22. def to_s # :nodoc:
  23. "#{super} (#{uri})"
  24. end
  25. end
  26. @fetcher = nil
  27. ##
  28. # Cached RemoteFetcher instance.
  29. def self.fetcher
  30. @fetcher ||= self.new Gem.configuration[:http_proxy]
  31. end
  32. ##
  33. # Initialize a remote fetcher using the source URI and possible proxy
  34. # information.
  35. #
  36. # +proxy+
  37. # * [String]: explicit specification of proxy; overrides any environment
  38. # variable setting
  39. # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER,
  40. # HTTP_PROXY_PASS)
  41. # * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy
  42. def initialize(proxy = nil)
  43. Socket.do_not_reverse_lookup = true
  44. @connections = {}
  45. @requests = Hash.new 0
  46. @proxy_uri =
  47. case proxy
  48. when :no_proxy then nil
  49. when nil then get_proxy_from_env
  50. when URI::HTTP then proxy
  51. else URI.parse(proxy)
  52. end
  53. end
  54. ##
  55. # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is
  56. # already there. If the source_uri is local the gem cache dir copy is
  57. # always replaced.
  58. def download(spec, source_uri, install_dir = Gem.dir)
  59. if File.writable?(install_dir)
  60. cache_dir = File.join install_dir, 'cache'
  61. else
  62. cache_dir = File.join(Gem.user_dir, 'cache')
  63. end
  64. gem_file_name = spec.file_name
  65. local_gem_path = File.join cache_dir, gem_file_name
  66. FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir
  67. # Always escape URI's to deal with potential spaces and such
  68. unless URI::Generic === source_uri
  69. source_uri = URI.parse(URI.const_defined?(:DEFAULT_PARSER) ?
  70. URI::DEFAULT_PARSER.escape(source_uri) :
  71. URI.escape(source_uri))
  72. end
  73. scheme = source_uri.scheme
  74. # URI.parse gets confused by MS Windows paths with forward slashes.
  75. scheme = nil if scheme =~ /^[a-z]$/i
  76. case scheme
  77. when 'http', 'https' then
  78. unless File.exist? local_gem_path then
  79. begin
  80. say "Downloading gem #{gem_file_name}" if
  81. Gem.configuration.really_verbose
  82. remote_gem_path = source_uri + "gems/#{gem_file_name}"
  83. gem = self.fetch_path remote_gem_path
  84. rescue Gem::RemoteFetcher::FetchError
  85. raise if spec.original_platform == spec.platform
  86. alternate_name = "#{spec.original_name}.gem"
  87. say "Failed, downloading gem #{alternate_name}" if
  88. Gem.configuration.really_verbose
  89. remote_gem_path = source_uri + "gems/#{alternate_name}"
  90. gem = self.fetch_path remote_gem_path
  91. end
  92. File.open local_gem_path, 'wb' do |fp|
  93. fp.write gem
  94. end
  95. end
  96. when 'file' then
  97. begin
  98. path = source_uri.path
  99. path = File.dirname(path) if File.extname(path) == '.gem'
  100. remote_gem_path = File.join(path, 'gems', gem_file_name)
  101. FileUtils.cp(remote_gem_path, local_gem_path)
  102. rescue Errno::EACCES
  103. local_gem_path = source_uri.to_s
  104. end
  105. say "Using local gem #{local_gem_path}" if
  106. Gem.configuration.really_verbose
  107. when nil then # TODO test for local overriding cache
  108. source_path = if Gem.win_platform? && source_uri.scheme &&
  109. !source_uri.path.include?(':') then
  110. "#{source_uri.scheme}:#{source_uri.path}"
  111. else
  112. source_uri.path
  113. end
  114. source_path = URI.unescape source_path
  115. begin
  116. FileUtils.cp source_path, local_gem_path unless
  117. File.expand_path(source_path) == File.expand_path(local_gem_path)
  118. rescue Errno::EACCES
  119. local_gem_path = source_uri.to_s
  120. end
  121. say "Using local gem #{local_gem_path}" if
  122. Gem.configuration.really_verbose
  123. else
  124. raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}"
  125. end
  126. local_gem_path
  127. end
  128. ##
  129. # Downloads +uri+ and returns it as a String.
  130. def fetch_path(uri, mtime = nil, head = false)
  131. data = open_uri_or_path uri, mtime, head
  132. data = Gem.gunzip data if data and not head and uri.to_s =~ /gz$/
  133. data
  134. rescue FetchError
  135. raise
  136. rescue Timeout::Error
  137. raise FetchError.new('timed out', uri)
  138. rescue IOError, SocketError, SystemCallError => e
  139. raise FetchError.new("#{e.class}: #{e}", uri)
  140. end
  141. ##
  142. # Returns the size of +uri+ in bytes.
  143. def fetch_size(uri) # TODO: phase this out
  144. response = fetch_path(uri, nil, true)
  145. response['content-length'].to_i
  146. end
  147. def escape(str)
  148. return unless str
  149. URI.escape(str)
  150. end
  151. def unescape(str)
  152. return unless str
  153. URI.unescape(str)
  154. end
  155. ##
  156. # Returns an HTTP proxy URI if one is set in the environment variables.
  157. def get_proxy_from_env
  158. env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
  159. return nil if env_proxy.nil? or env_proxy.empty?
  160. uri = URI.parse(normalize_uri(env_proxy))
  161. if uri and uri.user.nil? and uri.password.nil? then
  162. # Probably we have http_proxy_* variables?
  163. uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'])
  164. uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'])
  165. end
  166. uri
  167. end
  168. ##
  169. # Normalize the URI by adding "http://" if it is missing.
  170. def normalize_uri(uri)
  171. (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
  172. end
  173. ##
  174. # Creates or an HTTP connection based on +uri+, or retrieves an existing
  175. # connection, using a proxy if needed.
  176. def connection_for(uri)
  177. net_http_args = [uri.host, uri.port]
  178. if @proxy_uri then
  179. net_http_args += [
  180. @proxy_uri.host,
  181. @proxy_uri.port,
  182. @proxy_uri.user,
  183. @proxy_uri.password
  184. ]
  185. end
  186. connection_id = net_http_args.join ':'
  187. @connections[connection_id] ||= Net::HTTP.new(*net_http_args)
  188. connection = @connections[connection_id]
  189. if uri.scheme == 'https' and not connection.started? then
  190. require 'net/https'
  191. connection.use_ssl = true
  192. connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
  193. end
  194. connection.start unless connection.started?
  195. connection
  196. rescue Errno::EHOSTDOWN => e
  197. raise FetchError.new(e.message, uri)
  198. end
  199. ##
  200. # Read the data from the (source based) URI, but if it is a file:// URI,
  201. # read from the filesystem instead.
  202. def open_uri_or_path(uri, last_modified = nil, head = false, depth = 0)
  203. raise "block is dead" if block_given?
  204. uri = URI.parse uri unless URI::Generic === uri
  205. # This check is redundant unless Gem::RemoteFetcher is likely
  206. # to be used directly, since the scheme is checked elsewhere.
  207. # - Daniel Berger
  208. unless ['http', 'https', 'file'].include?(uri.scheme)
  209. raise ArgumentError, 'uri scheme is invalid'
  210. end
  211. if uri.scheme == 'file'
  212. path = uri.path
  213. # Deal with leading slash on Windows paths
  214. if path[0].chr == '/' && path[1].chr =~ /[a-zA-Z]/ && path[2].chr == ':'
  215. path = path[1..-1]
  216. end
  217. return Gem.read_binary(path)
  218. end
  219. fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get
  220. response = request uri, fetch_type, last_modified
  221. case response
  222. when Net::HTTPOK, Net::HTTPNotModified then
  223. head ? response : response.body
  224. when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
  225. Net::HTTPTemporaryRedirect then
  226. raise FetchError.new('too many redirects', uri) if depth > 10
  227. open_uri_or_path(response['Location'], last_modified, head, depth + 1)
  228. else
  229. raise FetchError.new("bad response #{response.message} #{response.code}", uri)
  230. end
  231. end
  232. ##
  233. # Performs a Net::HTTP request of type +request_class+ on +uri+ returning
  234. # a Net::HTTP response object. request maintains a table of persistent
  235. # connections to reduce connect overhead.
  236. def request(uri, request_class, last_modified = nil)
  237. request = request_class.new uri.request_uri
  238. unless uri.nil? || uri.user.nil? || uri.user.empty? then
  239. request.basic_auth uri.user, uri.password
  240. end
  241. ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}"
  242. ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
  243. ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
  244. ua << ")"
  245. request.add_field 'User-Agent', ua
  246. request.add_field 'Connection', 'keep-alive'
  247. request.add_field 'Keep-Alive', '30'
  248. if last_modified then
  249. last_modified = last_modified.utc
  250. request.add_field 'If-Modified-Since', last_modified.rfc2822
  251. end
  252. yield request if block_given?
  253. connection = connection_for uri
  254. retried = false
  255. bad_response = false
  256. begin
  257. @requests[connection.object_id] += 1
  258. say "#{request.method} #{uri}" if
  259. Gem.configuration.really_verbose
  260. response = connection.request request
  261. say "#{response.code} #{response.message}" if
  262. Gem.configuration.really_verbose
  263. rescue Net::HTTPBadResponse
  264. say "bad response" if Gem.configuration.really_verbose
  265. reset connection
  266. raise FetchError.new('too many bad responses', uri) if bad_response
  267. bad_response = true
  268. retry
  269. # HACK work around EOFError bug in Net::HTTP
  270. # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
  271. # to install gems.
  272. rescue EOFError, Timeout::Error,
  273. Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
  274. requests = @requests[connection.object_id]
  275. say "connection reset after #{requests} requests, retrying" if
  276. Gem.configuration.really_verbose
  277. raise FetchError.new('too many connection resets', uri) if retried
  278. reset connection
  279. retried = true
  280. retry
  281. end
  282. response
  283. end
  284. ##
  285. # Resets HTTP connection +connection+.
  286. def reset(connection)
  287. @requests.delete connection.object_id
  288. connection.finish
  289. connection.start
  290. end
  291. end