1# coding: US-ASCII
2# frozen_string_literal: false
3require 'test/unit'
4require 'net/http'
5require 'stringio'
6require_relative 'utils'
7
8class TestNetHTTP < Test::Unit::TestCase
9
10  def test_class_Proxy
11    no_proxy_class = Net::HTTP.Proxy nil
12
13    assert_equal Net::HTTP, no_proxy_class
14
15    proxy_class = Net::HTTP.Proxy 'proxy.example', 8000, 'user', 'pass'
16
17    assert_not_equal Net::HTTP, proxy_class
18
19    assert_operator proxy_class, :<, Net::HTTP
20
21    assert_equal 'proxy.example', proxy_class.proxy_address
22    assert_equal 8000,            proxy_class.proxy_port
23    assert_equal 'user',          proxy_class.proxy_user
24    assert_equal 'pass',          proxy_class.proxy_pass
25
26    http = proxy_class.new 'hostname.example'
27
28    assert_not_predicate http, :proxy_from_env?
29
30
31    proxy_class = Net::HTTP.Proxy 'proxy.example'
32    assert_equal 'proxy.example', proxy_class.proxy_address
33    assert_equal 80,              proxy_class.proxy_port
34  end
35
36  def test_class_Proxy_from_ENV
37    clean_http_proxy_env do
38      ENV['http_proxy']      = 'http://proxy.example:8000'
39
40      # These are ignored on purpose.  See Bug 4388 and Feature 6546
41      ENV['http_proxy_user'] = 'user'
42      ENV['http_proxy_pass'] = 'pass'
43
44      proxy_class = Net::HTTP.Proxy :ENV
45
46      assert_not_equal Net::HTTP, proxy_class
47
48      assert_operator proxy_class, :<, Net::HTTP
49
50      assert_nil proxy_class.proxy_address
51      assert_nil proxy_class.proxy_user
52      assert_nil proxy_class.proxy_pass
53
54      assert_not_equal 8000, proxy_class.proxy_port
55
56      http = proxy_class.new 'hostname.example'
57
58      assert http.proxy_from_env?
59    end
60  end
61
62  def test_addr_port
63    http = Net::HTTP.new 'hostname.example', nil, nil
64    addr_port = http.__send__ :addr_port
65    assert_equal 'hostname.example', addr_port
66
67    http.use_ssl = true
68    addr_port = http.__send__ :addr_port
69    assert_equal 'hostname.example:80', addr_port
70
71    http = Net::HTTP.new '203.0.113.1', nil, nil
72    addr_port = http.__send__ :addr_port
73    assert_equal '203.0.113.1', addr_port
74
75    http.use_ssl = true
76    addr_port = http.__send__ :addr_port
77    assert_equal '203.0.113.1:80', addr_port
78
79    http = Net::HTTP.new '2001:db8::1', nil, nil
80    addr_port = http.__send__ :addr_port
81    assert_equal '[2001:db8::1]', addr_port
82
83    http.use_ssl = true
84    addr_port = http.__send__ :addr_port
85    assert_equal '[2001:db8::1]:80', addr_port
86
87  end
88
89  def test_edit_path
90    http = Net::HTTP.new 'hostname.example', nil, nil
91
92    edited = http.send :edit_path, '/path'
93
94    assert_equal '/path', edited
95
96    http.use_ssl = true
97
98    edited = http.send :edit_path, '/path'
99
100    assert_equal '/path', edited
101  end
102
103  def test_edit_path_proxy
104    http = Net::HTTP.new 'hostname.example', nil, 'proxy.example'
105
106    edited = http.send :edit_path, '/path'
107
108    assert_equal 'http://hostname.example/path', edited
109
110    http.use_ssl = true
111
112    edited = http.send :edit_path, '/path'
113
114    assert_equal '/path', edited
115  end
116
117  def test_proxy_address
118    clean_http_proxy_env do
119      http = Net::HTTP.new 'hostname.example', nil, 'proxy.example'
120      assert_equal 'proxy.example', http.proxy_address
121
122      http = Net::HTTP.new 'hostname.example', nil
123      assert_equal nil, http.proxy_address
124    end
125  end
126
127  def test_proxy_address_no_proxy
128    clean_http_proxy_env do
129      http = Net::HTTP.new 'hostname.example', nil, 'proxy.example', nil, nil, nil, 'example'
130      assert_nil http.proxy_address
131
132      http = Net::HTTP.new '10.224.1.1', nil, 'proxy.example', nil, nil, nil, 'example,10.224.0.0/22'
133      assert_nil http.proxy_address
134    end
135  end
136
137  def test_proxy_from_env_ENV
138    clean_http_proxy_env do
139      ENV['http_proxy'] = 'http://proxy.example:8000'
140
141      assert_equal false, Net::HTTP.proxy_class?
142      http = Net::HTTP.new 'hostname.example'
143
144      assert_equal true, http.proxy_from_env?
145    end
146  end
147
148  def test_proxy_address_ENV
149    clean_http_proxy_env do
150      ENV['http_proxy'] = 'http://proxy.example:8000'
151
152      http = Net::HTTP.new 'hostname.example'
153
154      assert_equal 'proxy.example', http.proxy_address
155    end
156  end
157
158  def test_proxy_eh_no_proxy
159    clean_http_proxy_env do
160      assert_equal false, Net::HTTP.new('hostname.example', nil, nil).proxy?
161    end
162  end
163
164  def test_proxy_eh_ENV
165    clean_http_proxy_env do
166      ENV['http_proxy'] = 'http://proxy.example:8000'
167
168      http = Net::HTTP.new 'hostname.example'
169
170      assert_equal true, http.proxy?
171    end
172  end
173
174  def test_proxy_eh_ENV_with_user
175    clean_http_proxy_env do
176      ENV['http_proxy'] = 'http://foo:bar@proxy.example:8000'
177
178      http = Net::HTTP.new 'hostname.example'
179
180      assert_equal true, http.proxy?
181      if Net::HTTP::ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE
182        assert_equal 'foo', http.proxy_user
183        assert_equal 'bar', http.proxy_pass
184      else
185        assert_nil http.proxy_user
186        assert_nil http.proxy_pass
187      end
188    end
189  end
190
191  def test_proxy_eh_ENV_none_set
192    clean_http_proxy_env do
193      assert_equal false, Net::HTTP.new('hostname.example').proxy?
194    end
195  end
196
197  def test_proxy_eh_ENV_no_proxy
198    clean_http_proxy_env do
199      ENV['http_proxy'] = 'http://proxy.example:8000'
200      ENV['no_proxy']   = 'hostname.example'
201
202      assert_equal false, Net::HTTP.new('hostname.example').proxy?
203    end
204  end
205
206  def test_proxy_port
207    clean_http_proxy_env do
208      http = Net::HTTP.new 'example', nil, 'proxy.example'
209      assert_equal 'proxy.example', http.proxy_address
210      assert_equal 80, http.proxy_port
211      http = Net::HTTP.new 'example', nil, 'proxy.example', 8000
212      assert_equal 8000, http.proxy_port
213      http = Net::HTTP.new 'example', nil
214      assert_equal nil, http.proxy_port
215    end
216  end
217
218  def test_proxy_port_ENV
219    clean_http_proxy_env do
220      ENV['http_proxy'] = 'http://proxy.example:8000'
221
222      http = Net::HTTP.new 'hostname.example'
223
224      assert_equal 8000, http.proxy_port
225    end
226  end
227
228  def test_newobj
229    clean_http_proxy_env do
230      ENV['http_proxy'] = 'http://proxy.example:8000'
231
232      http = Net::HTTP.newobj 'hostname.example'
233
234      assert_equal false, http.proxy?
235    end
236  end
237
238  def clean_http_proxy_env
239    orig = {
240      'http_proxy'      => ENV['http_proxy'],
241      'http_proxy_user' => ENV['http_proxy_user'],
242      'http_proxy_pass' => ENV['http_proxy_pass'],
243      'no_proxy'        => ENV['no_proxy'],
244    }
245
246    orig.each_key do |key|
247      ENV.delete key
248    end
249
250    yield
251  ensure
252    orig.each do |key, value|
253      ENV[key] = value
254    end
255  end
256
257  def test_failure_message_includes_failed_domain_and_port
258    # hostname to be included in the error message
259    host = Struct.new(:to_s).new("<example>")
260    port = 2119
261    # hack to let TCPSocket.open fail
262    def host.to_str; raise SocketError, "open failure"; end
263    uri = Struct.new(:scheme, :hostname, :port).new("http", host, port)
264    assert_raise_with_message(SocketError, /#{host}:#{port}/) do
265      clean_http_proxy_env{ Net::HTTP.get(uri) }
266    end
267  end
268
269end
270
271module TestNetHTTP_version_1_1_methods
272
273  def test_s_start
274    begin
275      h = Net::HTTP.start(config('host'), config('port'))
276    ensure
277      h&.finish
278    end
279    assert_equal config('host'), h.address
280    assert_equal config('port'), h.port
281    assert_equal true, h.instance_variable_get(:@proxy_from_env)
282
283    begin
284      h = Net::HTTP.start(config('host'), config('port'), :ENV)
285    ensure
286      h&.finish
287    end
288    assert_equal config('host'), h.address
289    assert_equal config('port'), h.port
290    assert_equal true, h.instance_variable_get(:@proxy_from_env)
291
292    begin
293      h = Net::HTTP.start(config('host'), config('port'), nil)
294    ensure
295      h&.finish
296    end
297    assert_equal config('host'), h.address
298    assert_equal config('port'), h.port
299    assert_equal false, h.instance_variable_get(:@proxy_from_env)
300  end
301
302  def test_s_get
303    assert_equal $test_net_http_data,
304        Net::HTTP.get(config('host'), '/', config('port'))
305  end
306
307  def test_head
308    start {|http|
309      res = http.head('/')
310      assert_kind_of Net::HTTPResponse, res
311      assert_equal $test_net_http_data_type, res['Content-Type']
312      unless self.is_a?(TestNetHTTP_v1_2_chunked)
313        assert_equal $test_net_http_data.size, res['Content-Length'].to_i
314      end
315    }
316  end
317
318  def test_get
319    start {|http|
320      _test_get__get http
321      _test_get__iter http
322      _test_get__chunked http
323    }
324  end
325
326  def _test_get__get(http)
327    res = http.get('/')
328    assert_kind_of Net::HTTPResponse, res
329    assert_kind_of String, res.body
330    unless self.is_a?(TestNetHTTP_v1_2_chunked)
331      assert_not_nil res['content-length']
332      assert_equal $test_net_http_data.size, res['content-length'].to_i
333    end
334    assert_equal $test_net_http_data_type, res['Content-Type']
335    assert_equal $test_net_http_data.size, res.body.size
336    assert_equal $test_net_http_data, res.body
337
338    assert_nothing_raised {
339      http.get('/', { 'User-Agent' => 'test' }.freeze)
340    }
341
342    assert res.decode_content, '[Bug #7924]' if Net::HTTP::HAVE_ZLIB
343  end
344
345  def _test_get__iter(http)
346    buf = ''
347    res = http.get('/') {|s| buf << s }
348    assert_kind_of Net::HTTPResponse, res
349    # assert_kind_of String, res.body
350    unless self.is_a?(TestNetHTTP_v1_2_chunked)
351      assert_not_nil res['content-length']
352      assert_equal $test_net_http_data.size, res['content-length'].to_i
353    end
354    assert_equal $test_net_http_data_type, res['Content-Type']
355    assert_equal $test_net_http_data.size, buf.size
356    assert_equal $test_net_http_data, buf
357    # assert_equal $test_net_http_data.size, res.body.size
358    # assert_equal $test_net_http_data, res.body
359  end
360
361  def _test_get__chunked(http)
362    buf = ''
363    res = http.get('/') {|s| buf << s }
364    assert_kind_of Net::HTTPResponse, res
365    # assert_kind_of String, res.body
366    unless self.is_a?(TestNetHTTP_v1_2_chunked)
367      assert_not_nil res['content-length']
368      assert_equal $test_net_http_data.size, res['content-length'].to_i
369    end
370    assert_equal $test_net_http_data_type, res['Content-Type']
371    assert_equal $test_net_http_data.size, buf.size
372    assert_equal $test_net_http_data, buf
373    # assert_equal $test_net_http_data.size, res.body.size
374    # assert_equal $test_net_http_data, res.body
375  end
376
377  def test_get__break
378    i = 0
379    start {|http|
380      http.get('/') do |str|
381        i += 1
382        break
383      end
384    }
385    assert_equal 1, i
386    @log_tester = nil # server may encount ECONNRESET
387  end
388
389  def test_get__implicit_start
390    res = new().get('/')
391    assert_kind_of Net::HTTPResponse, res
392    assert_kind_of String, res.body
393    unless self.is_a?(TestNetHTTP_v1_2_chunked)
394      assert_not_nil res['content-length']
395    end
396    assert_equal $test_net_http_data_type, res['Content-Type']
397    assert_equal $test_net_http_data.size, res.body.size
398    assert_equal $test_net_http_data, res.body
399  end
400
401  def test_get__crlf
402    start {|http|
403      assert_raise(ArgumentError) do
404        http.get("\r")
405      end
406      assert_raise(ArgumentError) do
407        http.get("\n")
408      end
409    }
410  end
411
412  def test_get2
413    start {|http|
414      http.get2('/') {|res|
415        EnvUtil.suppress_warning do
416          assert_kind_of Net::HTTPResponse, res
417          assert_kind_of Net::HTTPResponse, res.header
418        end
419
420        unless self.is_a?(TestNetHTTP_v1_2_chunked)
421          assert_not_nil res['content-length']
422        end
423        assert_equal $test_net_http_data_type, res['Content-Type']
424        assert_kind_of String, res.body
425        assert_kind_of String, res.entity
426        assert_equal $test_net_http_data.size, res.body.size
427        assert_equal $test_net_http_data, res.body
428        assert_equal $test_net_http_data, res.entity
429      }
430    }
431  end
432
433  def test_post
434    start {|http|
435      _test_post__base http
436      _test_post__file http
437      _test_post__no_data http
438    }
439  end
440
441  def _test_post__base(http)
442    uheader = {}
443    uheader['Accept'] = 'application/octet-stream'
444    uheader['Content-Type'] = 'application/x-www-form-urlencoded'
445    data = 'post data'
446    res = http.post('/', data, uheader)
447    assert_kind_of Net::HTTPResponse, res
448    assert_kind_of String, res.body
449    assert_equal data, res.body
450    assert_equal data, res.entity
451  end
452
453  def _test_post__file(http)
454    data = 'post data'
455    f = StringIO.new
456    http.post('/', data, {'content-type' => 'application/x-www-form-urlencoded'}, f)
457    assert_equal data, f.string
458  end
459
460  def _test_post__no_data(http)
461    unless self.is_a?(TestNetHTTP_v1_2_chunked)
462      EnvUtil.suppress_warning do
463        data = nil
464        res = http.post('/', data)
465        assert_not_equal '411', res.code
466      end
467    end
468  end
469
470  def test_s_post
471    url = "http://#{config('host')}:#{config('port')}/?q=a"
472    res = Net::HTTP.post(
473              URI.parse(url),
474              "a=x")
475    assert_equal "application/x-www-form-urlencoded", res["Content-Type"]
476    assert_equal "a=x", res.body
477    assert_equal url, res["X-request-uri"]
478
479    res = Net::HTTP.post(
480              URI.parse(url),
481              "hello world",
482              "Content-Type" => "text/plain; charset=US-ASCII")
483    assert_equal "text/plain; charset=US-ASCII", res["Content-Type"]
484    assert_equal "hello world", res.body
485  end
486
487  def test_s_post_form
488    url = "http://#{config('host')}:#{config('port')}/"
489    res = Net::HTTP.post_form(
490              URI.parse(url),
491              "a" => "x")
492    assert_equal ["a=x"], res.body.split(/[;&]/).sort
493
494    res = Net::HTTP.post_form(
495              URI.parse(url),
496              "a" => "x",
497              "b" => "y")
498    assert_equal ["a=x", "b=y"], res.body.split(/[;&]/).sort
499
500    res = Net::HTTP.post_form(
501              URI.parse(url),
502              "a" => ["x1", "x2"],
503              "b" => "y")
504    assert_equal url, res['X-request-uri']
505    assert_equal ["a=x1", "a=x2", "b=y"], res.body.split(/[;&]/).sort
506
507    res = Net::HTTP.post_form(
508              URI.parse(url + '?a=x'),
509              "b" => "y")
510    assert_equal url + '?a=x', res['X-request-uri']
511    assert_equal ["b=y"], res.body.split(/[;&]/).sort
512  end
513
514  def test_patch
515    start {|http|
516      _test_patch__base http
517    }
518  end
519
520  def _test_patch__base(http)
521    uheader = {}
522    uheader['Accept'] = 'application/octet-stream'
523    uheader['Content-Type'] = 'application/x-www-form-urlencoded'
524    data = 'patch data'
525    res = http.patch('/', data, uheader)
526    assert_kind_of Net::HTTPResponse, res
527    assert_kind_of String, res.body
528    assert_equal data, res.body
529    assert_equal data, res.entity
530  end
531
532  def test_timeout_during_HTTP_session_write
533    th = nil
534    # listen for connections... but deliberately do not read
535    TCPServer.open('localhost', 0) {|server|
536      port = server.addr[1]
537
538      conn = Net::HTTP.new('localhost', port)
539      conn.write_timeout = 0.01
540      conn.read_timeout = 0.01 if windows?
541      conn.open_timeout = 0.1
542
543      th = Thread.new do
544        err = !windows? ? Net::WriteTimeout : Net::ReadTimeout
545        assert_raise(err) { conn.post('/', "a"*50_000_000) }
546      end
547      assert th.join(10)
548    }
549  ensure
550    th&.kill
551    th&.join
552  end
553
554  def test_timeout_during_HTTP_session
555    bug4246 = "expected the HTTP session to have timed out but have not. c.f. [ruby-core:34203]"
556
557    th = nil
558    # listen for connections... but deliberately do not read
559    TCPServer.open('localhost', 0) {|server|
560      port = server.addr[1]
561
562      conn = Net::HTTP.new('localhost', port)
563      conn.read_timeout = 0.01
564      conn.open_timeout = 0.1
565
566      th = Thread.new do
567        assert_raise(Net::ReadTimeout) {
568          conn.get('/')
569        }
570      end
571      assert th.join(10), bug4246
572    }
573  ensure
574    th.kill
575    th.join
576  end
577end
578
579
580module TestNetHTTP_version_1_2_methods
581
582  def test_request
583    start {|http|
584      _test_request__GET http
585      _test_request__accept_encoding http
586      _test_request__file http
587      # _test_request__range http   # WEBrick does not support Range: header.
588      _test_request__HEAD http
589      _test_request__POST http
590      _test_request__stream_body http
591      _test_request__uri http
592      _test_request__uri_host http
593    }
594  end
595
596  def _test_request__GET(http)
597    req = Net::HTTP::Get.new('/')
598    http.request(req) {|res|
599      assert_kind_of Net::HTTPResponse, res
600      assert_kind_of String, res.body
601      unless self.is_a?(TestNetHTTP_v1_2_chunked)
602        assert_not_nil res['content-length']
603        assert_equal $test_net_http_data.size, res['content-length'].to_i
604      end
605      assert_equal $test_net_http_data.size, res.body.size
606      assert_equal $test_net_http_data, res.body
607
608      assert res.decode_content, 'Bug #7831' if Net::HTTP::HAVE_ZLIB
609    }
610  end
611
612  def _test_request__accept_encoding(http)
613    req = Net::HTTP::Get.new('/', 'accept-encoding' => 'deflate')
614    http.request(req) {|res|
615      assert_kind_of Net::HTTPResponse, res
616      assert_kind_of String, res.body
617      unless self.is_a?(TestNetHTTP_v1_2_chunked)
618        assert_not_nil res['content-length']
619        assert_equal $test_net_http_data.size, res['content-length'].to_i
620      end
621      assert_equal $test_net_http_data.size, res.body.size
622      assert_equal $test_net_http_data, res.body
623
624      assert_not_predicate res, :decode_content, 'Bug #7831' if Net::HTTP::HAVE_ZLIB
625    }
626  end
627
628  def _test_request__file(http)
629    req = Net::HTTP::Get.new('/')
630    http.request(req) {|res|
631      assert_kind_of Net::HTTPResponse, res
632      unless self.is_a?(TestNetHTTP_v1_2_chunked)
633        assert_not_nil res['content-length']
634        assert_equal $test_net_http_data.size, res['content-length'].to_i
635      end
636      f = StringIO.new("".force_encoding("ASCII-8BIT"))
637      res.read_body f
638      assert_equal $test_net_http_data.bytesize, f.string.bytesize
639      assert_equal $test_net_http_data.encoding, f.string.encoding
640      assert_equal $test_net_http_data, f.string
641    }
642  end
643
644  def _test_request__range(http)
645    req = Net::HTTP::Get.new('/')
646    req['range'] = 'bytes=0-5'
647    assert_equal $test_net_http_data[0,6], http.request(req).body
648  end
649
650  def _test_request__HEAD(http)
651    req = Net::HTTP::Head.new('/')
652    http.request(req) {|res|
653      assert_kind_of Net::HTTPResponse, res
654      unless self.is_a?(TestNetHTTP_v1_2_chunked)
655        assert_not_nil res['content-length']
656        assert_equal $test_net_http_data.size, res['content-length'].to_i
657      end
658      assert_nil res.body
659    }
660  end
661
662  def _test_request__POST(http)
663    data = 'post data'
664    req = Net::HTTP::Post.new('/')
665    req['Accept'] = $test_net_http_data_type
666    req['Content-Type'] = 'application/x-www-form-urlencoded'
667    http.request(req, data) {|res|
668      assert_kind_of Net::HTTPResponse, res
669      unless self.is_a?(TestNetHTTP_v1_2_chunked)
670        assert_equal data.size, res['content-length'].to_i
671      end
672      assert_kind_of String, res.body
673      assert_equal data, res.body
674    }
675  end
676
677  def _test_request__stream_body(http)
678    req = Net::HTTP::Post.new('/')
679    data = $test_net_http_data
680    req.content_length = data.size
681    req['Content-Type'] = 'application/x-www-form-urlencoded'
682    req.body_stream = StringIO.new(data)
683    res = http.request(req)
684    assert_kind_of Net::HTTPResponse, res
685    assert_kind_of String, res.body
686    assert_equal data.size, res.body.size
687    assert_equal data, res.body
688  end
689
690  def _test_request__path(http)
691    uri = URI 'https://hostname.example/'
692    req = Net::HTTP::Get.new('/')
693
694    res = http.request(req)
695
696    assert_kind_of URI::Generic, req.uri
697
698    assert_not_equal uri, req.uri
699
700    assert_equal uri, res.uri
701
702    assert_not_same uri,     req.uri
703    assert_not_same req.uri, res.uri
704  end
705
706  def _test_request__uri(http)
707    uri = URI 'https://hostname.example/'
708    req = Net::HTTP::Get.new(uri)
709
710    res = http.request(req)
711
712    assert_kind_of URI::Generic, req.uri
713
714    assert_not_equal uri, req.uri
715
716    assert_equal req.uri, res.uri
717
718    assert_not_same uri,     req.uri
719    assert_not_same req.uri, res.uri
720  end
721
722  def _test_request__uri_host(http)
723    uri = URI 'http://other.example/'
724
725    req = Net::HTTP::Get.new(uri)
726    req['host'] = 'hostname.example'
727
728    res = http.request(req)
729
730    assert_kind_of URI::Generic, req.uri
731
732    assert_equal URI("http://hostname.example:#{http.port}"), res.uri
733  end
734
735  def test_send_request
736    start {|http|
737      _test_send_request__GET http
738      _test_send_request__HEAD http
739      _test_send_request__POST http
740    }
741  end
742
743  def _test_send_request__GET(http)
744    res = http.send_request('GET', '/')
745    assert_kind_of Net::HTTPResponse, res
746    unless self.is_a?(TestNetHTTP_v1_2_chunked)
747      assert_equal $test_net_http_data.size, res['content-length'].to_i
748    end
749    assert_kind_of String, res.body
750    assert_equal $test_net_http_data, res.body
751  end
752
753  def _test_send_request__HEAD(http)
754    res = http.send_request('HEAD', '/')
755    assert_kind_of Net::HTTPResponse, res
756    unless self.is_a?(TestNetHTTP_v1_2_chunked)
757      assert_not_nil res['content-length']
758      assert_equal $test_net_http_data.size, res['content-length'].to_i
759    end
760    assert_nil res.body
761  end
762
763  def _test_send_request__POST(http)
764    data = 'aaabbb cc ddddddddddd lkjoiu4j3qlkuoa'
765    res = http.send_request('POST', '/', data, 'content-type' => 'application/x-www-form-urlencoded')
766    assert_kind_of Net::HTTPResponse, res
767    assert_kind_of String, res.body
768    assert_equal data.size, res.body.size
769    assert_equal data, res.body
770  end
771
772  def test_set_form
773    require 'tempfile'
774    Tempfile.create('ruby-test') {|file|
775      file << "\u{30c7}\u{30fc}\u{30bf}"
776      data = [
777        ['name', 'Gonbei Nanashi'],
778        ['name', "\u{540d}\u{7121}\u{3057}\u{306e}\u{6a29}\u{5175}\u{885b}"],
779        ['s"i\o', StringIO.new("\u{3042 3044 4e9c 925b}")],
780        ["file", file, filename: "ruby-test"]
781      ]
782      expected = <<"__EOM__".gsub(/\n/, "\r\n")
783--<boundary>
784Content-Disposition: form-data; name="name"
785
786Gonbei Nanashi
787--<boundary>
788Content-Disposition: form-data; name="name"
789
790\xE5\x90\x8D\xE7\x84\xA1\xE3\x81\x97\xE3\x81\xAE\xE6\xA8\xA9\xE5\x85\xB5\xE8\xA1\x9B
791--<boundary>
792Content-Disposition: form-data; name="s\\"i\\\\o"
793
794\xE3\x81\x82\xE3\x81\x84\xE4\xBA\x9C\xE9\x89\x9B
795--<boundary>
796Content-Disposition: form-data; name="file"; filename="ruby-test"
797Content-Type: application/octet-stream
798
799\xE3\x83\x87\xE3\x83\xBC\xE3\x82\xBF
800--<boundary>--
801__EOM__
802      start {|http|
803        _test_set_form_urlencoded(http, data.reject{|k,v|!v.is_a?(String)})
804        _test_set_form_multipart(http, false, data, expected)
805        _test_set_form_multipart(http, true, data, expected)
806      }
807    }
808  end
809
810  def _test_set_form_urlencoded(http, data)
811    req = Net::HTTP::Post.new('/')
812    req.set_form(data)
813    res = http.request req
814    assert_equal "name=Gonbei+Nanashi&name=%E5%90%8D%E7%84%A1%E3%81%97%E3%81%AE%E6%A8%A9%E5%85%B5%E8%A1%9B", res.body
815  end
816
817  def _test_set_form_multipart(http, chunked_p, data, expected)
818    data.each{|k,v|v.rewind rescue nil}
819    req = Net::HTTP::Post.new('/')
820    req.set_form(data, 'multipart/form-data')
821    req['Transfer-Encoding'] = 'chunked' if chunked_p
822    res = http.request req
823    body = res.body
824    assert_match(/\A--(?<boundary>\S+)/, body)
825    /\A--(?<boundary>\S+)/ =~ body
826    expected = expected.gsub(/<boundary>/, boundary)
827    assert_equal(expected, body)
828  end
829
830  def test_set_form_with_file
831    require 'tempfile'
832    Tempfile.create('ruby-test') {|file|
833      file.binmode
834      file << $test_net_http_data
835      filename = File.basename(file.to_path)
836      data = [['file', file]]
837      expected = <<"__EOM__".gsub(/\n/, "\r\n")
838--<boundary>
839Content-Disposition: form-data; name="file"; filename="<filename>"
840Content-Type: application/octet-stream
841
842<data>
843--<boundary>--
844__EOM__
845      expected.sub!(/<filename>/, filename)
846      expected.sub!(/<data>/, $test_net_http_data)
847      start {|http|
848        data.each{|k,v|v.rewind rescue nil}
849        req = Net::HTTP::Post.new('/')
850        req.set_form(data, 'multipart/form-data')
851        res = http.request req
852        body = res.body
853        header, _ = body.split(/\r\n\r\n/, 2)
854        assert_match(/\A--(?<boundary>\S+)/, body)
855        /\A--(?<boundary>\S+)/ =~ body
856        expected = expected.gsub(/<boundary>/, boundary)
857        assert_match(/^--(?<boundary>\S+)\r\n/, header)
858        assert_match(
859          /^Content-Disposition: form-data; name="file"; filename="#{filename}"\r\n/,
860          header)
861        assert_equal(expected, body)
862
863        data.each{|k,v|v.rewind rescue nil}
864        req['Transfer-Encoding'] = 'chunked'
865        res = http.request req
866        #assert_equal(expected, res.body)
867      }
868    }
869  end
870end
871
872class TestNetHTTP_v1_2 < Test::Unit::TestCase
873  CONFIG = {
874    'host' => '127.0.0.1',
875    'proxy_host' => nil,
876    'proxy_port' => nil,
877  }
878
879  include TestNetHTTPUtils
880  include TestNetHTTP_version_1_1_methods
881  include TestNetHTTP_version_1_2_methods
882
883  def new
884    Net::HTTP.version_1_2
885    super
886  end
887end
888
889class TestNetHTTP_v1_2_chunked < Test::Unit::TestCase
890  CONFIG = {
891    'host' => '127.0.0.1',
892    'proxy_host' => nil,
893    'proxy_port' => nil,
894    'chunked' => true,
895  }
896
897  include TestNetHTTPUtils
898  include TestNetHTTP_version_1_1_methods
899  include TestNetHTTP_version_1_2_methods
900
901  def new
902    Net::HTTP.version_1_2
903    super
904  end
905
906  def test_chunked_break
907    assert_nothing_raised("[ruby-core:29229]") {
908      start {|http|
909        http.request_get('/') {|res|
910          res.read_body {|chunk|
911            break
912          }
913        }
914      }
915    }
916  end
917end
918
919class TestNetHTTPContinue < Test::Unit::TestCase
920  CONFIG = {
921    'host' => '127.0.0.1',
922    'proxy_host' => nil,
923    'proxy_port' => nil,
924    'chunked' => true,
925  }
926
927  include TestNetHTTPUtils
928
929  def logfile
930    @debug = StringIO.new('')
931  end
932
933  def mount_proc(&block)
934    @server.mount('/continue', WEBrick::HTTPServlet::ProcHandler.new(block.to_proc))
935  end
936
937  def test_expect_continue
938    mount_proc {|req, res|
939      req.continue
940      res.body = req.query['body']
941    }
942    start {|http|
943      uheader = {'content-type' => 'application/x-www-form-urlencoded', 'expect' => '100-continue'}
944      http.continue_timeout = 0.2
945      http.request_post('/continue', 'body=BODY', uheader) {|res|
946        assert_equal('BODY', res.read_body)
947      }
948    }
949    assert_match(/Expect: 100-continue/, @debug.string)
950    assert_match(/HTTP\/1.1 100 continue/, @debug.string)
951  end
952
953  def test_expect_continue_timeout
954    mount_proc {|req, res|
955      sleep 0.2
956      req.continue # just ignored because it's '100'
957      res.body = req.query['body']
958    }
959    start {|http|
960      uheader = {'content-type' => 'application/x-www-form-urlencoded', 'expect' => '100-continue'}
961      http.continue_timeout = 0
962      http.request_post('/continue', 'body=BODY', uheader) {|res|
963        assert_equal('BODY', res.read_body)
964      }
965    }
966    assert_match(/Expect: 100-continue/, @debug.string)
967    assert_match(/HTTP\/1.1 100 continue/, @debug.string)
968  end
969
970  def test_expect_continue_error
971    mount_proc {|req, res|
972      res.status = 501
973      res.body = req.query['body']
974    }
975    start {|http|
976      uheader = {'content-type' => 'application/x-www-form-urlencoded', 'expect' => '100-continue'}
977      http.continue_timeout = 0
978      http.request_post('/continue', 'body=ERROR', uheader) {|res|
979        assert_equal('ERROR', res.read_body)
980      }
981    }
982    assert_match(/Expect: 100-continue/, @debug.string)
983    assert_not_match(/HTTP\/1.1 100 continue/, @debug.string)
984  end
985
986  def test_expect_continue_error_before_body
987    @log_tester = nil
988    mount_proc {|req, res|
989      raise WEBrick::HTTPStatus::Forbidden
990    }
991    start {|http|
992      uheader = {'content-length' => '5', 'expect' => '100-continue'}
993      http.continue_timeout = 1 # allow the server to respond before sending
994      http.request_post('/continue', 'data', uheader) {|res|
995        assert_equal(res.code, '403')
996      }
997    }
998    assert_match(/Expect: 100-continue/, @debug.string)
999    assert_not_match(/HTTP\/1.1 100 continue/, @debug.string)
1000  end
1001
1002  def test_expect_continue_error_while_waiting
1003    mount_proc {|req, res|
1004      res.status = 501
1005      res.body = req.query['body']
1006    }
1007    start {|http|
1008      uheader = {'content-type' => 'application/x-www-form-urlencoded', 'expect' => '100-continue'}
1009      http.continue_timeout = 0.5
1010      http.request_post('/continue', 'body=ERROR', uheader) {|res|
1011        assert_equal('ERROR', res.read_body)
1012      }
1013    }
1014    assert_match(/Expect: 100-continue/, @debug.string)
1015    assert_not_match(/HTTP\/1.1 100 continue/, @debug.string)
1016  end
1017end
1018
1019class TestNetHTTPSwitchingProtocols < Test::Unit::TestCase
1020  CONFIG = {
1021    'host' => '127.0.0.1',
1022    'proxy_host' => nil,
1023    'proxy_port' => nil,
1024    'chunked' => true,
1025  }
1026
1027  include TestNetHTTPUtils
1028
1029  def logfile
1030    @debug = StringIO.new('')
1031  end
1032
1033  def mount_proc(&block)
1034    @server.mount('/continue', WEBrick::HTTPServlet::ProcHandler.new(block.to_proc))
1035  end
1036
1037  def test_info
1038    mount_proc {|req, res|
1039      req.instance_variable_get(:@socket) << "HTTP/1.1 101 Switching Protocols\r\n\r\n"
1040      res.body = req.query['body']
1041    }
1042    start {|http|
1043      http.continue_timeout = 0.2
1044      http.request_post('/continue', 'body=BODY') {|res|
1045        assert_equal('BODY', res.read_body)
1046      }
1047    }
1048    assert_match(/HTTP\/1.1 101 Switching Protocols/, @debug.string)
1049  end
1050end
1051
1052class TestNetHTTPKeepAlive < Test::Unit::TestCase
1053  CONFIG = {
1054    'host' => '127.0.0.1',
1055    'proxy_host' => nil,
1056    'proxy_port' => nil,
1057    'RequestTimeout' => 1,
1058  }
1059
1060  include TestNetHTTPUtils
1061
1062  def test_keep_alive_get_auto_reconnect
1063    start {|http|
1064      res = http.get('/')
1065      http.keep_alive_timeout = 1
1066      assert_kind_of Net::HTTPResponse, res
1067      assert_kind_of String, res.body
1068      sleep 1.5
1069      assert_nothing_raised {
1070        res = http.get('/')
1071      }
1072      assert_kind_of Net::HTTPResponse, res
1073      assert_kind_of String, res.body
1074    }
1075  end
1076
1077  def test_server_closed_connection_auto_reconnect
1078    start {|http|
1079      res = http.get('/')
1080      http.keep_alive_timeout = 5
1081      assert_kind_of Net::HTTPResponse, res
1082      assert_kind_of String, res.body
1083      sleep 1.5
1084      assert_nothing_raised {
1085        # Net::HTTP should detect the closed connection before attempting the
1086        # request, since post requests cannot be retried.
1087        res = http.post('/', 'query=foo', 'content-type' => 'application/x-www-form-urlencoded')
1088      }
1089      assert_kind_of Net::HTTPResponse, res
1090      assert_kind_of String, res.body
1091    }
1092  end
1093
1094  def test_keep_alive_get_auto_retry
1095    start {|http|
1096      res = http.get('/')
1097      http.keep_alive_timeout = 5
1098      assert_kind_of Net::HTTPResponse, res
1099      assert_kind_of String, res.body
1100      sleep 1.5
1101      res = http.get('/')
1102      assert_kind_of Net::HTTPResponse, res
1103      assert_kind_of String, res.body
1104    }
1105  end
1106
1107  class MockSocket
1108    attr_reader :count
1109    def initialize(success_after: nil)
1110      @success_after = success_after
1111      @count = 0
1112    end
1113    def close
1114    end
1115    def closed?
1116    end
1117    def write(_)
1118    end
1119    def readline
1120      @count += 1
1121      if @success_after && @success_after <= @count
1122        "HTTP/1.1 200 OK"
1123      else
1124        raise Errno::ECONNRESET
1125      end
1126    end
1127    def readuntil(*_)
1128      ""
1129    end
1130    def read_all(_)
1131    end
1132  end
1133
1134  def test_http_retry_success
1135    start {|http|
1136      socket = MockSocket.new(success_after: 10)
1137      http.instance_variable_get(:@socket).close
1138      http.instance_variable_set(:@socket, socket)
1139      assert_equal 0, socket.count
1140      http.max_retries = 10
1141      res = http.get('/')
1142      assert_equal 10, socket.count
1143      assert_kind_of Net::HTTPResponse, res
1144      assert_kind_of String, res.body
1145    }
1146  end
1147
1148  def test_http_retry_failed
1149    start {|http|
1150      socket = MockSocket.new
1151      http.instance_variable_get(:@socket).close
1152      http.instance_variable_set(:@socket, socket)
1153      http.max_retries = 10
1154      assert_raise(Errno::ECONNRESET){ http.get('/') }
1155      assert_equal 11, socket.count
1156    }
1157  end
1158
1159  def test_keep_alive_server_close
1160    def @server.run(sock)
1161      sock.close
1162    end
1163
1164    start {|http|
1165      assert_raise(EOFError, Errno::ECONNRESET, IOError) {
1166        http.get('/')
1167      }
1168    }
1169  end
1170end
1171
1172class TestNetHTTPLocalBind < Test::Unit::TestCase
1173  CONFIG = {
1174    'host' => 'localhost',
1175    'proxy_host' => nil,
1176    'proxy_port' => nil,
1177  }
1178
1179  include TestNetHTTPUtils
1180
1181  def test_bind_to_local_host
1182    @server.mount_proc('/show_ip') { |req, res| res.body = req.remote_ip }
1183
1184    http = Net::HTTP.new(config('host'), config('port'))
1185    http.local_host = Addrinfo.tcp(config('host'), config('port')).ip_address
1186    assert_not_nil(http.local_host)
1187    assert_nil(http.local_port)
1188
1189    res = http.get('/show_ip')
1190    assert_equal(http.local_host, res.body)
1191  end
1192
1193  def test_bind_to_local_port
1194    @server.mount_proc('/show_port') { |req, res| res.body = req.peeraddr[1].to_s }
1195
1196    http = Net::HTTP.new(config('host'), config('port'))
1197    http.local_host = Addrinfo.tcp(config('host'), config('port')).ip_address
1198    http.local_port = Addrinfo.tcp(config('host'), 0).bind {|s|
1199      s.local_address.ip_port.to_s
1200    }
1201    assert_not_nil(http.local_host)
1202    assert_not_nil(http.local_port)
1203
1204    res = http.get('/show_port')
1205    assert_equal(http.local_port, res.body)
1206  end
1207end
1208
1209