1require 'test/unit'
2require 'open-uri'
3require 'webrick'
4require 'webrick/httpproxy'
5begin
6  require 'zlib'
7rescue LoadError
8end
9
10class TestOpenURI < Test::Unit::TestCase
11
12  NullLog = Object.new
13  def NullLog.<<(arg)
14  end
15
16  def with_http
17    Dir.mktmpdir {|dr|
18      srv = WEBrick::HTTPServer.new({
19        :DocumentRoot => dr,
20        :ServerType => Thread,
21        :Logger => WEBrick::Log.new(NullLog),
22        :AccessLog => [[NullLog, ""]],
23        :BindAddress => '127.0.0.1',
24        :Port => 0})
25      _, port, _, host = srv.listeners[0].addr
26      begin
27        srv.start
28        yield srv, dr, "http://#{host}:#{port}"
29      ensure
30        srv.shutdown
31        until srv.status == :Stop
32          sleep 0.1
33        end
34      end
35    }
36  end
37
38  def with_env(h)
39    begin
40      old = {}
41      h.each_key {|k| old[k] = ENV[k] }
42      h.each {|k, v| ENV[k] = v }
43      yield
44    ensure
45      h.each_key {|k| ENV[k] = old[k] }
46    end
47  end
48
49  def setup
50    @proxies = %w[http_proxy HTTP_PROXY ftp_proxy FTP_PROXY no_proxy]
51    @old_proxies = @proxies.map {|k| ENV[k] }
52    @proxies.each {|k| ENV[k] = nil }
53  end
54
55  def teardown
56    @proxies.each_with_index {|k, i| ENV[k] = @old_proxies[i] }
57  end
58
59  def test_200
60    with_http {|srv, dr, url|
61      srv.mount_proc("/foo200", lambda { |req, res| res.body = "foo200" } )
62      open("#{url}/foo200") {|f|
63        assert_equal("200", f.status[0])
64        assert_equal("foo200", f.read)
65      }
66    }
67  end
68
69  def test_200big
70    with_http {|srv, dr, url|
71      content = "foo200big"*10240
72      srv.mount_proc("/foo200big", lambda { |req, res| res.body = content } )
73      open("#{url}/foo200big") {|f|
74        assert_equal("200", f.status[0])
75        assert_equal(content, f.read)
76      }
77    }
78  end
79
80  def test_404
81    with_http {|srv, dr, url|
82      exc = assert_raise(OpenURI::HTTPError) { open("#{url}/not-exist") {} }
83      assert_equal("404", exc.io.status[0])
84    }
85  end
86
87  def test_open_uri
88    with_http {|srv, dr, url|
89      srv.mount_proc("/foo_ou", lambda { |req, res| res.body = "foo_ou" } )
90      u = URI("#{url}/foo_ou")
91      open(u) {|f|
92        assert_equal("200", f.status[0])
93        assert_equal("foo_ou", f.read)
94      }
95    }
96  end
97
98  def test_open_too_many_arg
99    assert_raise(ArgumentError) { open("http://192.0.2.1/tma", "r", 0666, :extra) {} }
100  end
101
102  def test_read_timeout
103    TCPServer.open("127.0.0.1", 0) {|serv|
104      port = serv.addr[1]
105      th = Thread.new {
106        sock = serv.accept
107        begin
108          req = sock.gets("\r\n\r\n")
109          assert_match(%r{\AGET /foo/bar }, req)
110          sock.print "HTTP/1.0 200 OK\r\n"
111          sock.print "Content-Length: 4\r\n\r\n"
112          sleep 1
113          sock.print "ab\r\n"
114        ensure
115          sock.close
116        end
117      }
118      begin
119        assert_raise(Net::ReadTimeout) { URI("http://127.0.0.1:#{port}/foo/bar").read(:read_timeout=>0.1) }
120      ensure
121        Thread.kill(th)
122        th.join
123      end
124    }
125  end
126
127  def test_invalid_option
128    assert_raise(ArgumentError) { open("http://127.0.0.1/", :invalid_option=>true) {} }
129  end
130
131  def test_mode
132    with_http {|srv, dr, url|
133      srv.mount_proc("/mode", lambda { |req, res| res.body = "mode" } )
134      open("#{url}/mode", "r") {|f|
135        assert_equal("200", f.status[0])
136        assert_equal("mode", f.read)
137      }
138      open("#{url}/mode", "r", 0600) {|f|
139        assert_equal("200", f.status[0])
140        assert_equal("mode", f.read)
141      }
142      assert_raise(ArgumentError) { open("#{url}/mode", "a") {} }
143      open("#{url}/mode", "r:us-ascii") {|f|
144        assert_equal(Encoding::US_ASCII, f.read.encoding)
145      }
146      open("#{url}/mode", "r:utf-8") {|f|
147        assert_equal(Encoding::UTF_8, f.read.encoding)
148      }
149      assert_raise(ArgumentError) { open("#{url}/mode", "r:invalid-encoding") {} }
150    }
151  end
152
153  def test_without_block
154    with_http {|srv, dr, url|
155      srv.mount_proc("/without_block", lambda { |req, res| res.body = "without_block" } )
156      begin
157        f = open("#{url}/without_block")
158        assert_equal("200", f.status[0])
159        assert_equal("without_block", f.read)
160      ensure
161        f.close if f && !f.closed?
162      end
163    }
164  end
165
166  def test_header
167    myheader1 = 'barrrr'
168    myheader2 = nil
169    with_http {|srv, dr, url|
170      srv.mount_proc("/h/") {|req, res| myheader2 = req['myheader']; res.body = "foo" }
171      open("#{url}/h/", 'MyHeader'=>myheader1) {|f|
172        assert_equal("foo", f.read)
173        assert_equal(myheader1, myheader2)
174      }
175    }
176  end
177
178  def test_multi_proxy_opt
179    assert_raise(ArgumentError) {
180      open("http://127.0.0.1/", :proxy_http_basic_authentication=>true, :proxy=>true) {}
181    }
182  end
183
184  def test_non_http_proxy
185    assert_raise(RuntimeError) {
186      open("http://127.0.0.1/", :proxy=>URI("ftp://127.0.0.1/")) {}
187    }
188  end
189
190  def test_proxy
191    with_http {|srv, dr, url|
192      log = ''
193      proxy = WEBrick::HTTPProxyServer.new({
194        :ServerType => Thread,
195        :Logger => WEBrick::Log.new(NullLog),
196        :AccessLog => [[NullLog, ""]],
197        :ProxyAuthProc => lambda {|req, res|
198          log << req.request_line
199        },
200        :BindAddress => '127.0.0.1',
201        :Port => 0})
202      _, proxy_port, _, proxy_host = proxy.listeners[0].addr
203      proxy_url = "http://#{proxy_host}:#{proxy_port}/"
204      begin
205        proxy.start
206        srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } )
207        open("#{url}/proxy", :proxy=>proxy_url) {|f|
208          assert_equal("200", f.status[0])
209          assert_equal("proxy", f.read)
210        }
211        assert_match(/#{Regexp.quote url}/, log); log.clear
212        open("#{url}/proxy", :proxy=>URI(proxy_url)) {|f|
213          assert_equal("200", f.status[0])
214          assert_equal("proxy", f.read)
215        }
216        assert_match(/#{Regexp.quote url}/, log); log.clear
217        open("#{url}/proxy", :proxy=>nil) {|f|
218          assert_equal("200", f.status[0])
219          assert_equal("proxy", f.read)
220        }
221        assert_equal("", log); log.clear
222        assert_raise(ArgumentError) {
223          open("#{url}/proxy", :proxy=>:invalid) {}
224        }
225        assert_equal("", log); log.clear
226        with_env("http_proxy"=>proxy_url) {
227          # should not use proxy for 127.0.0.0/8.
228          open("#{url}/proxy") {|f|
229            assert_equal("200", f.status[0])
230            assert_equal("proxy", f.read)
231          }
232        }
233        assert_equal("", log); log.clear
234      ensure
235        proxy.shutdown
236      end
237    }
238  end
239
240  def test_proxy_http_basic_authentication
241    with_http {|srv, dr, url|
242      log = ''
243      proxy = WEBrick::HTTPProxyServer.new({
244        :ServerType => Thread,
245        :Logger => WEBrick::Log.new(NullLog),
246        :AccessLog => [[NullLog, ""]],
247        :ProxyAuthProc => lambda {|req, res|
248          log << req.request_line
249          if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}"
250            raise WEBrick::HTTPStatus::ProxyAuthenticationRequired
251          end
252        },
253        :BindAddress => '127.0.0.1',
254        :Port => 0})
255      _, proxy_port, _, proxy_host = proxy.listeners[0].addr
256      proxy_url = "http://#{proxy_host}:#{proxy_port}/"
257      begin
258        proxy.start
259        srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } )
260        exc = assert_raise(OpenURI::HTTPError) { open("#{url}/proxy", :proxy=>proxy_url) {} }
261        assert_equal("407", exc.io.status[0])
262        assert_match(/#{Regexp.quote url}/, log); log.clear
263        open("#{url}/proxy",
264            :proxy_http_basic_authentication=>[proxy_url, "user", "pass"]) {|f|
265          assert_equal("200", f.status[0])
266          assert_equal("proxy", f.read)
267        }
268        assert_match(/#{Regexp.quote url}/, log); log.clear
269        assert_raise(ArgumentError) {
270          open("#{url}/proxy",
271              :proxy_http_basic_authentication=>[true, "user", "pass"]) {}
272        }
273        assert_equal("", log); log.clear
274      ensure
275        proxy.shutdown
276      end
277    }
278  end
279
280  def test_redirect
281    with_http {|srv, dr, url|
282      srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" }
283      srv.mount_proc("/r2/") {|req, res| res.body = "r2" }
284      srv.mount_proc("/to-file/") {|req, res| res.status = 301; res["location"] = "file:///foo" }
285      open("#{url}/r1/") {|f|
286        assert_equal("#{url}/r2", f.base_uri.to_s)
287        assert_equal("r2", f.read)
288      }
289      assert_raise(OpenURI::HTTPRedirect) { open("#{url}/r1/", :redirect=>false) {} }
290      assert_raise(RuntimeError) { open("#{url}/to-file/") {} }
291    }
292  end
293
294  def test_redirect_loop
295    with_http {|srv, dr, url|
296      srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" }
297      srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r1"; res.body = "r2" }
298      assert_raise(RuntimeError) { open("#{url}/r1/") {} }
299    }
300  end
301
302  def test_redirect_relative
303    TCPServer.open("127.0.0.1", 0) {|serv|
304      port = serv.addr[1]
305      th = Thread.new {
306        sock = serv.accept
307        begin
308          req = sock.gets("\r\n\r\n")
309          assert_match(%r{\AGET /foo/bar }, req)
310          sock.print "HTTP/1.0 302 Found\r\n"
311          sock.print "Location: ../baz\r\n\r\n"
312        ensure
313          sock.close
314        end
315        sock = serv.accept
316        begin
317          req = sock.gets("\r\n\r\n")
318          assert_match(%r{\AGET /baz }, req)
319          sock.print "HTTP/1.0 200 OK\r\n"
320          sock.print "Content-Length: 4\r\n\r\n"
321          sock.print "ab\r\n"
322        ensure
323          sock.close
324        end
325      }
326      begin
327        content = URI("http://127.0.0.1:#{port}/foo/bar").read
328        assert_equal("ab\r\n", content)
329      ensure
330        Thread.kill(th)
331        th.join
332      end
333    }
334  end
335
336  def test_redirect_invalid
337    TCPServer.open("127.0.0.1", 0) {|serv|
338      port = serv.addr[1]
339      th = Thread.new {
340        sock = serv.accept
341        begin
342          req = sock.gets("\r\n\r\n")
343          assert_match(%r{\AGET /foo/bar }, req)
344          sock.print "HTTP/1.0 302 Found\r\n"
345          sock.print "Location: ::\r\n\r\n"
346        ensure
347          sock.close
348        end
349      }
350      begin
351        assert_raise(OpenURI::HTTPError) {
352          URI("http://127.0.0.1:#{port}/foo/bar").read
353        }
354      ensure
355        Thread.kill(th)
356        th.join
357      end
358    }
359  end
360
361  def test_redirect_auth
362    with_http {|srv, dr, url|
363      srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2" }
364      srv.mount_proc("/r2/") {|req, res|
365        if req["Authorization"] != "Basic #{['user:pass'].pack('m').chomp}"
366          raise WEBrick::HTTPStatus::Unauthorized
367        end
368        res.body = "r2"
369      }
370      exc = assert_raise(OpenURI::HTTPError) { open("#{url}/r2/") {} }
371      assert_equal("401", exc.io.status[0])
372      open("#{url}/r2/", :http_basic_authentication=>['user', 'pass']) {|f|
373        assert_equal("r2", f.read)
374      }
375      exc = assert_raise(OpenURI::HTTPError) { open("#{url}/r1/", :http_basic_authentication=>['user', 'pass']) {} }
376      assert_equal("401", exc.io.status[0])
377    }
378  end
379
380  def test_userinfo
381    if "1.9.0" <= RUBY_VERSION
382      assert_raise(ArgumentError) { open("http://user:pass@127.0.0.1/") {} }
383    end
384  end
385
386  def test_progress
387    with_http {|srv, dr, url|
388      content = "a" * 100000
389      srv.mount_proc("/data/") {|req, res| res.body = content }
390      length = []
391      progress = []
392      open("#{url}/data/",
393           :content_length_proc => lambda {|n| length << n },
394           :progress_proc => lambda {|n| progress << n }
395          ) {|f|
396        assert_equal(1, length.length)
397        assert_equal(content.length, length[0])
398        assert(progress.length>1,"maybe test is wrong")
399        assert(progress.sort == progress,"monotone increasing expected but was\n#{progress.inspect}")
400        assert_equal(content.length, progress[-1])
401        assert_equal(content, f.read)
402      }
403    }
404  end
405
406  def test_progress_chunked
407    with_http {|srv, dr, url|
408      content = "a" * 100000
409      srv.mount_proc("/data/") {|req, res| res.body = content; res.chunked = true }
410      length = []
411      progress = []
412      open("#{url}/data/",
413           :content_length_proc => lambda {|n| length << n },
414           :progress_proc => lambda {|n| progress << n }
415          ) {|f|
416        assert_equal(1, length.length)
417        assert_equal(nil, length[0])
418        assert(progress.length>1,"maybe test is worng")
419        assert(progress.sort == progress,"monotone increasing expected but was\n#{progress.inspect}")
420        assert_equal(content.length, progress[-1])
421        assert_equal(content, f.read)
422      }
423    }
424  end
425
426  def test_uri_read
427    with_http {|srv, dr, url|
428      srv.mount_proc("/uriread", lambda { |req, res| res.body = "uriread" } )
429      data = URI("#{url}/uriread").read
430      assert_equal("200", data.status[0])
431      assert_equal("uriread", data)
432    }
433  end
434
435  def test_encoding
436    with_http {|srv, dr, url|
437      content_u8 = "\u3042"
438      content_ej = "\xa2\xa4".force_encoding("euc-jp")
439      srv.mount_proc("/u8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset=utf-8' }
440      srv.mount_proc("/ej/") {|req, res| res.body = content_ej; res['content-type'] = 'TEXT/PLAIN; charset=EUC-JP' }
441      srv.mount_proc("/nc/") {|req, res| res.body = "aa"; res['content-type'] = 'Text/Plain' }
442      open("#{url}/u8/") {|f|
443        assert_equal(content_u8, f.read)
444        assert_equal("text/plain", f.content_type)
445        assert_equal("utf-8", f.charset)
446      }
447      open("#{url}/ej/") {|f|
448        assert_equal(content_ej, f.read)
449        assert_equal("text/plain", f.content_type)
450        assert_equal("euc-jp", f.charset)
451      }
452      open("#{url}/nc/") {|f|
453        assert_equal("aa", f.read)
454        assert_equal("text/plain", f.content_type)
455        assert_equal("iso-8859-1", f.charset)
456        assert_equal("unknown", f.charset { "unknown" })
457      }
458    }
459  end
460
461  def test_quoted_attvalue
462    with_http {|srv, dr, url|
463      content_u8 = "\u3042"
464      srv.mount_proc("/qu8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset="utf\-8"' }
465      open("#{url}/qu8/") {|f|
466        assert_equal(content_u8, f.read)
467        assert_equal("text/plain", f.content_type)
468        assert_equal("utf-8", f.charset)
469      }
470    }
471  end
472
473  def test_last_modified
474    with_http {|srv, dr, url|
475      srv.mount_proc("/data/") {|req, res| res.body = "foo"; res['last-modified'] = 'Fri, 07 Aug 2009 06:05:04 GMT' }
476      open("#{url}/data/") {|f|
477        assert_equal("foo", f.read)
478        assert_equal(Time.utc(2009,8,7,6,5,4), f.last_modified)
479      }
480    }
481  end
482
483  def test_content_encoding
484    with_http {|srv, dr, url|
485      content = "abc" * 10000
486      Zlib::GzipWriter.wrap(StringIO.new(content_gz="".force_encoding("ascii-8bit"))) {|z| z.write content }
487      srv.mount_proc("/data/") {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip' }
488      srv.mount_proc("/data2/") {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip'; res.chunked = true }
489      srv.mount_proc("/noce/") {|req, res| res.body = content_gz }
490      open("#{url}/data/") {|f|
491        assert_equal [], f.content_encoding
492        assert_equal(content, f.read)
493      }
494      open("#{url}/data2/") {|f|
495        assert_equal [], f.content_encoding
496        assert_equal(content, f.read)
497      }
498      open("#{url}/noce/") {|f|
499        assert_equal [], f.content_encoding
500        assert_equal(content_gz, f.read.force_encoding("ascii-8bit"))
501      }
502    }
503  end if defined?(Zlib::GzipWriter)
504
505  # 192.0.2.0/24 is TEST-NET.  [RFC3330]
506
507  def test_ftp_invalid_request
508    assert_raise(ArgumentError) { URI("ftp://127.0.0.1/").read }
509    assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Db").read }
510    assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Ab").read }
511    assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Db/f").read }
512    assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Ab/f").read }
513    assert_raise(URI::InvalidComponentError) { URI("ftp://127.0.0.1/d/f;type=x") }
514  end
515
516  def test_ftp
517    TCPServer.open("127.0.0.1", 0) {|serv|
518      _, port, _, host = serv.addr
519      th = Thread.new {
520        s = serv.accept
521        begin
522          s.print "220 Test FTP Server\r\n"
523          assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n"
524          assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n"
525          assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n"
526          assert_equal("CWD foo\r\n", s.gets); s.print "250 CWD successful\r\n"
527          assert_equal("PASV\r\n", s.gets)
528          TCPServer.open("127.0.0.1", 0) {|data_serv|
529            _, data_serv_port, _, _ = data_serv.addr
530            hi = data_serv_port >> 8
531            lo = data_serv_port & 0xff
532            s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n"
533            assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n"
534            data_sock = data_serv.accept
535            begin
536              data_sock << "content"
537            ensure
538              data_sock.close
539            end
540            s.print "226 transfer complete\r\n"
541            assert_nil(s.gets)
542          }
543        ensure
544          s.close if s
545        end
546      }
547      begin
548        content = URI("ftp://#{host}:#{port}/foo/bar").read
549        assert_equal("content", content)
550      ensure
551        Thread.kill(th)
552        th.join
553      end
554    }
555  end
556
557  def test_ftp_active
558    TCPServer.open("127.0.0.1", 0) {|serv|
559      _, port, _, host = serv.addr
560      th = Thread.new {
561        s = serv.accept
562        begin
563          content = "content"
564          s.print "220 Test FTP Server\r\n"
565          assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n"
566          assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n"
567          assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n"
568          assert_equal("CWD foo\r\n", s.gets); s.print "250 CWD successful\r\n"
569          assert(m = /\APORT 127,0,0,1,(\d+),(\d+)\r\n\z/.match(s.gets))
570          active_port = m[1].to_i << 8 | m[2].to_i
571          TCPSocket.open("127.0.0.1", active_port) {|data_sock|
572            s.print "200 data connection opened\r\n"
573            assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n"
574            begin
575              data_sock << content
576            ensure
577              data_sock.close
578            end
579            s.print "226 transfer complete\r\n"
580            assert_nil(s.gets)
581          }
582        ensure
583          s.close if s
584        end
585      }
586      begin
587        content = URI("ftp://#{host}:#{port}/foo/bar").read(:ftp_active_mode=>true)
588        assert_equal("content", content)
589      ensure
590        Thread.kill(th)
591        th.join
592      end
593    }
594  end
595
596  def test_ftp_ascii
597    TCPServer.open("127.0.0.1", 0) {|serv|
598      _, port, _, host = serv.addr
599      th = Thread.new {
600        s = serv.accept
601        begin
602          content = "content"
603          s.print "220 Test FTP Server\r\n"
604          assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n"
605          assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n"
606          assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n"
607          assert_equal("CWD /foo\r\n", s.gets); s.print "250 CWD successful\r\n"
608          assert_equal("TYPE A\r\n", s.gets); s.print "200 type set to A\r\n"
609          assert_equal("SIZE bar\r\n", s.gets); s.print "213 #{content.bytesize}\r\n"
610          assert_equal("PASV\r\n", s.gets)
611          TCPServer.open("127.0.0.1", 0) {|data_serv|
612            _, data_serv_port, _, _ = data_serv.addr
613            hi = data_serv_port >> 8
614            lo = data_serv_port & 0xff
615            s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n"
616            assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n"
617            data_sock = data_serv.accept
618            begin
619              data_sock << content
620            ensure
621              data_sock.close
622            end
623            s.print "226 transfer complete\r\n"
624            assert_nil(s.gets)
625          }
626        ensure
627          s.close if s
628        end
629      }
630      begin
631        length = []
632        progress = []
633        content = URI("ftp://#{host}:#{port}/%2Ffoo/b%61r;type=a").read(
634         :content_length_proc => lambda {|n| length << n },
635         :progress_proc => lambda {|n| progress << n })
636        assert_equal("content", content)
637        assert_equal([7], length)
638        assert_equal(7, progress.inject(&:+))
639      ensure
640        Thread.kill(th)
641        th.join
642      end
643    }
644  end
645
646  def test_ftp_over_http_proxy
647    TCPServer.open("127.0.0.1", 0) {|proxy_serv|
648      proxy_port = proxy_serv.addr[1]
649      th = Thread.new {
650        proxy_sock = proxy_serv.accept
651        begin
652          req = proxy_sock.gets("\r\n\r\n")
653          assert_match(%r{\AGET ftp://192.0.2.1/foo/bar }, req)
654          proxy_sock.print "HTTP/1.0 200 OK\r\n"
655          proxy_sock.print "Content-Length: 4\r\n\r\n"
656          proxy_sock.print "ab\r\n"
657        ensure
658          proxy_sock.close
659        end
660      }
661      begin
662        with_env('ftp_proxy'=>"http://127.0.0.1:#{proxy_port}") {
663          content = URI("ftp://192.0.2.1/foo/bar").read
664          assert_equal("ab\r\n", content)
665        }
666      ensure
667        Thread.kill(th)
668        th.join
669      end
670    }
671  end
672
673  def test_ftp_over_http_proxy_auth
674    TCPServer.open("127.0.0.1", 0) {|proxy_serv|
675      proxy_port = proxy_serv.addr[1]
676      th = Thread.new {
677        proxy_sock = proxy_serv.accept
678        begin
679          req = proxy_sock.gets("\r\n\r\n")
680          assert_match(%r{\AGET ftp://192.0.2.1/foo/bar }, req)
681          assert_match(%r{Proxy-Authorization: Basic #{['proxy-user:proxy-password'].pack('m').chomp}\r\n}, req)
682          proxy_sock.print "HTTP/1.0 200 OK\r\n"
683          proxy_sock.print "Content-Length: 4\r\n\r\n"
684          proxy_sock.print "ab\r\n"
685        ensure
686          proxy_sock.close
687        end
688      }
689      begin
690        content = URI("ftp://192.0.2.1/foo/bar").read(
691          :proxy_http_basic_authentication => ["http://127.0.0.1:#{proxy_port}", "proxy-user", "proxy-password"])
692        assert_equal("ab\r\n", content)
693      ensure
694        Thread.kill(th)
695        th.join
696      end
697    }
698  end
699
700end
701
702