httpclient 2.7.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3efa89267c49ad8538e958b70fa1d5ad60a51f14
4
- data.tar.gz: fddc29793643f39d3d29de2bda66a096353686e0
2
+ SHA256:
3
+ metadata.gz: e1df0b78f53d502000683d048be1316f156247e5e17995f5fb04a125302bdb4e
4
+ data.tar.gz: d533eeaf2333f2f35820441a74cdf84075e5227d4fb0648f4e2ea4d2099bd511
5
5
  SHA512:
6
- metadata.gz: 7f31fd63927c5e74d67fd57710e9a8e8528ef4eeee5ee03accac31629af74ccc19dca082df5d36fb2827e3713d9e36f1e6e444472f56111c9c32805df9a757f1
7
- data.tar.gz: a74ae21b44d061adc75c34488de4cde93c55f3e5cb98f22871e7f31cd5fec66f954339a664cd6576b8f796dbec28a9e6f48815e8b34331cafdb5f5fb74b3abe8
6
+ metadata.gz: 94024e0c5b5bd08800d9b3989151ab919869fabcaed65e4a375490fde0f6ea3b5bfc3ad4a5a9803bb057c5b8d36efc10c6ae5ade8a974c665d443730c63bc7ba
7
+ data.tar.gz: 768ae9ad96a73740675aca861a81075af3164a4b8875f81368a3228ecfdf523fe6c13cd9355bbe8ddd9155031ff1f908d8ec85e1bd112e0cd31d8b26d809dd6a
data/bin/httpclient CHANGED
@@ -20,6 +20,7 @@ end
20
20
  url = ARGV.shift
21
21
  if method && url
22
22
  client = HTTPClient.new
23
+ client.strict_response_size_check = true
23
24
  if method == 'download'
24
25
  print client.get_content(url)
25
26
  else
@@ -37,6 +38,7 @@ require 'irb/completion'
37
38
  class Runner
38
39
  def initialize
39
40
  @httpclient = HTTPClient.new
41
+ @httpclient.strict_response_size_check = true
40
42
  end
41
43
 
42
44
  def method_missing(msg, *a, &b)
data/lib/hexdump.rb CHANGED
@@ -11,13 +11,13 @@ module HexDump
11
11
  result = []
12
12
  while raw = str.slice(offset, 16) and raw.length > 0
13
13
  # data field
14
- data = ''
14
+ data = ''.dup
15
15
  for v in raw.unpack('N* a*')
16
- if v.kind_of? Integer
17
- data << sprintf("%08x ", v)
18
- else
19
- v.each_byte {|c| data << sprintf("%02x", c) }
20
- end
16
+ if v.kind_of? Integer
17
+ data << sprintf("%08x ", v)
18
+ else
19
+ v.each_byte {|c| data << sprintf("%02x", c) }
20
+ end
21
21
  end
22
22
  # text field
23
23
  text = raw.tr("\000-\037\177-\377", ".")
@@ -25,12 +25,12 @@ module HexDump
25
25
  offset += 16
26
26
  # omit duplicate line
27
27
  if /^(#{regex_quote_n(raw)})+/n =~ str[offset .. -1]
28
- result << sprintf("%08x ...", offset)
29
- offset += $&.length
30
- # should print at the end
31
- if offset == str.length
32
- result << sprintf("%08x %-36s %s", offset-16, data, text)
33
- end
28
+ result << sprintf("%08x ...", offset)
29
+ offset += $&.length
30
+ # should print at the end
31
+ if offset == str.length
32
+ result << sprintf("%08x %-36s %s", offset-16, data, text)
33
+ end
34
34
  end
35
35
  end
36
36
  result
@@ -238,7 +238,7 @@ module HTTP
238
238
  if defined?(Encoding::ASCII_8BIT)
239
239
  def set_body_encoding
240
240
  if type = self.content_type
241
- OpenURI::Meta.init(o = '')
241
+ OpenURI::Meta.init(o = ''.dup)
242
242
  o.meta_add_field('content-type', type)
243
243
  @body_encoding = o.encoding
244
244
  end
@@ -491,7 +491,7 @@ module HTTP
491
491
  # String.
492
492
  #
493
493
  # assert: @size is not nil
494
- def dump(header = '', dev = '')
494
+ def dump(header = '', dev = ''.dup)
495
495
  if @body.is_a?(Parts)
496
496
  dev << header
497
497
  @body.parts.each do |part|
@@ -521,7 +521,7 @@ module HTTP
521
521
  # reason. (header is dumped to dev, too)
522
522
  # If no dev (the second argument) given, this method returns a dumped
523
523
  # String.
524
- def dump_chunked(header = '', dev = '')
524
+ def dump_chunked(header = '', dev = ''.dup)
525
525
  dev << header
526
526
  if @body.is_a?(Parts)
527
527
  @body.parts.each do |part|
@@ -574,7 +574,7 @@ module HTTP
574
574
  end
575
575
 
576
576
  def dump_file(io, dev, sz)
577
- buf = ''
577
+ buf = ''.dup
578
578
  rest = sz
579
579
  while rest > 0
580
580
  n = io.read([rest, @chunk_size].min, buf)
@@ -585,7 +585,7 @@ module HTTP
585
585
  end
586
586
 
587
587
  def dump_chunks(io, dev)
588
- buf = ''
588
+ buf = ''.dup
589
589
  while !io.read(@chunk_size, buf).nil?
590
590
  dev << dump_chunk(buf)
591
591
  end
@@ -618,8 +618,8 @@ module HTTP
618
618
  if Message.file?(part)
619
619
  @as_stream = true
620
620
  @body << part
621
- if part.respond_to?(:lstat)
622
- sz = part.lstat.size
621
+ if part.respond_to?(:stat)
622
+ sz = part.stat.size
623
623
  add_size(part, sz)
624
624
  elsif part.respond_to?(:size)
625
625
  if sz = part.size
@@ -700,8 +700,9 @@ module HTTP
700
700
 
701
701
  def params_from_file(value)
702
702
  params = {}
703
+ original_filename = value.respond_to?(:original_filename) ? value.original_filename : nil
703
704
  path = value.respond_to?(:path) ? value.path : nil
704
- params['filename'] = File.basename(path || '')
705
+ params['filename'] = original_filename || File.basename(path || '')
705
706
  # Creation time is not available from File::Stat
706
707
  if value.respond_to?(:mtime)
707
708
  params['modification-date'] = value.mtime.rfc822
@@ -808,6 +809,8 @@ module HTTP
808
809
  case path
809
810
  when /\.txt$/i
810
811
  'text/plain'
812
+ when /\.xml$/i
813
+ 'text/xml'
811
814
  when /\.(htm|html)$/i
812
815
  'text/html'
813
816
  when /\.doc$/i
@@ -951,7 +954,7 @@ module HTTP
951
954
 
952
955
  # Dumps message (header and body) to given dev.
953
956
  # dev needs to respond to <<.
954
- def dump(dev = '')
957
+ def dump(dev = ''.dup)
955
958
  str = @http_header.dump + CRLF
956
959
  if @http_header.chunked
957
960
  dev = @http_body.dump_chunked(str, dev)
@@ -15,9 +15,27 @@ class HTTPClient
15
15
  unless defined?(SSLSocket)
16
16
 
17
17
  class JavaSocketWrap
18
+ java_import 'java.net.InetSocketAddress'
18
19
  java_import 'java.io.BufferedInputStream'
20
+
19
21
  BUF_SIZE = 1024 * 16
20
22
 
23
+ def self.normalize_timeout(timeout)
24
+ [Java::JavaLang::Integer::MAX_VALUE, timeout].min
25
+ end
26
+
27
+ def self.connect(socket, site, opts = {})
28
+ socket_addr = InetSocketAddress.new(site.host, site.port)
29
+ if opts[:connect_timeout]
30
+ socket.connect(socket_addr, normalize_timeout(opts[:connect_timeout]))
31
+ else
32
+ socket.connect(socket_addr)
33
+ end
34
+ socket.setSoTimeout(normalize_timeout(opts[:so_timeout])) if opts[:so_timeout]
35
+ socket.setKeepAlive(true) if opts[:tcp_keepalive]
36
+ socket
37
+ end
38
+
21
39
  def initialize(socket, debug_dev = nil)
22
40
  @socket = socket
23
41
  @debug_dev = debug_dev
@@ -39,7 +57,6 @@ unless defined?(SSLSocket)
39
57
  @socket.isClosed
40
58
  end
41
59
 
42
-
43
60
  def gets(rs)
44
61
  while (size = @bufstr.index(rs)).nil?
45
62
  if fill() == -1
@@ -105,11 +122,15 @@ unless defined?(SSLSocket)
105
122
  private
106
123
 
107
124
  def fill
108
- size = @instr.read(@buf)
109
- if size > 0
110
- @bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
125
+ begin
126
+ size = @instr.read(@buf)
127
+ if size > 0
128
+ @bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
129
+ end
130
+ size
131
+ rescue java.io.IOException => e
132
+ raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
111
133
  end
112
- size
113
134
  end
114
135
 
115
136
  def debug(str)
@@ -267,8 +288,8 @@ unless defined?(SSLSocket)
267
288
 
268
289
  module PEMUtils
269
290
  def self.read_certificate(pem)
270
- pem = pem.sub(/-----BEGIN CERTIFICATE-----/, '').sub(/-----END CERTIFICATE-----/, '')
271
- der = pem.unpack('m*').first
291
+ cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
292
+ der = cert.unpack('m*').first
272
293
  cf = CertificateFactory.getInstance('X.509')
273
294
  cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
274
295
  end
@@ -289,11 +310,11 @@ unless defined?(SSLSocket)
289
310
  @keystore.load(nil)
290
311
  end
291
312
 
292
- def add(cert_file, key_file, password)
293
- cert_str = cert_file.respond_to?(:to_pem) ? cert_file.to_pem : File.read(cert_file.to_s)
313
+ def add(cert_source, key_source, password)
314
+ cert_str = cert_source.respond_to?(:to_pem) ? cert_source.to_pem : File.read(cert_source.to_s)
294
315
  cert = PEMUtils.read_certificate(cert_str)
295
316
  @keystore.setCertificateEntry('client_cert', cert)
296
- key_str = key_file.respond_to?(:to_pem) ? key_file.to_pem : File.read(key_file.to_s)
317
+ key_str = key_source.respond_to?(:to_pem) ? key_source.to_pem : File.read(key_source.to_s)
297
318
  key_pair = PEMUtils.read_private_key(key_str, password)
298
319
  @keystore.setKeyEntry('client_key', key_pair.getPrivate, PASSWORD, [cert].to_java(Certificate))
299
320
  end
@@ -312,20 +333,23 @@ unless defined?(SSLSocket)
312
333
  @size = 0
313
334
  end
314
335
 
315
- def add(file_or_dir)
316
- return if file_or_dir == :default
317
- if File.directory?(file_or_dir)
318
- warn("#{file_or_dir}: directory not yet supported")
336
+ def add(cert_source)
337
+ return if cert_source == :default
338
+ if cert_source.respond_to?(:to_pem)
339
+ pem = cert_source.to_pem
340
+ load_pem(pem)
341
+ elsif File.directory?(cert_source)
342
+ warn("#{cert_source}: directory not yet supported")
343
+ return
319
344
  else
320
345
  pem = nil
321
- File.read(file_or_dir).each_line do |line|
346
+ File.read(cert_source).each_line do |line|
322
347
  case line
323
348
  when /-----BEGIN CERTIFICATE-----/
324
349
  pem = ''
325
350
  when /-----END CERTIFICATE-----/
326
- cert = PEMUtils.read_certificate(pem)
327
- @size += 1
328
- @trust_store.setCertificateEntry("cert_#{@size}", cert)
351
+ load_pem(pem)
352
+ # keep parsing in case where multiple certificates in a file
329
353
  else
330
354
  if pem
331
355
  pem << line
@@ -342,6 +366,14 @@ unless defined?(SSLSocket)
342
366
  @trust_store
343
367
  end
344
368
  end
369
+
370
+ private
371
+
372
+ def load_pem(pem)
373
+ cert = PEMUtils.read_certificate(pem)
374
+ @size += 1
375
+ @trust_store.setCertificateEntry("cert_#{@size}", cert)
376
+ end
345
377
  end
346
378
 
347
379
  # Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
@@ -429,26 +461,58 @@ unless defined?(SSLSocket)
429
461
  end
430
462
 
431
463
  def self.create_socket(session)
432
- site = session.proxy || session.dest
433
- socket = Socket.new(site.host, site.port)
464
+ opts = {
465
+ :connect_timeout => session.connect_timeout * 1000,
466
+ # send_timeout is ignored in JRuby
467
+ :so_timeout => session.receive_timeout * 1000,
468
+ :tcp_keepalive => session.tcp_keepalive,
469
+ :debug_dev => session.debug_dev
470
+ }
471
+ socket = nil
434
472
  begin
435
473
  if session.proxy
474
+ site = session.proxy || session.dest
475
+ socket = JavaSocketWrap.connect(Socket.new, site, opts)
436
476
  session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
437
477
  end
478
+ new(socket, session.dest, session.ssl_config, opts)
438
479
  rescue
439
- socket.close
480
+ socket.close if socket
440
481
  raise
441
482
  end
442
- new(socket, session.dest, session.ssl_config, session.debug_dev)
443
483
  end
444
484
 
445
- DEFAULT_SSL_PROTOCOL = 'TLS'
446
- def initialize(socket, dest, config, debug_dev = nil)
485
+ DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
486
+ def initialize(socket, dest, config, opts = {})
487
+ @config = config
488
+ begin
489
+ @ssl_socket = create_ssl_socket(socket, dest, config, opts)
490
+ ssl_version = java_ssl_version(config)
491
+ @ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
492
+ if config.ciphers != SSLConfig::CIPHERS_DEFAULT
493
+ @ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
494
+ end
495
+ ssl_connect(dest.host)
496
+ rescue java.security.GeneralSecurityException => e
497
+ raise OpenSSL::SSL::SSLError.new(e.getMessage)
498
+ rescue java.net.SocketTimeoutException => e
499
+ raise HTTPClient::ConnectTimeoutError.new(e.getMessage)
500
+ rescue java.io.IOException => e
501
+ raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
502
+ end
503
+
504
+ super(@ssl_socket, opts[:debug_dev])
505
+ end
506
+
507
+ def java_ssl_version(config)
447
508
  if config.ssl_version == :auto
448
- ssl_version = DEFAULT_SSL_PROTOCOL
509
+ DEFAULT_SSL_PROTOCOL
449
510
  else
450
- ssl_version = config.ssl_version.to_s.gsub(/_/, '.')
511
+ config.ssl_version.to_s.tr('_', '.')
451
512
  end
513
+ end
514
+
515
+ def create_ssl_context(config)
452
516
  unless config.cert_store_crl_items.empty?
453
517
  raise NotImplementedError.new('Manual CRL configuration is not yet supported')
454
518
  end
@@ -464,7 +528,7 @@ unless defined?(SSLSocket)
464
528
 
465
529
  trust_store = nil
466
530
  verify_callback = config.verify_callback || config.method(:default_verify_callback)
467
- if config.verify_mode == nil
531
+ if !config.verify?
468
532
  tmf = VerifyNoneTrustManagerFactory.new(verify_callback)
469
533
  else
470
534
  tmf = SystemTrustManagerFactory.new(verify_callback)
@@ -477,36 +541,24 @@ unless defined?(SSLSocket)
477
541
  tmf.init(trust_store)
478
542
  tm = tmf.getTrustManagers
479
543
 
480
- ctx = SSLContext.getInstance(ssl_version)
544
+ ctx = SSLContext.getInstance(java_ssl_version(config))
481
545
  ctx.init(km, tm, nil)
482
546
  if config.timeout
483
547
  ctx.getClientSessionContext.setSessionTimeout(config.timeout)
484
548
  end
549
+ ctx
550
+ end
485
551
 
552
+ def create_ssl_socket(socket, dest, config, opts)
553
+ ctx = create_ssl_context(config)
486
554
  factory = ctx.getSocketFactory
487
- begin
488
- ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
489
- ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
490
- if config.ciphers != SSLConfig::CIPHERS_DEFAULT
491
- ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
492
- end
493
- ssl_socket.startHandshake
494
- ssl_session = ssl_socket.getSession
495
- @peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
496
- if $DEBUG
497
- warn("Protocol version: #{ssl_session.getProtocol}")
498
- warn("Cipher: #{ssl_socket.getSession.getCipherSuite}")
499
- end
500
- post_connection_check(dest.host, @peer_cert)
501
- rescue java.security.GeneralSecurityException => e
502
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
503
- rescue javax.net.ssl.SSLException => e
504
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
505
- rescue java.net.SocketException => e
506
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
555
+ unless socket
556
+ # Create a plain socket first to set connection timeouts on,
557
+ # then wrap it in a SSL socket so that SNI gets setup on it.
558
+ socket = javax.net.SocketFactory.getDefault.createSocket
559
+ JavaSocketWrap.connect(socket, dest, opts)
507
560
  end
508
-
509
- super(ssl_socket, debug_dev)
561
+ factory.createSocket(socket, dest.host, dest.port, true)
510
562
  end
511
563
 
512
564
  def peer_cert
@@ -515,8 +567,23 @@ unless defined?(SSLSocket)
515
567
 
516
568
  private
517
569
 
518
- def post_connection_check(hostname, wrap_cert)
519
- BrowserCompatHostnameVerifier.new.verify(hostname, wrap_cert.cert)
570
+ def ssl_connect(hostname)
571
+ @ssl_socket.startHandshake
572
+ ssl_session = @ssl_socket.getSession
573
+ @peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
574
+ if $DEBUG
575
+ warn("Protocol version: #{ssl_session.getProtocol}")
576
+ warn("Cipher: #{@ssl_socket.getSession.getCipherSuite}")
577
+ end
578
+ post_connection_check(hostname)
579
+ end
580
+
581
+ def post_connection_check(hostname)
582
+ if !@config.verify?
583
+ return
584
+ else
585
+ BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
586
+ end
520
587
  end
521
588
  end
522
589
 
@@ -21,7 +21,7 @@ require 'zlib'
21
21
  require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
22
22
  require 'httpclient/ssl_config'
23
23
  require 'httpclient/http'
24
- if defined?(JRuby)
24
+ if defined? JRUBY_VERSION
25
25
  require 'httpclient/jruby_ssl_socket'
26
26
  else
27
27
  require 'httpclient/ssl_socket'
@@ -76,7 +76,7 @@ class HTTPClient
76
76
  def to_s # :nodoc:
77
77
  addr
78
78
  end
79
-
79
+
80
80
  # Returns true if scheme, host and port of the given URI matches with this.
81
81
  def match(uri)
82
82
  (@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
@@ -105,6 +105,8 @@ class HTTPClient
105
105
  attr_accessor :debug_dev
106
106
  # Boolean value for Socket#sync
107
107
  attr_accessor :socket_sync
108
+ # Boolean value to send TCP keepalive packets; no timing settings exist at present
109
+ attr_accessor :tcp_keepalive
108
110
 
109
111
  attr_accessor :connect_timeout
110
112
  # Maximum retry count. 0 for infinite.
@@ -115,6 +117,9 @@ class HTTPClient
115
117
  attr_accessor :read_block_size
116
118
  attr_accessor :protocol_retry_count
117
119
 
120
+ # Raise BadResponseError if response size does not match with Content-Length header in response.
121
+ attr_accessor :strict_response_size_check
122
+
118
123
  # Local address to bind local side of the socket to
119
124
  attr_accessor :socket_local
120
125
 
@@ -134,6 +139,7 @@ class HTTPClient
134
139
  @protocol_version = nil
135
140
  @debug_dev = client.debug_dev
136
141
  @socket_sync = true
142
+ @tcp_keepalive = false
137
143
  @chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
138
144
 
139
145
  @connect_timeout = 60
@@ -148,6 +154,7 @@ class HTTPClient
148
154
  @test_loopback_http_response = []
149
155
 
150
156
  @transparent_gzip_decompression = false
157
+ @strict_response_size_check = false
151
158
  @socket_local = Site.new
152
159
 
153
160
  @sess_pool = {}
@@ -212,6 +219,7 @@ class HTTPClient
212
219
  sess = Session.new(@client, site, @agent_name, @from)
213
220
  sess.proxy = via_proxy ? @proxy : nil
214
221
  sess.socket_sync = @socket_sync
222
+ sess.tcp_keepalive = @tcp_keepalive
215
223
  sess.requested_version = @protocol_version if @protocol_version
216
224
  sess.connect_timeout = @connect_timeout
217
225
  sess.connect_retry = @connect_retry
@@ -221,6 +229,7 @@ class HTTPClient
221
229
  sess.protocol_retry_count = @protocol_retry_count
222
230
  sess.ssl_config = @ssl_config
223
231
  sess.debug_dev = @debug_dev
232
+ sess.strict_response_size_check = @strict_response_size_check
224
233
  sess.socket_local = @socket_local
225
234
  sess.test_loopback_http_response = @test_loopback_http_response
226
235
  sess.transparent_gzip_decompression = @transparent_gzip_decompression
@@ -432,6 +441,8 @@ class HTTPClient
432
441
  attr_accessor :proxy
433
442
  # Boolean value for Socket#sync
434
443
  attr_accessor :socket_sync
444
+ # Boolean value to send TCP keepalive packets; no timing settings exist at present
445
+ attr_accessor :tcp_keepalive
435
446
  # Requested protocol version
436
447
  attr_accessor :requested_version
437
448
  # Device for dumping log for debugging
@@ -444,6 +455,7 @@ class HTTPClient
444
455
  attr_accessor :read_block_size
445
456
  attr_accessor :protocol_retry_count
446
457
 
458
+ attr_accessor :strict_response_size_check
447
459
  attr_accessor :socket_local
448
460
 
449
461
  attr_accessor :ssl_config
@@ -458,6 +470,7 @@ class HTTPClient
458
470
  @dest = dest
459
471
  @proxy = nil
460
472
  @socket_sync = true
473
+ @tcp_keepalive = false
461
474
  @requested_version = nil
462
475
 
463
476
  @debug_dev = nil
@@ -473,6 +486,7 @@ class HTTPClient
473
486
  @ssl_peer_cert = nil
474
487
 
475
488
  @test_loopback_http_response = nil
489
+ @strict_response_size_check = false
476
490
  @socket_local = Site::EMPTY
477
491
 
478
492
  @agent_name = agent_name
@@ -599,17 +613,16 @@ class HTTPClient
599
613
  clean_local = @socket_local.host.delete("[]")
600
614
  socket = TCPSocket.new(clean_host, port, clean_local, @socket_local.port)
601
615
  end
616
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if @tcp_keepalive
602
617
  if @debug_dev
603
618
  @debug_dev << "! CONNECTION ESTABLISHED\n"
604
619
  socket.extend(DebugSocket)
605
620
  socket.debug_dev = @debug_dev
606
621
  end
607
622
  rescue SystemCallError => e
608
- e.message << " (#{host}:#{port})"
609
- raise
623
+ raise e.class, e.message + " (#{host}:#{port})"
610
624
  rescue SocketError => e
611
- e.message << " (#{host}:#{port})"
612
- raise
625
+ raise e.class, e.message + " (#{host}:#{port})"
613
626
  end
614
627
  socket
615
628
  end
@@ -871,6 +884,9 @@ class HTTPClient
871
884
  rescue EOFError
872
885
  close
873
886
  buf = nil
887
+ if @strict_response_size_check
888
+ raise BadResponseError.new("EOF while reading rest #{@content_length} bytes")
889
+ end
874
890
  end
875
891
  end
876
892
  if buf && buf.bytesize > 0
@@ -887,18 +903,18 @@ class HTTPClient
887
903
  def read_body_chunked(&block)
888
904
  buf = empty_bin_str
889
905
  while true
890
- len = @socket.gets(RS)
891
- if len.nil? # EOF
892
- close
893
- return
894
- end
895
- @chunk_length = len.hex
896
- if @chunk_length == 0
897
- @content_length = 0
898
- @socket.gets(RS)
899
- return
900
- end
901
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
902
918
  @socket.read(@chunk_length, buf)
903
919
  @socket.read(2)
904
920
  end
@@ -920,6 +936,9 @@ class HTTPClient
920
936
  @socket.readpartial(@read_block_size, buf)
921
937
  rescue EOFError
922
938
  buf = nil
939
+ if @strict_response_size_check
940
+ raise BadResponseError.new("EOF while reading chunked response")
941
+ end
923
942
  end
924
943
  end
925
944
  if buf && buf.bytesize > 0
@@ -931,7 +950,7 @@ class HTTPClient
931
950
  end
932
951
 
933
952
  def empty_bin_str
934
- str = ''
953
+ str = ''.dup
935
954
  str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
936
955
  str
937
956
  end