1require 'rubygems/test_case'
2require 'ostruct'
3require 'webrick'
4require 'webrick/https'
5require 'rubygems/remote_fetcher'
6require 'rubygems/package'
7require 'minitest/mock'
8
9# = Testing Proxy Settings
10#
11# These tests check the proper proxy server settings by running two
12# web servers.  The web server at http://localhost:#{SERVER_PORT}
13# represents the normal gem server and returns a gemspec with a rake
14# version of 0.4.11.  The web server at http://localhost:#{PROXY_PORT}
15# represents the proxy server and returns a different dataset where
16# rake has version 0.4.2.  This allows us to detect which server is
17# returning the data.
18#
19# Note that the proxy server is not a *real* proxy server.  But our
20# software doesn't really care, as long as we hit the proxy URL when a
21# proxy is configured.
22
23class TestGemRemoteFetcher < Gem::TestCase
24
25  include Gem::DefaultUserInteraction
26
27  SERVER_DATA = <<-EOY
28--- !ruby/object:Gem::Cache
29gems:
30  rake-0.4.11: !ruby/object:Gem::Specification
31    rubygems_version: "0.7"
32    specification_version: 1
33    name: rake
34    version: !ruby/object:Gem::Version
35      version: 0.4.11
36    date: 2004-11-12
37    summary: Ruby based make-like utility.
38    require_paths:
39      - lib
40    author: Jim Weirich
41    email: jim@weirichhouse.org
42    homepage: http://rake.rubyforge.org
43    rubyforge_project: rake
44    description: Rake is a Make-like program implemented in Ruby. Tasks and dependencies are specified in standard Ruby syntax.
45    autorequire:
46    default_executable: rake
47    bindir: bin
48    has_rdoc: true
49    required_ruby_version: !ruby/object:Gem::Version::Requirement
50      requirements:
51        -
52          - ">"
53          - !ruby/object:Gem::Version
54            version: 0.0.0
55      version:
56    platform: ruby
57    files:
58      - README
59    test_files: []
60    library_stubs:
61    rdoc_options:
62    extra_rdoc_files:
63    executables:
64      - rake
65    extensions: []
66    requirements: []
67    dependencies: []
68  EOY
69
70  PROXY_DATA = SERVER_DATA.gsub(/0.4.11/, '0.4.2')
71
72  DIR = File.expand_path(File.dirname(__FILE__))
73
74  def setup
75    @proxies = %w[http_proxy HTTP_PROXY http_proxy_user HTTP_PROXY_USER http_proxy_pass HTTP_PROXY_PASS no_proxy NO_PROXY]
76    @old_proxies = @proxies.map {|k| ENV[k] }
77    @proxies.each {|k| ENV[k] = nil }
78
79    super
80    self.class.start_servers
81    self.class.enable_yaml = true
82    self.class.enable_zip = false
83
84    base_server_uri = "http://localhost:#{self.class.normal_server_port}"
85    @proxy_uri = "http://localhost:#{self.class.proxy_server_port}"
86
87    @server_uri = base_server_uri + "/yaml"
88    @server_z_uri = base_server_uri + "/yaml.Z"
89
90    # REFACTOR: copied from test_gem_dependency_installer.rb
91    @gems_dir = File.join @tempdir, 'gems'
92    @cache_dir = File.join @gemhome, "cache"
93    FileUtils.mkdir @gems_dir
94
95    # TODO: why does the remote fetcher need it written to disk?
96    @a1, @a1_gem = util_gem 'a', '1' do |s| s.executables << 'a_bin' end
97    @a1.loaded_from = File.join(@gemhome, 'specifications', @a1.full_name)
98
99    Gem::RemoteFetcher.fetcher = nil
100
101    @fetcher = Gem::RemoteFetcher.fetcher
102  end
103
104  def teardown
105    super
106    Gem.configuration[:http_proxy] = nil
107    @proxies.each_with_index {|k, i| ENV[k] = @old_proxies[i] }
108  end
109
110  def test_self_fetcher
111    fetcher = Gem::RemoteFetcher.fetcher
112    refute_nil fetcher
113    assert_kind_of Gem::RemoteFetcher, fetcher
114  end
115
116  def test_self_fetcher_with_proxy
117    proxy_uri = 'http://proxy.example.com'
118    Gem.configuration[:http_proxy] = proxy_uri
119    Gem::RemoteFetcher.fetcher = nil
120
121    fetcher = Gem::RemoteFetcher.fetcher
122
123    refute_nil fetcher
124    assert_kind_of Gem::RemoteFetcher, fetcher
125    assert_equal proxy_uri, fetcher.instance_variable_get(:@proxy_uri).to_s
126  end
127
128  def test_self_fetcher_with_proxy_URI
129    proxy_uri = URI.parse 'http://proxy.example.com'
130    Gem.configuration[:http_proxy] = proxy_uri
131    Gem::RemoteFetcher.fetcher = nil
132
133    fetcher = Gem::RemoteFetcher.fetcher
134    refute_nil fetcher
135
136    assert_kind_of Gem::RemoteFetcher, fetcher
137    assert_equal proxy_uri, fetcher.instance_variable_get(:@proxy_uri)
138  end
139
140  def test_escape_auth_info
141    assert_equal 'a%40b%5Cc', @fetcher.escape_auth_info('a@b\c')
142  end
143
144  def test_unescape_auth_info
145    assert_equal 'a@b\c', @fetcher.unescape_auth_info('a%40b%5Cc')
146  end
147
148  def test_fetch_size_bad_uri
149    fetcher = Gem::RemoteFetcher.new nil
150
151    e = assert_raises ArgumentError do
152      fetcher.fetch_size 'gems.example.com/yaml'
153    end
154
155    assert_equal 'uri scheme is invalid: nil', e.message
156  end
157
158  def test_fetch_size_socket_error
159    fetcher = Gem::RemoteFetcher.new nil
160    def fetcher.connection_for(uri)
161      raise SocketError, "tarded"
162    end
163
164    uri = 'http://gems.example.com/yaml'
165    e = assert_raises Gem::RemoteFetcher::FetchError do
166      fetcher.fetch_size uri
167    end
168
169    assert_equal "SocketError: tarded (#{uri})", e.message
170  end
171
172  def test_no_proxy
173    use_ui @ui do
174      assert_data_from_server @fetcher.fetch_path(@server_uri)
175      assert_equal SERVER_DATA.size, @fetcher.fetch_size(@server_uri)
176    end
177  end
178
179  def test_api_endpoint
180    uri = URI.parse "http://gems.example.com/foo"
181    target = MiniTest::Mock.new
182    target.expect :target, "http://blah.com"
183
184    dns = MiniTest::Mock.new
185    dns.expect :getresource, target, [String, Object]
186
187    fetch = Gem::RemoteFetcher.new nil, dns
188    assert_equal URI.parse("http://blah.com/foo"), fetch.api_endpoint(uri)
189
190    target.verify
191    dns.verify
192  end
193
194  def test_cache_update_path
195    uri = URI 'http://example/file'
196    path = File.join @tempdir, 'file'
197
198    fetcher = util_fuck_with_fetcher 'hello'
199
200    data = fetcher.cache_update_path uri, path
201
202    assert_equal 'hello', data
203
204    assert_equal 'hello', File.read(path)
205  end
206
207  def test_cache_update_path_no_update
208    uri = URI 'http://example/file'
209    path = File.join @tempdir, 'file'
210
211    fetcher = util_fuck_with_fetcher 'hello'
212
213    data = fetcher.cache_update_path uri, path, false
214
215    assert_equal 'hello', data
216
217    refute_path_exists path
218  end
219
220  def util_fuck_with_fetcher data, blow = false
221    fetcher = Gem::RemoteFetcher.fetcher
222    fetcher.instance_variable_set :@test_data, data
223
224    unless blow then
225      def fetcher.fetch_path arg
226        @test_arg = arg
227        @test_data
228      end
229    else
230      def fetcher.fetch_path arg
231        # OMG I'm such an ass
232        class << self; remove_method :fetch_path; end
233        def self.fetch_path arg
234          @test_arg = arg
235          @test_data
236        end
237
238        raise Gem::RemoteFetcher::FetchError.new("haha!", nil)
239      end
240    end
241
242    fetcher
243  end
244
245  def test_download
246    a1_data = nil
247    File.open @a1_gem, 'rb' do |fp|
248      a1_data = fp.read
249    end
250
251    fetcher = util_fuck_with_fetcher a1_data
252
253    a1_cache_gem = @a1.cache_file
254    assert_equal a1_cache_gem, fetcher.download(@a1, 'http://gems.example.com')
255    assert_equal("http://gems.example.com/gems/a-1.gem",
256                 fetcher.instance_variable_get(:@test_arg).to_s)
257    assert File.exist?(a1_cache_gem)
258  end
259
260  def test_download_cached
261    FileUtils.mv @a1_gem, @cache_dir
262
263    inst = Gem::RemoteFetcher.fetcher
264
265    assert_equal @a1.cache_file, inst.download(@a1, 'http://gems.example.com')
266  end
267
268  def test_download_local
269    FileUtils.mv @a1_gem, @tempdir
270    local_path = File.join @tempdir, @a1.file_name
271    inst = nil
272
273    Dir.chdir @tempdir do
274      inst = Gem::RemoteFetcher.fetcher
275    end
276
277    assert_equal @a1.cache_file, inst.download(@a1, local_path)
278  end
279
280  def test_download_local_space
281    space_path = File.join @tempdir, 'space path'
282    FileUtils.mkdir space_path
283    FileUtils.mv @a1_gem, space_path
284    local_path = File.join space_path, @a1.file_name
285    inst = nil
286
287    Dir.chdir @tempdir do
288      inst = Gem::RemoteFetcher.fetcher
289    end
290
291    assert_equal @a1.cache_file, inst.download(@a1, local_path)
292  end
293
294  def test_download_install_dir
295    a1_data = File.open @a1_gem, 'rb' do |fp|
296      fp.read
297    end
298
299    fetcher = util_fuck_with_fetcher a1_data
300
301    install_dir = File.join @tempdir, 'more_gems'
302
303    a1_cache_gem = File.join install_dir, "cache", @a1.file_name
304    FileUtils.mkdir_p(File.dirname(a1_cache_gem))
305    actual = fetcher.download(@a1, 'http://gems.example.com', install_dir)
306
307    assert_equal a1_cache_gem, actual
308    assert_equal("http://gems.example.com/gems/a-1.gem",
309                 fetcher.instance_variable_get(:@test_arg).to_s)
310
311    assert File.exist?(a1_cache_gem)
312  end
313
314  unless win_platform? # File.chmod doesn't work
315    def test_download_local_read_only
316      FileUtils.mv @a1_gem, @tempdir
317      local_path = File.join @tempdir, @a1.file_name
318      inst = nil
319      FileUtils.chmod 0555, @a1.cache_dir
320
321      Dir.chdir @tempdir do
322        inst = Gem::RemoteFetcher.fetcher
323      end
324
325      assert_equal(File.join(@tempdir, @a1.file_name),
326                   inst.download(@a1, local_path))
327    ensure
328      FileUtils.chmod 0755, @a1.cache_dir
329    end
330
331    def test_download_read_only
332      FileUtils.chmod 0555, @a1.cache_dir
333      FileUtils.chmod 0555, @gemhome
334
335      fetcher = util_fuck_with_fetcher File.read(@a1_gem)
336      fetcher.download(@a1, 'http://gems.example.com')
337      a1_cache_gem = File.join Gem.user_dir, "cache", @a1.file_name
338      assert File.exist? a1_cache_gem
339    ensure
340      FileUtils.chmod 0755, @gemhome
341      FileUtils.chmod 0755, @a1.cache_dir
342    end
343  end
344
345  def test_download_platform_legacy
346    original_platform = 'old-platform'
347
348    e1, e1_gem = util_gem 'e', '1' do |s|
349      s.platform = Gem::Platform::CURRENT
350      s.instance_variable_set :@original_platform, original_platform
351    end
352    e1.loaded_from = File.join(@gemhome, 'specifications', e1.full_name)
353
354    e1_data = nil
355    File.open e1_gem, 'rb' do |fp|
356      e1_data = fp.read
357    end
358
359    fetcher = util_fuck_with_fetcher e1_data, :blow_chunks
360
361    e1_cache_gem = e1.cache_file
362
363    assert_equal e1_cache_gem, fetcher.download(e1, 'http://gems.example.com')
364
365    assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem",
366                 fetcher.instance_variable_get(:@test_arg).to_s)
367    assert File.exist?(e1_cache_gem)
368  end
369
370  def test_download_same_file
371    FileUtils.mv @a1_gem, @tempdir
372    local_path = File.join @tempdir, @a1.file_name
373    inst = nil
374
375    Dir.chdir @tempdir do
376      inst = Gem::RemoteFetcher.fetcher
377    end
378
379    cache_path = @a1.cache_file
380    FileUtils.mv local_path, cache_path
381
382    gem = Gem::Package.new cache_path
383
384    assert_equal cache_path, inst.download(gem.spec, cache_path)
385  end
386
387  def test_download_unsupported
388    inst = Gem::RemoteFetcher.fetcher
389
390    e = assert_raises ArgumentError do
391      inst.download @a1, 'ftp://gems.rubyforge.org'
392    end
393
394    assert_equal 'unsupported URI scheme ftp', e.message
395  end
396
397  def test_download_to_cache
398    @a2, @a2_gem = util_gem 'a', '2'
399
400    util_setup_spec_fetcher @a1, @a2
401    @fetcher.instance_variable_set :@a1, @a1
402    @fetcher.instance_variable_set :@a2, @a2
403    def @fetcher.fetch_path uri, mtime = nil, head = false
404      case uri.request_uri
405      when /#{@a1.spec_name}/ then
406        Gem.deflate Marshal.dump @a1
407      when /#{@a2.spec_name}/ then
408        Gem.deflate Marshal.dump @a2
409      else
410        uri.to_s
411      end
412    end
413
414    gem = Gem::RemoteFetcher.fetcher.download_to_cache dep 'a'
415
416    assert_equal @a2.file_name, File.basename(gem)
417  end
418
419  def test_explicit_proxy
420    use_ui @ui do
421      fetcher = Gem::RemoteFetcher.new @proxy_uri
422      assert_equal PROXY_DATA.size, fetcher.fetch_size(@server_uri)
423      assert_data_from_proxy fetcher.fetch_path(@server_uri)
424    end
425  end
426
427  def test_explicit_proxy_with_user_auth
428    use_ui @ui do
429      uri = URI.parse @proxy_uri
430      uri.user, uri.password = 'foo', 'bar'
431      fetcher = Gem::RemoteFetcher.new uri.to_s
432      proxy = fetcher.instance_variable_get("@proxy_uri")
433      assert_equal 'foo', proxy.user
434      assert_equal 'bar', proxy.password
435      assert_data_from_proxy fetcher.fetch_path(@server_uri)
436    end
437
438    use_ui @ui do
439      uri = URI.parse @proxy_uri
440      uri.user, uri.password = 'domain%5Cuser', 'bar'
441      fetcher = Gem::RemoteFetcher.new uri.to_s
442      proxy = fetcher.instance_variable_get("@proxy_uri")
443      assert_equal 'domain\user', fetcher.unescape_auth_info(proxy.user)
444      assert_equal 'bar', proxy.password
445      assert_data_from_proxy fetcher.fetch_path(@server_uri)
446    end
447
448    use_ui @ui do
449      uri = URI.parse @proxy_uri
450      uri.user, uri.password = 'user', 'my%20pass'
451      fetcher = Gem::RemoteFetcher.new uri.to_s
452      proxy = fetcher.instance_variable_get("@proxy_uri")
453      assert_equal 'user', proxy.user
454      assert_equal 'my pass', fetcher.unescape_auth_info(proxy.password)
455      assert_data_from_proxy fetcher.fetch_path(@server_uri)
456    end
457  end
458
459  def test_explicit_proxy_with_user_auth_in_env
460    use_ui @ui do
461      ENV['http_proxy'] = @proxy_uri
462      ENV['http_proxy_user'] = 'foo'
463      ENV['http_proxy_pass'] = 'bar'
464      fetcher = Gem::RemoteFetcher.new nil
465      proxy = fetcher.instance_variable_get("@proxy_uri")
466      assert_equal 'foo', proxy.user
467      assert_equal 'bar', proxy.password
468      assert_data_from_proxy fetcher.fetch_path(@server_uri)
469    end
470
471    use_ui @ui do
472      ENV['http_proxy'] = @proxy_uri
473      ENV['http_proxy_user'] = 'foo\user'
474      ENV['http_proxy_pass'] = 'my bar'
475      fetcher = Gem::RemoteFetcher.new nil
476      proxy = fetcher.instance_variable_get("@proxy_uri")
477      assert_equal 'foo\user', fetcher.unescape_auth_info(proxy.user)
478      assert_equal 'my bar', fetcher.unescape_auth_info(proxy.password)
479      assert_data_from_proxy fetcher.fetch_path(@server_uri)
480    end
481
482    use_ui @ui do
483      ENV['http_proxy'] = @proxy_uri
484      ENV['http_proxy_user'] = 'foo@user'
485      ENV['http_proxy_pass'] = 'my@bar'
486      fetcher = Gem::RemoteFetcher.new nil
487      proxy = fetcher.instance_variable_get("@proxy_uri")
488      assert_equal 'foo@user', fetcher.unescape_auth_info(proxy.user)
489      assert_equal 'my@bar', fetcher.unescape_auth_info(proxy.password)
490      assert_data_from_proxy fetcher.fetch_path(@server_uri)
491    end
492  end
493
494  def test_fetch_path_gzip
495    fetcher = Gem::RemoteFetcher.new nil
496
497    def fetcher.fetch_http(uri, mtime, head = nil)
498      Gem.gzip 'foo'
499    end
500
501    assert_equal 'foo', fetcher.fetch_path(@uri + 'foo.gz')
502  end
503
504  def test_fetch_path_gzip_unmodified
505    fetcher = Gem::RemoteFetcher.new nil
506
507    def fetcher.fetch_http(uri, mtime, head = nil)
508      nil
509    end
510
511    assert_equal nil, fetcher.fetch_path(@uri + 'foo.gz', Time.at(0))
512  end
513
514  def test_fetch_path_io_error
515    fetcher = Gem::RemoteFetcher.new nil
516
517    def fetcher.fetch_http(*)
518      raise EOFError
519    end
520
521    url = 'http://example.com/uri'
522
523    e = assert_raises Gem::RemoteFetcher::FetchError do
524      fetcher.fetch_path url
525    end
526
527    assert_equal "EOFError: EOFError (#{url})", e.message
528    assert_equal url, e.uri
529  end
530
531  def test_fetch_path_socket_error
532    fetcher = Gem::RemoteFetcher.new nil
533
534    def fetcher.fetch_http(uri, mtime, head = nil)
535      raise SocketError
536    end
537
538    url = 'http://example.com/uri'
539
540    e = assert_raises Gem::RemoteFetcher::FetchError do
541      fetcher.fetch_path url
542    end
543
544    assert_equal "SocketError: SocketError (#{url})", e.message
545    assert_equal url, e.uri
546  end
547
548  def test_fetch_path_system_call_error
549    fetcher = Gem::RemoteFetcher.new nil
550
551    def fetcher.fetch_http(uri, mtime = nil, head = nil)
552      raise Errno::ECONNREFUSED, 'connect(2)'
553    end
554
555    url = 'http://example.com/uri'
556
557    e = assert_raises Gem::RemoteFetcher::FetchError do
558      fetcher.fetch_path url
559    end
560
561    assert_match %r|ECONNREFUSED:.*connect\(2\) \(#{Regexp.escape url}\)\z|,
562                 e.message
563    assert_equal url, e.uri
564  end
565
566  def test_fetch_path_unmodified
567    fetcher = Gem::RemoteFetcher.new nil
568
569    def fetcher.fetch_http(uri, mtime, head = nil)
570      nil
571    end
572
573    assert_equal nil, fetcher.fetch_path(URI.parse(@gem_repo), Time.at(0))
574  end
575
576  def test_get_proxy_from_env_auto_normalizes
577    fetcher = Gem::RemoteFetcher.new(nil)
578    ENV['HTTP_PROXY'] = 'fakeurl:12345'
579
580    assert_equal('http://fakeurl:12345', fetcher.get_proxy_from_env.to_s)
581  end
582
583  def test_get_proxy_from_env_empty
584    ENV['HTTP_PROXY'] = ''
585    ENV.delete 'http_proxy'
586
587    fetcher = Gem::RemoteFetcher.new nil
588
589    assert_equal nil, fetcher.send(:get_proxy_from_env)
590  end
591
592  def test_implicit_no_proxy
593    use_ui @ui do
594      ENV['http_proxy'] = 'http://fakeurl:12345'
595      fetcher = Gem::RemoteFetcher.new :no_proxy
596      assert_data_from_server fetcher.fetch_path(@server_uri)
597    end
598  end
599
600  def test_implicit_proxy
601    use_ui @ui do
602      ENV['http_proxy'] = @proxy_uri
603      fetcher = Gem::RemoteFetcher.new nil
604      assert_data_from_proxy fetcher.fetch_path(@server_uri)
605    end
606  end
607
608  def test_implicit_upper_case_proxy
609    use_ui @ui do
610      ENV['HTTP_PROXY'] = @proxy_uri
611      fetcher = Gem::RemoteFetcher.new nil
612      assert_data_from_proxy fetcher.fetch_path(@server_uri)
613    end
614  end
615
616  def test_implicit_proxy_no_env
617    use_ui @ui do
618      fetcher = Gem::RemoteFetcher.new nil
619      assert_data_from_server fetcher.fetch_path(@server_uri)
620    end
621  end
622
623  def test_fetch_http
624    fetcher = Gem::RemoteFetcher.new nil
625    url = 'http://gems.example.com/redirect'
626
627    conn = Object.new
628    def conn.started?() true end
629    def conn.request(req)
630      url = 'http://gems.example.com/redirect'
631      unless defined? @requested then
632        @requested = true
633        res = Net::HTTPMovedPermanently.new nil, 301, nil
634        res.add_field 'Location', url
635        res
636      else
637        res = Net::HTTPOK.new nil, 200, nil
638        def res.body() 'real_path' end
639        res
640      end
641    end
642
643    conn = { "#{Thread.current.object_id}:gems.example.com:80" => conn }
644    fetcher.instance_variable_set :@connections, conn
645
646    data = fetcher.fetch_http URI.parse(url)
647
648    assert_equal 'real_path', data
649  end
650
651  def test_fetch_http_redirects
652    fetcher = Gem::RemoteFetcher.new nil
653    url = 'http://gems.example.com/redirect'
654
655    conn = Object.new
656    def conn.started?() true end
657    def conn.request(req)
658      url = 'http://gems.example.com/redirect'
659      res = Net::HTTPMovedPermanently.new nil, 301, nil
660      res.add_field 'Location', url
661      res
662    end
663
664    conn = { "#{Thread.current.object_id}:gems.example.com:80" => conn }
665    fetcher.instance_variable_set :@connections, conn
666
667    e = assert_raises Gem::RemoteFetcher::FetchError do
668      fetcher.fetch_http URI.parse(url)
669    end
670
671    assert_equal "too many redirects (#{url})", e.message
672  end
673
674  def test_normalize_uri
675    assert_equal 'FILE://example/',  @fetcher.normalize_uri('FILE://example/')
676    assert_equal 'FTP://example/',   @fetcher.normalize_uri('FTP://example/')
677    assert_equal 'HTTP://example/',  @fetcher.normalize_uri('HTTP://example/')
678    assert_equal 'HTTPS://example/', @fetcher.normalize_uri('HTTPS://example/')
679    assert_equal 'http://example/',  @fetcher.normalize_uri('example/')
680  end
681
682  def test_observe_no_proxy_env_single_host
683    use_ui @ui do
684      ENV["http_proxy"] = @proxy_uri
685      ENV["no_proxy"] = URI::parse(@server_uri).host
686      fetcher = Gem::RemoteFetcher.new nil
687      assert_data_from_server fetcher.fetch_path(@server_uri)
688    end
689  end
690
691  def test_observe_no_proxy_env_list
692    use_ui @ui do
693      ENV["http_proxy"] = @proxy_uri
694      ENV["no_proxy"] = "fakeurl.com, #{URI::parse(@server_uri).host}"
695      fetcher = Gem::RemoteFetcher.new nil
696      assert_data_from_server fetcher.fetch_path(@server_uri)
697    end
698  end
699
700  def test_request
701    uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
702    util_stub_connection_for :body => :junk, :code => 200
703
704    response = @fetcher.request uri, Net::HTTP::Get
705
706    assert_equal 200, response.code
707    assert_equal :junk, response.body
708  end
709
710  def test_request_head
711    uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
712    util_stub_connection_for :body => '', :code => 200
713    response = @fetcher.request uri, Net::HTTP::Head
714
715    assert_equal 200, response.code
716    assert_equal '', response.body
717  end
718
719  def test_request_unmodified
720    uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
721    conn = util_stub_connection_for :body => '', :code => 304
722
723    t = Time.now
724    response = @fetcher.request uri, Net::HTTP::Head, t
725
726    assert_equal 304, response.code
727    assert_equal '', response.body
728
729    assert_equal t.rfc2822, conn.payload['if-modified-since']
730  end
731
732  def test_user_agent
733    ua = @fetcher.user_agent
734
735    assert_match %r%^RubyGems/\S+ \S+ Ruby/\S+ \(.*?\)%,          ua
736    assert_match %r%RubyGems/#{Regexp.escape Gem::VERSION}%,      ua
737    assert_match %r% #{Regexp.escape Gem::Platform.local.to_s} %, ua
738    assert_match %r%Ruby/#{Regexp.escape RUBY_VERSION}%,          ua
739    assert_match %r%\(#{Regexp.escape RUBY_RELEASE_DATE} %,       ua
740  end
741
742  def test_user_agent_engine
743    util_save_version
744
745    Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
746    Object.send :const_set,    :RUBY_ENGINE, 'vroom'
747
748    ua = @fetcher.user_agent
749
750    assert_match %r%\) vroom%, ua
751  ensure
752    util_restore_version
753  end
754
755  def test_user_agent_engine_ruby
756    util_save_version
757
758    Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
759    Object.send :const_set,    :RUBY_ENGINE, 'ruby'
760
761    ua = @fetcher.user_agent
762
763    assert_match %r%\)%, ua
764  ensure
765    util_restore_version
766  end
767
768  def test_user_agent_patchlevel
769    util_save_version
770
771    Object.send :remove_const, :RUBY_PATCHLEVEL
772    Object.send :const_set,    :RUBY_PATCHLEVEL, 5
773
774    ua = @fetcher.user_agent
775
776    assert_match %r% patchlevel 5\)%, ua
777  ensure
778    util_restore_version
779  end
780
781  def test_user_agent_revision
782    util_save_version
783
784    Object.send :remove_const, :RUBY_PATCHLEVEL
785    Object.send :const_set,    :RUBY_PATCHLEVEL, -1
786    Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
787    Object.send :const_set,    :RUBY_REVISION, 6
788
789    ua = @fetcher.user_agent
790
791    assert_match %r% revision 6\)%, ua
792    assert_match %r%Ruby/#{Regexp.escape RUBY_VERSION}dev%, ua
793  ensure
794    util_restore_version
795  end
796
797  def test_user_agent_revision_missing
798    util_save_version
799
800    Object.send :remove_const, :RUBY_PATCHLEVEL
801    Object.send :const_set,    :RUBY_PATCHLEVEL, -1
802    Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
803
804    ua = @fetcher.user_agent
805
806    assert_match %r%\(#{Regexp.escape RUBY_RELEASE_DATE}\)%, ua
807  ensure
808    util_restore_version
809  end
810
811  def test_yaml_error_on_size
812    use_ui @ui do
813      self.class.enable_yaml = false
814      fetcher = Gem::RemoteFetcher.new nil
815      assert_error { fetcher.size }
816    end
817  end
818
819  def test_ssl_connection
820    ssl_server = self.class.start_ssl_server
821    temp_ca_cert = File.join(DIR, 'ca_cert.pem')
822    with_configured_fetcher(":ssl_ca_cert: #{temp_ca_cert}") do |fetcher|
823      fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml")
824    end
825  end
826
827  def test_do_not_allow_insecure_ssl_connection_by_default
828    ssl_server = self.class.start_ssl_server
829    with_configured_fetcher do |fetcher|
830      assert_raises Gem::RemoteFetcher::FetchError do
831        fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml")
832      end
833    end
834  end
835
836  def test_ssl_connection_allow_verify_none
837    ssl_server = self.class.start_ssl_server
838    with_configured_fetcher(":ssl_verify_mode: 0") do |fetcher|
839      fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml")
840    end
841  end
842
843  def test_do_not_follow_insecure_redirect
844    ssl_server = self.class.start_ssl_server
845    temp_ca_cert = File.join(DIR, 'ca_cert.pem'),
846    with_configured_fetcher(":ssl_ca_cert: #{temp_ca_cert}") do |fetcher|
847      assert_raises Gem::RemoteFetcher::FetchError do
848        fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/insecure_redirect?to=#{@server_uri}")
849      end
850    end
851  end
852
853  def with_configured_fetcher(config_str = nil, &block)
854    if config_str
855      temp_conf = File.join @tempdir, '.gemrc'
856      File.open temp_conf, 'w' do |fp|
857        fp.puts config_str
858      end
859      Gem.configuration = Gem::ConfigFile.new %W[--config-file #{temp_conf}]
860    end
861    yield Gem::RemoteFetcher.new
862  ensure
863    Gem.configuration = nil
864  end
865
866  def util_stub_connection_for hash
867    def @fetcher.connection= conn
868      @conn = conn
869    end
870
871    def @fetcher.connection_for uri
872      @conn
873    end
874
875    @fetcher.connection = Conn.new OpenStruct.new(hash)
876  end
877
878  def assert_error(exception_class=Exception)
879    got_exception = false
880
881    begin
882      yield
883    rescue exception_class
884      got_exception = true
885    end
886
887    assert got_exception, "Expected exception conforming to #{exception_class}"
888  end
889
890  def assert_data_from_server(data)
891    assert_match(/0\.4\.11/, data, "Data is not from server")
892  end
893
894  def assert_data_from_proxy(data)
895    assert_match(/0\.4\.2/, data, "Data is not from proxy")
896  end
897
898  class Conn
899    attr_accessor :payload
900
901    def initialize(response)
902      @response = response
903      self.payload = nil
904    end
905
906    def request(req)
907      self.payload = req
908      @response
909    end
910  end
911
912  class NilLog < WEBrick::Log
913    def log(level, data) #Do nothing
914    end
915  end
916
917  class << self
918    attr_reader :normal_server, :proxy_server
919    attr_accessor :enable_zip, :enable_yaml
920
921    def start_servers
922      @normal_server ||= start_server(SERVER_DATA)
923      @proxy_server  ||= start_server(PROXY_DATA)
924      @enable_yaml = true
925      @enable_zip = false
926    end
927
928    def normal_server_port
929      @normal_server[:server].config[:Port]
930    end
931
932    def proxy_server_port
933      @proxy_server[:server].config[:Port]
934    end
935
936    DIR = File.expand_path(File.dirname(__FILE__))
937    DH_PARAM = OpenSSL::PKey::DH.new(128)
938
939    def start_ssl_server(config = {})
940      null_logger = NilLog.new
941      server = WEBrick::HTTPServer.new({
942        :Port => 0,
943        :Logger => null_logger,
944        :AccessLog => [],
945        :SSLEnable => true,
946        :SSLCACertificateFile => File.join(DIR, 'ca_cert.pem'),
947        :SSLCertificate => cert('ssl_cert.pem'),
948        :SSLPrivateKey => key('ssl_key.pem'),
949        :SSLVerifyClient => nil,
950        :SSLCertName => nil
951      }.merge(config))
952      server.mount_proc("/yaml") { |req, res|
953        res.body = "--- true\n"
954      }
955      server.mount_proc("/insecure_redirect") { |req, res|
956        res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, req.query['to'])
957      }
958      server.ssl_context.tmp_dh_callback = proc { DH_PARAM }
959      t = Thread.new do
960        begin
961          server.start
962        rescue Exception => ex
963          abort ex.message
964          puts "ERROR during server thread: #{ex.message}"
965        end
966      end
967      while server.status != :Running
968        sleep 0.1
969        unless t.alive?
970          t.join
971          raise
972        end
973      end
974      server
975    end
976
977
978
979    private
980
981    def start_server(data)
982      null_logger = NilLog.new
983      s = WEBrick::HTTPServer.new(
984        :Port            => 0,
985        :DocumentRoot    => nil,
986        :Logger          => null_logger,
987        :AccessLog       => null_logger
988        )
989      s.mount_proc("/kill") { |req, res| s.shutdown }
990      s.mount_proc("/yaml") { |req, res|
991        if @enable_yaml
992          res.body = data
993          res['Content-Type'] = 'text/plain'
994          res['content-length'] = data.size
995        else
996          res.status = "404"
997          res.body = "<h1>NOT FOUND</h1>"
998          res['Content-Type'] = 'text/html'
999        end
1000      }
1001      s.mount_proc("/yaml.Z") { |req, res|
1002        if @enable_zip
1003          res.body = Zlib::Deflate.deflate(data)
1004          res['Content-Type'] = 'text/plain'
1005        else
1006          res.status = "404"
1007          res.body = "<h1>NOT FOUND</h1>"
1008          res['Content-Type'] = 'text/html'
1009        end
1010      }
1011      th = Thread.new do
1012        begin
1013          s.start
1014        rescue Exception => ex
1015          abort "ERROR during server thread: #{ex.message}"
1016        end
1017      end
1018      th[:server] = s
1019      th
1020    end
1021
1022    def cert(filename)
1023      OpenSSL::X509::Certificate.new(File.read(File.join(DIR, filename)))
1024    end
1025
1026    def key(filename)
1027      OpenSSL::PKey::RSA.new(File.read(File.join(DIR, filename)))
1028    end
1029  end
1030
1031  def test_correct_for_windows_path
1032    path = "/C:/WINDOWS/Temp/gems"
1033    assert_equal "C:/WINDOWS/Temp/gems", @fetcher.correct_for_windows_path(path)
1034
1035    path = "/home/skillet"
1036    assert_equal "/home/skillet", @fetcher.correct_for_windows_path(path)
1037  end
1038
1039  def util_save_version
1040    @orig_RUBY_ENGINE     = RUBY_ENGINE if defined? RUBY_ENGINE
1041    @orig_RUBY_PATCHLEVEL = RUBY_PATCHLEVEL
1042    @orig_RUBY_REVISION   = RUBY_REVISION if defined? RUBY_REVISION
1043  end
1044
1045  def util_restore_version
1046    Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
1047    Object.send :const_set,    :RUBY_ENGINE, @orig_RUBY_ENGINE if
1048      defined?(@orig_RUBY_ENGINE)
1049
1050    Object.send :remove_const, :RUBY_PATCHLEVEL
1051    Object.send :const_set,    :RUBY_PATCHLEVEL, @orig_RUBY_PATCHLEVEL
1052
1053    Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
1054    Object.send :const_set,    :RUBY_REVISION, @orig_RUBY_REVISION if
1055      defined?(@orig_RUBY_REVISION)
1056  end
1057
1058end
1059
1060