httpclient 2.6.0.1 → 2.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -14,12 +14,18 @@
14
14
 
15
15
  require 'socket'
16
16
  require 'thread'
17
+ require 'timeout'
17
18
  require 'stringio'
18
19
  require 'zlib'
19
20
 
20
21
  require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
21
22
  require 'httpclient/ssl_config'
22
23
  require 'httpclient/http'
24
+ if RUBY_ENGINE == 'jruby'
25
+ require 'httpclient/jruby_ssl_socket'
26
+ else
27
+ require 'httpclient/ssl_socket'
28
+ end
23
29
 
24
30
 
25
31
  class HTTPClient
@@ -70,7 +76,7 @@ class HTTPClient
70
76
  def to_s # :nodoc:
71
77
  addr
72
78
  end
73
-
79
+
74
80
  # Returns true if scheme, host and port of the given URI matches with this.
75
81
  def match(uri)
76
82
  (@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
@@ -99,6 +105,8 @@ class HTTPClient
99
105
  attr_accessor :debug_dev
100
106
  # Boolean value for Socket#sync
101
107
  attr_accessor :socket_sync
108
+ # Boolean value to send TCP keepalive packets; no timing settings exist at present
109
+ attr_accessor :tcp_keepalive
102
110
 
103
111
  attr_accessor :connect_timeout
104
112
  # Maximum retry count. 0 for infinite.
@@ -109,6 +117,9 @@ class HTTPClient
109
117
  attr_accessor :read_block_size
110
118
  attr_accessor :protocol_retry_count
111
119
 
120
+ # Raise BadResponseError if response size does not match with Content-Length header in response.
121
+ attr_accessor :strict_response_size_check
122
+
112
123
  # Local address to bind local side of the socket to
113
124
  attr_accessor :socket_local
114
125
 
@@ -128,6 +139,7 @@ class HTTPClient
128
139
  @protocol_version = nil
129
140
  @debug_dev = client.debug_dev
130
141
  @socket_sync = true
142
+ @tcp_keepalive = false
131
143
  @chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
132
144
 
133
145
  @connect_timeout = 60
@@ -142,6 +154,7 @@ class HTTPClient
142
154
  @test_loopback_http_response = []
143
155
 
144
156
  @transparent_gzip_decompression = false
157
+ @strict_response_size_check = false
145
158
  @socket_local = Site.new
146
159
 
147
160
  @sess_pool = {}
@@ -206,6 +219,7 @@ class HTTPClient
206
219
  sess = Session.new(@client, site, @agent_name, @from)
207
220
  sess.proxy = via_proxy ? @proxy : nil
208
221
  sess.socket_sync = @socket_sync
222
+ sess.tcp_keepalive = @tcp_keepalive
209
223
  sess.requested_version = @protocol_version if @protocol_version
210
224
  sess.connect_timeout = @connect_timeout
211
225
  sess.connect_retry = @connect_retry
@@ -215,6 +229,7 @@ class HTTPClient
215
229
  sess.protocol_retry_count = @protocol_retry_count
216
230
  sess.ssl_config = @ssl_config
217
231
  sess.debug_dev = @debug_dev
232
+ sess.strict_response_size_check = @strict_response_size_check
218
233
  sess.socket_local = @socket_local
219
234
  sess.test_loopback_http_response = @test_loopback_http_response
220
235
  sess.transparent_gzip_decompression = @transparent_gzip_decompression
@@ -288,131 +303,6 @@ class HTTPClient
288
303
  end
289
304
 
290
305
 
291
- # Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
292
- class SSLSocketWrap
293
- def initialize(socket, context, debug_dev = nil)
294
- unless SSLEnabled
295
- raise ConfigurationError.new('Ruby/OpenSSL module is required')
296
- end
297
- @context = context
298
- @socket = socket
299
- @ssl_socket = create_openssl_socket(@socket)
300
- @debug_dev = debug_dev
301
- end
302
-
303
- def ssl_connect(hostname = nil)
304
- if hostname && @ssl_socket.respond_to?(:hostname=)
305
- @ssl_socket.hostname = hostname
306
- end
307
- @ssl_socket.connect
308
- end
309
-
310
- def post_connection_check(host)
311
- verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
312
- if verify_mode == OpenSSL::SSL::VERIFY_NONE
313
- return
314
- elsif @ssl_socket.peer_cert.nil? and
315
- check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
316
- raise OpenSSL::SSL::SSLError.new('no peer cert')
317
- end
318
- hostname = host.host
319
- if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
320
- @ssl_socket.post_connection_check(hostname)
321
- else
322
- @context.post_connection_check(@ssl_socket.peer_cert, hostname)
323
- end
324
- end
325
-
326
- def ssl_version
327
- @ssl_socket.ssl_version if @ssl_socket.respond_to?(:ssl_version)
328
- end
329
-
330
- def ssl_cipher
331
- @ssl_socket.cipher
332
- end
333
-
334
- def ssl_state
335
- @ssl_socket.state
336
- end
337
-
338
- def peer_cert
339
- @ssl_socket.peer_cert
340
- end
341
-
342
- def close
343
- @ssl_socket.close
344
- @socket.close
345
- end
346
-
347
- def closed?
348
- @socket.closed?
349
- end
350
-
351
- def eof?
352
- @ssl_socket.eof?
353
- end
354
-
355
- def gets(*args)
356
- str = @ssl_socket.gets(*args)
357
- debug(str)
358
- str
359
- end
360
-
361
- def read(*args)
362
- str = @ssl_socket.read(*args)
363
- debug(str)
364
- str
365
- end
366
-
367
- def readpartial(*args)
368
- str = @ssl_socket.readpartial(*args)
369
- debug(str)
370
- str
371
- end
372
-
373
- def <<(str)
374
- rv = @ssl_socket.write(str)
375
- debug(str)
376
- rv
377
- end
378
-
379
- def flush
380
- @ssl_socket.flush
381
- end
382
-
383
- def sync
384
- @ssl_socket.sync
385
- end
386
-
387
- def sync=(sync)
388
- @ssl_socket.sync = sync
389
- end
390
-
391
- private
392
-
393
- def check_mask(value, mask)
394
- value & mask == mask
395
- end
396
-
397
- def create_openssl_socket(socket)
398
- ssl_socket = nil
399
- if OpenSSL::SSL.const_defined?("SSLContext")
400
- ctx = OpenSSL::SSL::SSLContext.new
401
- @context.set_context(ctx)
402
- ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
403
- else
404
- ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
405
- @context.set_context(ssl_socket)
406
- end
407
- ssl_socket
408
- end
409
-
410
- def debug(str)
411
- @debug_dev << str if @debug_dev && str
412
- end
413
- end
414
-
415
-
416
306
  # Wraps up a Socket for method interception.
417
307
  module SocketWrap
418
308
  def initialize(socket, *args)
@@ -432,20 +322,20 @@ class HTTPClient
432
322
  @socket.eof?
433
323
  end
434
324
 
435
- def gets(*args)
436
- @socket.gets(*args)
325
+ def gets(rs)
326
+ @socket.gets(rs)
437
327
  end
438
328
 
439
- def read(*args)
440
- @socket.read(*args)
329
+ def read(size, buf = nil)
330
+ @socket.read(size, buf)
441
331
  end
442
332
 
443
- def readpartial(*args)
333
+ def readpartial(size, buf = nil)
444
334
  # StringIO doesn't support :readpartial
445
335
  if @socket.respond_to?(:readpartial)
446
- @socket.readpartial(*args)
336
+ @socket.readpartial(size, buf)
447
337
  else
448
- @socket.read(*args)
338
+ @socket.read(size, buf)
449
339
  end
450
340
  end
451
341
 
@@ -481,19 +371,19 @@ class HTTPClient
481
371
  debug("! CONNECTION CLOSED\n")
482
372
  end
483
373
 
484
- def gets(*args)
374
+ def gets(rs)
485
375
  str = super
486
376
  debug(str)
487
377
  str
488
378
  end
489
379
 
490
- def read(*args)
380
+ def read(size, buf = nil)
491
381
  str = super
492
382
  debug(str)
493
383
  str
494
384
  end
495
385
 
496
- def readpartial(*args)
386
+ def readpartial(size, buf = nil)
497
387
  str = super
498
388
  debug(str)
499
389
  str
@@ -533,6 +423,10 @@ class HTTPClient
533
423
  def <<(str)
534
424
  # ignored
535
425
  end
426
+
427
+ def peer_cert
428
+ nil
429
+ end
536
430
  end
537
431
 
538
432
 
@@ -547,6 +441,8 @@ class HTTPClient
547
441
  attr_accessor :proxy
548
442
  # Boolean value for Socket#sync
549
443
  attr_accessor :socket_sync
444
+ # Boolean value to send TCP keepalive packets; no timing settings exist at present
445
+ attr_accessor :tcp_keepalive
550
446
  # Requested protocol version
551
447
  attr_accessor :requested_version
552
448
  # Device for dumping log for debugging
@@ -559,6 +455,7 @@ class HTTPClient
559
455
  attr_accessor :read_block_size
560
456
  attr_accessor :protocol_retry_count
561
457
 
458
+ attr_accessor :strict_response_size_check
562
459
  attr_accessor :socket_local
563
460
 
564
461
  attr_accessor :ssl_config
@@ -573,6 +470,7 @@ class HTTPClient
573
470
  @dest = dest
574
471
  @proxy = nil
575
472
  @socket_sync = true
473
+ @tcp_keepalive = false
576
474
  @requested_version = nil
577
475
 
578
476
  @debug_dev = nil
@@ -588,6 +486,7 @@ class HTTPClient
588
486
  @ssl_peer_cert = nil
589
487
 
590
488
  @test_loopback_http_response = nil
489
+ @strict_response_size_check = false
591
490
  @socket_local = Site::EMPTY
592
491
 
593
492
  @agent_name = agent_name
@@ -613,7 +512,7 @@ class HTTPClient
613
512
  # Use absolute URI (not absolute path) iif via proxy AND not HTTPS.
614
513
  req.header.request_absolute_uri = !@proxy.nil? && !https?(@dest)
615
514
  begin
616
- timeout(@send_timeout, SendTimeoutError) do
515
+ ::Timeout.timeout(@send_timeout, SendTimeoutError) do
617
516
  set_header(req)
618
517
  req.dump(@socket)
619
518
  # flush the IO stream as IO::sync mode is false
@@ -679,18 +578,8 @@ class HTTPClient
679
578
  begin
680
579
  read_header if @state == :META
681
580
  return nil if @state != :DATA
682
- if @gzipped and @transparent_gzip_decompression
683
- # zlib itself has a functionality to decompress gzip stream.
684
- # - zlib 1.2.5 Manual
685
- # http://www.zlib.net/manual.html#Advanced
686
- # > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
687
- # > windowBits to enable zlib and gzip decoding with automatic header detection,
688
- # > or add 16 to decode only the gzip format
689
- inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
690
- original_block = block
691
- block = Proc.new { |buf|
692
- original_block.call(inflate_stream.inflate(buf))
693
- }
581
+ if @transparent_gzip_decompression
582
+ block = content_inflater_block(@content_encoding, block)
694
583
  end
695
584
  if @chunked
696
585
  read_body_chunked(&block)
@@ -713,8 +602,121 @@ class HTTPClient
713
602
  nil
714
603
  end
715
604
 
605
+ def create_socket(host, port)
606
+ socket = nil
607
+ begin
608
+ @debug_dev << "! CONNECT TO #{host}:#{port}\n" if @debug_dev
609
+ clean_host = host.delete("[]")
610
+ if @socket_local == Site::EMPTY
611
+ socket = TCPSocket.new(clean_host, port)
612
+ else
613
+ clean_local = @socket_local.host.delete("[]")
614
+ socket = TCPSocket.new(clean_host, port, clean_local, @socket_local.port)
615
+ end
616
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if @tcp_keepalive
617
+ if @debug_dev
618
+ @debug_dev << "! CONNECTION ESTABLISHED\n"
619
+ socket.extend(DebugSocket)
620
+ socket.debug_dev = @debug_dev
621
+ end
622
+ rescue SystemCallError => e
623
+ raise e.class, e.message + " (#{host}:#{port})"
624
+ rescue SocketError => e
625
+ raise e.class, e.message + " (#{host}:#{port})"
626
+ end
627
+ socket
628
+ end
629
+
630
+ def create_loopback_socket(host, port, str)
631
+ @debug_dev << "! CONNECT TO #{host}:#{port}\n" if @debug_dev
632
+ socket = LoopBackSocket.new(host, port, str)
633
+ if @debug_dev
634
+ @debug_dev << "! CONNECTION ESTABLISHED\n"
635
+ socket.extend(DebugSocket)
636
+ socket.debug_dev = @debug_dev
637
+ end
638
+ if https?(@dest) && @proxy
639
+ connect_ssl_proxy(socket, Util.urify(@dest.to_s))
640
+ end
641
+ socket
642
+ end
643
+
644
+ def connect_ssl_proxy(socket, uri)
645
+ req = HTTP::Message.new_connect_request(uri)
646
+ @client.request_filter.each do |filter|
647
+ filter.filter_request(req)
648
+ end
649
+ set_header(req)
650
+ req.dump(socket)
651
+ socket.flush unless @socket_sync
652
+ res = HTTP::Message.new_response('')
653
+ parse_header(socket)
654
+ res.http_version, res.status, res.reason = @version, @status, @reason
655
+ @headers.each do |key, value|
656
+ res.header.set(key.to_s, value)
657
+ end
658
+ commands = @client.request_filter.collect { |filter|
659
+ filter.filter_response(req, res)
660
+ }
661
+ if commands.find { |command| command == :retry }
662
+ raise RetryableResponse.new(res)
663
+ end
664
+ unless @status == 200
665
+ raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
666
+ end
667
+ end
668
+
716
669
  private
717
670
 
671
+ # This inflater allows deflate compression with/without zlib header
672
+ class LenientInflater
673
+ def initialize
674
+ @inflater = Zlib::Inflate.new(Zlib::MAX_WBITS)
675
+ @first = true
676
+ end
677
+
678
+ def inflate(body)
679
+ if @first
680
+ first_inflate(body)
681
+ else
682
+ @inflater.inflate(body)
683
+ end
684
+ end
685
+
686
+ private
687
+
688
+ def first_inflate(body)
689
+ @first = false
690
+ begin
691
+ @inflater.inflate(body)
692
+ rescue Zlib::DataError
693
+ # fallback to deflate without zlib header
694
+ @inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
695
+ @inflater.inflate(body)
696
+ end
697
+ end
698
+ end
699
+
700
+ def content_inflater_block(content_encoding, block)
701
+ case content_encoding
702
+ when 'gzip', 'x-gzip'
703
+ # zlib itself has a functionality to decompress gzip stream.
704
+ # - zlib 1.2.5 Manual
705
+ # http://www.zlib.net/manual.html#Advanced
706
+ # > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
707
+ # > windowBits to enable zlib and gzip decoding with automatic header detection,
708
+ # > or add 16 to decode only the gzip format
709
+ inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
710
+ when 'deflate'
711
+ inflate_stream = LenientInflater.new
712
+ else
713
+ return block
714
+ end
715
+ Proc.new { |buf|
716
+ block.call(inflate_stream.inflate(buf))
717
+ }
718
+ end
719
+
718
720
  def set_header(req)
719
721
  if @requested_version
720
722
  if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
@@ -743,30 +745,15 @@ class HTTPClient
743
745
  site = @proxy || @dest
744
746
  retry_number = 0
745
747
  begin
746
- timeout(@connect_timeout, ConnectTimeoutError) do
747
- @socket = create_socket(site)
748
- if https?(@dest)
749
- if @socket.is_a?(LoopBackSocket)
750
- connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy
751
- else
752
- @socket = create_ssl_socket(@socket)
753
- connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy
754
- begin
755
- @socket.ssl_connect(@dest.host)
756
- ensure
757
- if $DEBUG
758
- warn("Protocol version: #{@socket.ssl_version}")
759
- warn("Cipher: #{@socket.ssl_cipher.inspect}")
760
- warn("State: #{@socket.ssl_state}")
761
- end
762
- end
763
- @socket.post_connection_check(@dest)
764
- @ssl_peer_cert = @socket.peer_cert
765
- end
748
+ ::Timeout.timeout(@connect_timeout, ConnectTimeoutError) do
749
+ if str = @test_loopback_http_response.shift
750
+ @socket = create_loopback_socket(site.host, site.port, str)
751
+ elsif https?(@dest)
752
+ @socket = SSLSocket.create_socket(self)
753
+ @ssl_peer_cert = @socket.peer_cert
754
+ else
755
+ @socket = create_socket(site.host, site.port)
766
756
  end
767
- # Use Ruby internal buffering instead of passing data immediately
768
- # to the underlying layer
769
- # => we need to to call explicitly flush on the socket
770
757
  @socket.sync = @socket_sync
771
758
  end
772
759
  rescue RetryableResponse
@@ -788,71 +775,13 @@ class HTTPClient
788
775
  @state = :WAIT
789
776
  end
790
777
 
791
- def create_socket(site)
792
- socket = nil
793
- begin
794
- @debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
795
- clean_host = site.host.delete("[]")
796
- if str = @test_loopback_http_response.shift
797
- socket = LoopBackSocket.new(clean_host, site.port, str)
798
- elsif @socket_local == Site::EMPTY
799
- socket = TCPSocket.new(clean_host, site.port)
800
- else
801
- clean_local = @socket_local.host.delete("[]")
802
- socket = TCPSocket.new(clean_host, site.port, clean_local, @socket_local.port)
803
- end
804
- if @debug_dev
805
- @debug_dev << "! CONNECTION ESTABLISHED\n"
806
- socket.extend(DebugSocket)
807
- socket.debug_dev = @debug_dev
808
- end
809
- rescue SystemCallError => e
810
- e.message << " (#{site})"
811
- raise
812
- rescue SocketError => e
813
- e.message << " (#{site})"
814
- raise
815
- end
816
- socket
817
- end
818
-
819
- # wrap socket with OpenSSL.
820
- def create_ssl_socket(raw_socket)
821
- SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev)
822
- end
823
-
824
- def connect_ssl_proxy(socket, uri)
825
- req = HTTP::Message.new_connect_request(uri)
826
- @client.request_filter.each do |filter|
827
- filter.filter_request(req)
828
- end
829
- set_header(req)
830
- req.dump(@socket)
831
- @socket.flush unless @socket_sync
832
- res = HTTP::Message.new_response('')
833
- parse_header
834
- res.http_version, res.status, res.reason = @version, @status, @reason
835
- @headers.each do |key, value|
836
- res.header.set(key.to_s, value)
837
- end
838
- commands = @client.request_filter.collect { |filter|
839
- filter.filter_response(req, res)
840
- }
841
- if commands.find { |command| command == :retry }
842
- raise RetryableResponse.new(res)
843
- end
844
- unless @status == 200
845
- raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
846
- end
847
- end
848
-
849
778
  # Read status block.
850
779
  def read_header
851
780
  @content_length = nil
852
781
  @chunked = false
853
- @gzipped = false
782
+ @content_encoding = nil
854
783
  @chunk_length = 0
855
- parse_header
784
+ parse_header(@socket)
856
785
  # Header of the request has been parsed.
857
786
  @state = :DATA
858
787
  req = @requests.shift
@@ -868,12 +797,12 @@ class HTTPClient
868
797
  end
869
798
 
870
799
  StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
871
- def parse_header
872
- timeout(@receive_timeout, ReceiveTimeoutError) do
800
+ def parse_header(socket)
801
+ ::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
873
802
  initial_line = nil
874
803
  begin
875
804
  begin
876
- initial_line = @socket.gets("\n")
805
+ initial_line = socket.gets("\n")
877
806
  if initial_line.nil?
878
807
  close
879
808
  raise KeepAliveDisconnected.new(self)
@@ -896,7 +825,7 @@ class HTTPClient
896
825
  @next_connection = HTTP::Message.keep_alive_enabled?(@version)
897
826
  @headers = []
898
827
  while true
899
- line = @socket.gets("\n")
828
+ line = socket.gets("\n")
900
829
  unless line
901
830
  raise BadResponseError.new('unexpected EOF')
902
831
  end
@@ -908,7 +837,7 @@ class HTTPClient
908
837
  last << line.strip
909
838
  else
910
839
  key, value = line.strip.split(/\s*:\s*/, 2)
911
- parse_keepalive_header(key, value)
840
+ parse_content_header(key, value)
912
841
  @headers << [key, value]
913
842
  end
914
843
  end
@@ -921,18 +850,20 @@ class HTTPClient
921
850
  ((status >= 100 && status < 200) || status == 204 || status == 304)
922
851
  end
923
852
 
924
- def parse_keepalive_header(key, value)
853
+ def parse_content_header(key, value)
925
854
  key = key.downcase
926
- if key == 'content-length'
855
+ case key
856
+ when 'content-length'
927
857
  @content_length = value.to_i
928
- elsif key == 'content-encoding' and ( value.downcase == 'gzip' or
929
- value.downcase == 'x-gzip' or value.downcase == 'deflate' )
930
- @gzipped = true
931
- elsif key == 'transfer-encoding' and value.downcase == 'chunked'
932
- @chunked = true
933
- @chunk_length = 0
934
- @content_length = nil
935
- elsif key == 'connection' or key == 'proxy-connection'
858
+ when 'content-encoding'
859
+ @content_encoding = value.downcase
860
+ when 'transfer-encoding'
861
+ if value.downcase == 'chunked'
862
+ @chunked = true
863
+ @chunk_length = 0
864
+ @content_length = nil
865
+ end
866
+ when 'connection', 'proxy-connection'
936
867
  if value.downcase == 'keep-alive'
937
868
  @next_connection = true
938
869
  else
@@ -947,12 +878,15 @@ class HTTPClient
947
878
  buf = empty_bin_str
948
879
  maxbytes = @read_block_size
949
880
  maxbytes = @content_length if maxbytes > @content_length && @content_length > 0
950
- timeout(@receive_timeout, ReceiveTimeoutError) do
881
+ ::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
951
882
  begin
952
883
  @socket.readpartial(maxbytes, buf)
953
884
  rescue EOFError
954
885
  close
955
886
  buf = nil
887
+ if @strict_response_size_check
888
+ raise BadResponseError.new("EOF while reading rest #{@content_length} bytes")
889
+ end
956
890
  end
957
891
  end
958
892
  if buf && buf.bytesize > 0
@@ -969,18 +903,18 @@ class HTTPClient
969
903
  def read_body_chunked(&block)
970
904
  buf = empty_bin_str
971
905
  while true
972
- len = @socket.gets(RS)
973
- if len.nil? # EOF
974
- close
975
- return
976
- end
977
- @chunk_length = len.hex
978
- if @chunk_length == 0
979
- @content_length = 0
980
- @socket.gets(RS)
981
- return
982
- end
983
- timeout(@receive_timeout, ReceiveTimeoutError) do
906
+ ::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
907
+ len = @socket.gets(RS)
908
+ if len.nil? # EOF
909
+ close
910
+ return
911
+ end
912
+ @chunk_length = len.hex
913
+ if @chunk_length == 0
914
+ @content_length = 0
915
+ @socket.gets(RS)
916
+ return
917
+ end
984
918
  @socket.read(@chunk_length, buf)
985
919
  @socket.read(2)
986
920
  end
@@ -997,11 +931,14 @@ class HTTPClient
997
931
  end
998
932
  while true
999
933
  buf = empty_bin_str
1000
- timeout(@receive_timeout, ReceiveTimeoutError) do
934
+ ::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
1001
935
  begin
1002
936
  @socket.readpartial(@read_block_size, buf)
1003
937
  rescue EOFError
1004
938
  buf = nil
939
+ if @strict_response_size_check
940
+ raise BadResponseError.new("EOF while reading chunked response")
941
+ end
1005
942
  end
1006
943
  end
1007
944
  if buf && buf.bytesize > 0