Geminstaller C0 Coverage Information - RCov

spec/fixture/rubygems_dist/rubygems-trunk/lib/rubygems/remote_fetcher.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
spec/fixture/rubygems_dist/rubygems-trunk/lib/rubygems/remote_fetcher.rb 387 237
64.34%
54.43%

Key

Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.

Coverage Details

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

Generated on Mon May 10 23:40:28 -0700 2010 with rcov 0.9.8