httpclient 2.8.2.4 → 2.8.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 241dd999985ce751afe54cf45ca1d2df8a4b05c7
4
- data.tar.gz: 9a4021e0b3f9f1a9a40ecb56f48b139217d51621
3
+ metadata.gz: 45217dcc777d36d71246dd468e40b79caad351d6
4
+ data.tar.gz: afcf1a175414e0a1dde95eb5823b0fa339655d9c
5
5
  SHA512:
6
- metadata.gz: 2d306ba29a27b6e1e8507141b83a162c8b4c97ac03b4daf61982883be51ef0831cd79ff1d7e90047c5cda4ca1f775d6609d645a01a7ce289cb7e2a99519637d6
7
- data.tar.gz: c3fe5f9e0e6ab7b2e4afd0560ff2ec8f01e736d18dc66e431d6d3f1b89481e20acee6d4d4eadb8c2a6d03c27b6ecc3f0175e84dccaefa74e461615f579f93716
6
+ metadata.gz: f5ae105eb3b269d67521a35446e3518b359e7a359c0c8a14500ef9e9ff8c4681c20b103bb4d5a2f96cdd9caa337fc149f39df3754bb9f3185b6914f284adfdc0
7
+ data.tar.gz: 005d1769b6906e0c107ba63e7a178c4d73aae59198ed3efe866e8722fdadcf4c4142c3724c167b6db5f8a45cb03f517083164c18fdd1aa65074dc4ab362bc9de
@@ -355,6 +355,8 @@ class HTTPClient
355
355
  # if your ruby is older than 2005-09-06, do not set socket_sync = false to
356
356
  # avoid an SSL socket blocking bug in openssl/buffering.rb.
357
357
  attr_proxy(:socket_sync, true)
358
+ # Enables TCP keepalive; no timing settings exist at present
359
+ attr_proxy(:tcp_keepalive, true)
358
360
  # User-Agent header in HTTP request.
359
361
  attr_proxy(:agent_name, true)
360
362
  # From header in HTTP request.
@@ -15,9 +15,23 @@ 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.connect(socket, site, opts = {})
24
+ socket_addr = InetSocketAddress.new(site.host, site.port)
25
+ if opts[:connect_timeout]
26
+ socket.connect(socket_addr, opts[:connect_timeout])
27
+ else
28
+ socket.connect(socket_addr)
29
+ end
30
+ socket.setSoTimeout(opts[:so_timeout]) if opts[:so_timeout]
31
+ socket.setKeepAlive(true) if opts[:tcp_keepalive]
32
+ socket
33
+ end
34
+
21
35
  def initialize(socket, debug_dev = nil)
22
36
  @socket = socket
23
37
  @debug_dev = debug_dev
@@ -39,7 +53,6 @@ unless defined?(SSLSocket)
39
53
  @socket.isClosed
40
54
  end
41
55
 
42
-
43
56
  def gets(rs)
44
57
  while (size = @bufstr.index(rs)).nil?
45
58
  if fill() == -1
@@ -105,11 +118,15 @@ unless defined?(SSLSocket)
105
118
  private
106
119
 
107
120
  def fill
108
- size = @instr.read(@buf)
109
- if size > 0
110
- @bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
121
+ begin
122
+ size = @instr.read(@buf)
123
+ if size > 0
124
+ @bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
125
+ end
126
+ size
127
+ rescue java.io.IOException => e
128
+ raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
111
129
  end
112
- size
113
130
  end
114
131
 
115
132
  def debug(str)
@@ -267,8 +284,8 @@ unless defined?(SSLSocket)
267
284
 
268
285
  module PEMUtils
269
286
  def self.read_certificate(pem)
270
- pem = pem.sub(/-----BEGIN CERTIFICATE-----/, '').sub(/-----END CERTIFICATE-----/, '')
271
- der = pem.unpack('m*').first
287
+ cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
288
+ der = cert.unpack('m*').first
272
289
  cf = CertificateFactory.getInstance('X.509')
273
290
  cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
274
291
  end
@@ -440,27 +457,56 @@ unless defined?(SSLSocket)
440
457
  end
441
458
 
442
459
  def self.create_socket(session)
443
- site = session.proxy || session.dest
444
- socket = Socket.new(site.host, site.port)
460
+ opts = {
461
+ :connect_timeout => session.connect_timeout * 1000,
462
+ # send_timeout is ignored in JRuby
463
+ :so_timeout => session.receive_timeout * 1000,
464
+ :tcp_keepalive => session.tcp_keepalive,
465
+ :debug_dev => session.debug_dev
466
+ }
467
+ socket = nil
445
468
  begin
446
469
  if session.proxy
470
+ site = session.proxy || session.dest
471
+ socket = JavaSocketWrap.connect(Socket.new, site, opts)
447
472
  session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
448
473
  end
474
+ new(socket, session.dest, session.ssl_config, opts)
449
475
  rescue
450
- socket.close
476
+ socket.close if socket
451
477
  raise
452
478
  end
453
- new(socket, session.dest, session.ssl_config, session.debug_dev)
454
479
  end
455
480
 
456
481
  DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
457
- def initialize(socket, dest, config, debug_dev = nil)
482
+ def initialize(socket, dest, config, opts = {})
458
483
  @config = config
484
+ begin
485
+ @ssl_socket = create_ssl_socket(socket, dest, config, opts)
486
+ ssl_version = java_ssl_version(config)
487
+ @ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
488
+ if config.ciphers != SSLConfig::CIPHERS_DEFAULT
489
+ @ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
490
+ end
491
+ ssl_connect(dest.host)
492
+ rescue java.security.GeneralSecurityException => e
493
+ raise OpenSSL::SSL::SSLError.new(e.getMessage)
494
+ rescue java.io.IOException => e
495
+ raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
496
+ end
497
+
498
+ super(@ssl_socket, opts[:debug_dev])
499
+ end
500
+
501
+ def java_ssl_version(config)
459
502
  if config.ssl_version == :auto
460
- ssl_version = DEFAULT_SSL_PROTOCOL
503
+ DEFAULT_SSL_PROTOCOL
461
504
  else
462
- ssl_version = config.ssl_version.to_s.tr('_', '.')
505
+ config.ssl_version.to_s.tr('_', '.')
463
506
  end
507
+ end
508
+
509
+ def create_ssl_context(config)
464
510
  unless config.cert_store_crl_items.empty?
465
511
  raise NotImplementedError.new('Manual CRL configuration is not yet supported')
466
512
  end
@@ -489,36 +535,24 @@ unless defined?(SSLSocket)
489
535
  tmf.init(trust_store)
490
536
  tm = tmf.getTrustManagers
491
537
 
492
- ctx = SSLContext.getInstance(ssl_version)
538
+ ctx = SSLContext.getInstance(java_ssl_version(config))
493
539
  ctx.init(km, tm, nil)
494
540
  if config.timeout
495
541
  ctx.getClientSessionContext.setSessionTimeout(config.timeout)
496
542
  end
543
+ ctx
544
+ end
497
545
 
546
+ def create_ssl_socket(socket, dest, config, opts)
547
+ ctx = create_ssl_context(config)
498
548
  factory = ctx.getSocketFactory
499
- begin
549
+ if socket
500
550
  ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
501
- ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
502
- if config.ciphers != SSLConfig::CIPHERS_DEFAULT
503
- ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
504
- end
505
- ssl_socket.startHandshake
506
- ssl_session = ssl_socket.getSession
507
- @peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
508
- if $DEBUG
509
- warn("Protocol version: #{ssl_session.getProtocol}")
510
- warn("Cipher: #{ssl_socket.getSession.getCipherSuite}")
511
- end
512
- post_connection_check(dest.host, @peer_cert)
513
- rescue java.security.GeneralSecurityException => e
514
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
515
- rescue javax.net.ssl.SSLException => e
516
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
517
- rescue java.net.SocketException => e
518
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
551
+ else
552
+ ssl_socket = factory.createSocket
553
+ JavaSocketWrap.connect(ssl_socket, dest, opts)
519
554
  end
520
-
521
- super(ssl_socket, debug_dev)
555
+ ssl_socket
522
556
  end
523
557
 
524
558
  def peer_cert
@@ -527,11 +561,22 @@ unless defined?(SSLSocket)
527
561
 
528
562
  private
529
563
 
530
- def post_connection_check(hostname, wrap_cert)
564
+ def ssl_connect(hostname)
565
+ @ssl_socket.startHandshake
566
+ ssl_session = @ssl_socket.getSession
567
+ @peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
568
+ if $DEBUG
569
+ warn("Protocol version: #{ssl_session.getProtocol}")
570
+ warn("Cipher: #{@ssl_socket.getSession.getCipherSuite}")
571
+ end
572
+ post_connection_check(hostname)
573
+ end
574
+
575
+ def post_connection_check(hostname)
531
576
  if !@config.verify?
532
577
  return
533
578
  else
534
- BrowserCompatHostnameVerifier.new.verify(hostname, wrap_cert.cert)
579
+ BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
535
580
  end
536
581
  end
537
582
  end
@@ -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.
@@ -137,6 +139,7 @@ class HTTPClient
137
139
  @protocol_version = nil
138
140
  @debug_dev = client.debug_dev
139
141
  @socket_sync = true
142
+ @tcp_keepalive = false
140
143
  @chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
141
144
 
142
145
  @connect_timeout = 60
@@ -216,6 +219,7 @@ class HTTPClient
216
219
  sess = Session.new(@client, site, @agent_name, @from)
217
220
  sess.proxy = via_proxy ? @proxy : nil
218
221
  sess.socket_sync = @socket_sync
222
+ sess.tcp_keepalive = @tcp_keepalive
219
223
  sess.requested_version = @protocol_version if @protocol_version
220
224
  sess.connect_timeout = @connect_timeout
221
225
  sess.connect_retry = @connect_retry
@@ -437,6 +441,8 @@ class HTTPClient
437
441
  attr_accessor :proxy
438
442
  # Boolean value for Socket#sync
439
443
  attr_accessor :socket_sync
444
+ # Boolean value to send TCP keepalive packets; no timing settings exist at present
445
+ attr_accessor :tcp_keepalive
440
446
  # Requested protocol version
441
447
  attr_accessor :requested_version
442
448
  # Device for dumping log for debugging
@@ -464,6 +470,7 @@ class HTTPClient
464
470
  @dest = dest
465
471
  @proxy = nil
466
472
  @socket_sync = true
473
+ @tcp_keepalive = false
467
474
  @requested_version = nil
468
475
 
469
476
  @debug_dev = nil
@@ -606,17 +613,16 @@ class HTTPClient
606
613
  clean_local = @socket_local.host.delete("[]")
607
614
  socket = TCPSocket.new(clean_host, port, clean_local, @socket_local.port)
608
615
  end
616
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if @tcp_keepalive
609
617
  if @debug_dev
610
618
  @debug_dev << "! CONNECTION ESTABLISHED\n"
611
619
  socket.extend(DebugSocket)
612
620
  socket.debug_dev = @debug_dev
613
621
  end
614
622
  rescue SystemCallError => e
615
- e.message << " (#{host}:#{port})"
616
- raise
623
+ raise e.class, e.message + " (#{host}:#{port})"
617
624
  rescue SocketError => e
618
- e.message << " (#{host}:#{port})"
619
- raise
625
+ raise e.class, e.message + " (#{host}:#{port})"
620
626
  end
621
627
  socket
622
628
  end
@@ -66,55 +66,76 @@ class HTTPClient
66
66
  end
67
67
  end
68
68
 
69
+ class << self
70
+ private
71
+ def attr_config(symbol)
72
+ name = symbol.to_s
73
+ ivar_name = "@#{name}"
74
+ define_method(name) {
75
+ instance_variable_get(ivar_name)
76
+ }
77
+ define_method("#{name}=") { |rhs|
78
+ if instance_variable_get(ivar_name) != rhs
79
+ instance_variable_set(ivar_name, rhs)
80
+ change_notify
81
+ end
82
+ }
83
+ symbol
84
+ end
85
+ end
86
+
87
+
69
88
  CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
70
89
 
71
90
  # Which TLS protocol version (also called method) will be used. Defaults
72
- # to :auto which means that OpenSSL decides (In my tests this resulted
91
+ # to :auto which means that OpenSSL decides (In my tests this resulted
73
92
  # with always the highest available protocol being used).
74
93
  # String name of OpenSSL's SSL version method name: TLSv1_2, TLSv1_1, TLSv1,
75
94
  # SSLv2, SSLv23, SSLv3 or :auto (and nil) to allow version negotiation (default).
76
95
  # See {OpenSSL::SSL::SSLContext::METHODS} for a list of available versions
77
96
  # in your specific Ruby environment.
78
- attr_reader :ssl_version
97
+ attr_config :ssl_version
79
98
  # OpenSSL::X509::Certificate:: certificate for SSL client authentication.
80
99
  # nil by default. (no client authentication)
81
- attr_reader :client_cert
100
+ attr_config :client_cert
82
101
  # OpenSSL::PKey::PKey:: private key for SSL client authentication.
83
102
  # nil by default. (no client authentication)
84
- attr_reader :client_key
85
- attr_reader :client_key_pass
103
+ attr_config :client_key
104
+ # OpenSSL::PKey::PKey:: private key pass phrase for client_key.
105
+ # nil by default. (no pass phrase)
106
+ attr_config :client_key_pass
86
107
 
87
108
  # A number which represents OpenSSL's verify mode. Default value is
88
109
  # OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
89
- attr_reader :verify_mode
110
+ attr_config :verify_mode
90
111
  # A number of verify depth. Certification path which length is longer than
91
112
  # this depth is not allowed.
92
113
  # CAUTION: this is OpenSSL specific option and ignored on JRuby.
93
- attr_reader :verify_depth
114
+ attr_config :verify_depth
94
115
  # A callback handler for custom certificate verification. nil by default.
95
116
  # If the handler is set, handler.call is invoked just after general
96
117
  # OpenSSL's verification. handler.call is invoked with 2 arguments,
97
118
  # ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
98
119
  # OpenSSL::X509::StoreContext.
99
- attr_reader :verify_callback
120
+ attr_config :verify_callback
100
121
  # SSL timeout in sec. nil by default.
101
- attr_reader :timeout
122
+ attr_config :timeout
102
123
  # A number of OpenSSL's SSL options. Default value is
103
124
  # OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
104
125
  # CAUTION: this is OpenSSL specific option and ignored on JRuby.
105
126
  # Use ssl_version to specify the TLS version you want to use.
106
- attr_reader :options
127
+ attr_config :options
107
128
  # A String of OpenSSL's cipher configuration. Default value is
108
129
  # ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
109
130
  # See ciphers(1) man in OpenSSL for more detail.
110
- attr_reader :ciphers
131
+ attr_config :ciphers
111
132
 
112
133
  # OpenSSL::X509::X509::Store used for verification. You can reset the
113
134
  # store with clear_cert_store and set the new store with cert_store=.
114
135
  attr_reader :cert_store # don't use if you don't know what it is.
115
136
 
116
137
  # For server side configuration. Ignore this.
117
- attr_reader :client_ca # :nodoc:
138
+ attr_config :client_ca # :nodoc:
118
139
 
119
140
  # These array keeps original files/dirs that was added to @cert_store
120
141
  def cert_store_items; @cert_store._httpclient_cert_store_items; end
@@ -126,7 +147,7 @@ class HTTPClient
126
147
  @client = client
127
148
  @cert_store = X509::Store.new
128
149
  @cert_store_crl_items = []
129
- @client_cert = @client_key = @client_ca = nil
150
+ @client_cert = @client_key = @client_key_pass = @client_ca = nil
130
151
  @verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
131
152
  @verify_depth = nil
132
153
  @verify_callback = nil
@@ -144,42 +165,18 @@ class HTTPClient
144
165
  @cacerts_loaded = false
145
166
  end
146
167
 
147
- # Sets SSL version method String. Possible values: "SSLv2" for SSL2,
148
- # "SSLv3" for SSL3 and TLS1.x, "SSLv23" for SSL3 with fallback to SSL2.
149
- def ssl_version=(ssl_version)
150
- @ssl_version = ssl_version
151
- change_notify
152
- end
153
-
154
- # Sets certificate (OpenSSL::X509::Certificate) for SSL client
155
- # authentication.
156
- # client_key and client_cert must be a pair.
157
- #
158
- # Calling this method resets all existing sessions.
159
- def client_cert=(client_cert)
160
- @client_cert = client_cert
161
- change_notify
162
- end
163
-
164
- # Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
165
- # client_key and client_cert must be a pair.
166
- #
167
- # Calling this method resets all existing sessions.
168
- def client_key=(client_key)
169
- @client_key = client_key
170
- change_notify
171
- end
172
-
173
168
  # Sets certificate and private key for SSL client authentication.
174
169
  # cert_file:: must be a filename of PEM/DER formatted file.
175
170
  # key_file:: must be a filename of PEM/DER formatted file. Key must be an
176
171
  # RSA key. If you want to use other PKey algorithm,
177
172
  # use client_key=.
178
173
  #
179
- # Calling this method resets all existing sessions.
174
+ # Calling this method resets all existing sessions if value is changed.
180
175
  def set_client_cert_file(cert_file, key_file, pass = nil)
181
- @client_cert, @client_key, @client_key_pass = cert_file, key_file, pass
182
- change_notify
176
+ if (@client_cert != cert_file) || (@client_key != key_file) || (@client_key_pass != pass)
177
+ @client_cert, @client_key, @client_key_pass = cert_file, key_file, pass
178
+ change_notify
179
+ end
183
180
  end
184
181
 
185
182
  # Sets OpenSSL's default trusted CA certificates. Generally, OpenSSL is
@@ -216,9 +213,12 @@ class HTTPClient
216
213
  #
217
214
  # Calling this method resets all existing sessions.
218
215
  def cert_store=(cert_store)
219
- @cacerts_loaded = true # avoid lazy override
220
- @cert_store = cert_store
221
- change_notify
216
+ # This is object equality check, since OpenSSL::X509::Store doesn't overload ==
217
+ if !@cacerts_loaded || (@cert_store != cert_store)
218
+ @cacerts_loaded = true # avoid lazy override
219
+ @cert_store = cert_store
220
+ change_notify
221
+ end
222
222
  end
223
223
 
224
224
  # Sets trust anchor certificate(s) for verification.
@@ -276,62 +276,6 @@ class HTTPClient
276
276
  end
277
277
  alias set_crl add_crl
278
278
 
279
- # Sets verify mode of OpenSSL. New value must be a combination of
280
- # constants OpenSSL::SSL::VERIFY_*
281
- #
282
- # Calling this method resets all existing sessions.
283
- def verify_mode=(verify_mode)
284
- @verify_mode = verify_mode
285
- change_notify
286
- end
287
-
288
- # Sets verify depth. New value must be a number.
289
- #
290
- # Calling this method resets all existing sessions.
291
- def verify_depth=(verify_depth)
292
- @verify_depth = verify_depth
293
- change_notify
294
- end
295
-
296
- # Sets callback handler for custom certificate verification.
297
- # See verify_callback.
298
- #
299
- # Calling this method resets all existing sessions.
300
- def verify_callback=(verify_callback)
301
- @verify_callback = verify_callback
302
- change_notify
303
- end
304
-
305
- # Sets SSL timeout in sec.
306
- #
307
- # Calling this method resets all existing sessions.
308
- def timeout=(timeout)
309
- @timeout = timeout
310
- change_notify
311
- end
312
-
313
- # Sets SSL options. New value must be a combination of # constants
314
- # OpenSSL::SSL::OP_*
315
- #
316
- # Calling this method resets all existing sessions.
317
- def options=(options)
318
- @options = options
319
- change_notify
320
- end
321
-
322
- # Sets cipher configuration. New value must be a String.
323
- #
324
- # Calling this method resets all existing sessions.
325
- def ciphers=(ciphers)
326
- @ciphers = ciphers
327
- change_notify
328
- end
329
-
330
- def client_ca=(client_ca) # :nodoc:
331
- @client_ca = client_ca
332
- change_notify
333
- end
334
-
335
279
  def verify?
336
280
  @verify_mode && (@verify_mode & OpenSSL::SSL::VERIFY_PEER != 0)
337
281
  end
@@ -471,7 +415,6 @@ class HTTPClient
471
415
 
472
416
  # Use 2048 bit certs trust anchor
473
417
  def load_cacerts(cert_store)
474
- ver = OpenSSL::OPENSSL_VERSION
475
418
  file = File.join(File.dirname(__FILE__), 'cacert.pem')
476
419
  add_trust_ca_to_store(cert_store, file)
477
420
  end
@@ -14,43 +14,31 @@ class HTTPClient
14
14
  # Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
15
15
  class SSLSocket
16
16
  def self.create_socket(session)
17
+ opts = {
18
+ :debug_dev => session.debug_dev
19
+ }
17
20
  site = session.proxy || session.dest
18
21
  socket = session.create_socket(site.host, site.port)
19
22
  begin
20
23
  if session.proxy
21
24
  session.connect_ssl_proxy(socket, Util.urify(session.dest.to_s))
22
25
  end
23
- ssl_socket = new(socket, session.ssl_config, session.debug_dev)
24
- ssl_socket.ssl_connect(session.dest.host)
25
- ssl_socket
26
+ new(socket, session.dest, session.ssl_config, opts)
26
27
  rescue
27
28
  socket.close
28
29
  raise
29
30
  end
30
31
  end
31
32
 
32
- def initialize(socket, context, debug_dev = nil)
33
+ def initialize(socket, dest, config, opts = {})
33
34
  unless SSLEnabled
34
35
  raise ConfigurationError.new('Ruby/OpenSSL module is required')
35
36
  end
36
37
  @socket = socket
37
- @context = context
38
+ @config = config
38
39
  @ssl_socket = create_openssl_socket(@socket)
39
- @debug_dev = debug_dev
40
- end
41
-
42
- def ssl_connect(hostname = nil)
43
- if hostname && @ssl_socket.respond_to?(:hostname=)
44
- @ssl_socket.hostname = hostname
45
- end
46
- @ssl_socket.connect
47
- if $DEBUG
48
- if @ssl_socket.respond_to?(:ssl_version)
49
- warn("Protocol version: #{@ssl_socket.ssl_version}")
50
- end
51
- warn("Cipher: #{@ssl_socket.cipher.inspect}")
52
- end
53
- post_connection_check(hostname)
40
+ @debug_dev = opts[:debug_dev]
41
+ ssl_connect(dest.host)
54
42
  end
55
43
 
56
44
  def peer_cert
@@ -108,8 +96,22 @@ class HTTPClient
108
96
 
109
97
  private
110
98
 
99
+ def ssl_connect(hostname = nil)
100
+ if hostname && @ssl_socket.respond_to?(:hostname=)
101
+ @ssl_socket.hostname = hostname
102
+ end
103
+ @ssl_socket.connect
104
+ if $DEBUG
105
+ if @ssl_socket.respond_to?(:ssl_version)
106
+ warn("Protocol version: #{@ssl_socket.ssl_version}")
107
+ end
108
+ warn("Cipher: #{@ssl_socket.cipher.inspect}")
109
+ end
110
+ post_connection_check(hostname)
111
+ end
112
+
111
113
  def post_connection_check(hostname)
112
- verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
114
+ verify_mode = @config.verify_mode || OpenSSL::SSL::VERIFY_NONE
113
115
  if verify_mode == OpenSSL::SSL::VERIFY_NONE
114
116
  return
115
117
  elsif @ssl_socket.peer_cert.nil? and
@@ -119,7 +121,7 @@ class HTTPClient
119
121
  if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
120
122
  @ssl_socket.post_connection_check(hostname)
121
123
  else
122
- @context.post_connection_check(@ssl_socket.peer_cert, hostname)
124
+ @config.post_connection_check(@ssl_socket.peer_cert, hostname)
123
125
  end
124
126
  end
125
127
 
@@ -131,11 +133,11 @@ class HTTPClient
131
133
  ssl_socket = nil
132
134
  if OpenSSL::SSL.const_defined?("SSLContext")
133
135
  ctx = OpenSSL::SSL::SSLContext.new
134
- @context.set_context(ctx)
136
+ @config.set_context(ctx)
135
137
  ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
136
138
  else
137
139
  ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
138
- @context.set_context(ssl_socket)
140
+ @config.set_context(ssl_socket)
139
141
  end
140
142
  ssl_socket
141
143
  end
@@ -1,3 +1,3 @@
1
1
  class HTTPClient
2
- VERSION = '2.8.2.4'
2
+ VERSION = '2.8.3'
3
3
  end
@@ -0,0 +1,32 @@
1
+ require File.expand_path('helper', File.join(File.dirname(__FILE__), ".."))
2
+
3
+
4
+ class PEMUtilsTest < Test::Unit::TestCase
5
+ include Helper
6
+
7
+ def setup
8
+ @raw_cert = "-----BEGIN CERTIFICATE-----\nMIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB\nGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5\nIENBMB4XDTE2MDgxMDE3MjEzNFoXDTE3MDgxMDE3MjEzNFowSzETMBEGCgmSJomT\n8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEZMBcGA1UEAwwQ\nUnVieSBjZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAJCfsSXpSMpmZCVa+ZCM+QDgomnhDlvnrGDq6pasTaIspGTXgws+7r8Dt/cNe6EH\nHJpRH2cGRiO4yPcfcT9eS4X7k8OC4f33wHfACOmLu6LeoNE8ujmSk6L6WzLUI+sE\nnLZbFrXxoAo4XHsm8vEG9C+jEoXZ1p+47wrAGaDwDQTnzlMy4dT9pRQEJP2G/Rry\nUkuZn8SUWmh3/YS78iaSzsNF1cgE1ealHOrPPFDjiCGDaH/LHyUPYlbFSLZ/B7Qx\nLxi5sePLcywWq/EJrmWpgeVTDjtNijsdKv/A3qkY+fm/oD0pzt7XsfJaP9YKNyJO\nQFdxWZeiPcDF+Hwf+IwSr+kCAwEAAaMxMC8wDgYDVR0PAQH/BAQDAgeAMB0GA1Ud\nDgQWBBQNvzYzJyXemGhxbA8NMXLolDnPyjANBgkqhkiG9w0BAQsFAAOCAQEARIJV\noKejGlOTn71QutnNnu07UtTu0IHs6YqjYzzND+m4JXLN+wvYm72AFUG0b1L7dRg0\niK8XjQrlNQNVqP1Mc6tffchy20neOPOHeiO6qTdRU8P2S8D3Uwe+1qhgxjfE+cWc\nwZmWxYK4HA8c58PxWMqrkr2QqXDplG9KWLvOgrtPGiLLZcQSKhvvB63QzItHBDU6\nRayiJY3oPkK/HrIvFlySqFqzWmuyknkciOFywEHQMz/tcSFJ2QFpPj/tBz9VXohH\nZ8KscmfhZrTPBjo+ky1lz/WraWoz4LMiLnkC2ABczWLRSawu+v3Irx1NFJngt05e\npqwtqIUeg7j+JLiTaA==\n-----END CERTIFICATE-----"
9
+ end
10
+
11
+ def test_read_certificate
12
+ assert_nothing_raised do
13
+ binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(@raw_cert)
14
+ end
15
+ end
16
+
17
+ def test_read_certificate_works_with_random_ascii_text_outside_begin_end
18
+ raw_cert_with_ascii = "some text before begin\n" + @raw_cert + "\nsome text after end"
19
+ assert_nothing_raised do
20
+ binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(raw_cert_with_ascii)
21
+ end
22
+ end
23
+
24
+ def test_read_certificate_uses_all_content_if_missing_begin_end
25
+ cert = @raw_cert.sub(/-----BEGIN CERTIFICATE-----/, '').sub(/-----END CERTIFICATE-----/, '')
26
+ assert_nothing_raised do
27
+ binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(cert)
28
+ end
29
+ end
30
+
31
+
32
+ end
@@ -1908,6 +1908,18 @@ EOS
1908
1908
  end
1909
1909
  end
1910
1910
 
1911
+ def test_tcp_keepalive
1912
+ @client.tcp_keepalive = true
1913
+ @client.get(serverurl)
1914
+
1915
+ # expecting HTTP keepalive caches the socket
1916
+ session = @client.instance_variable_get(:@session_manager).send(:get_cached_session, HTTPClient::Site.new(URI.parse(serverurl)))
1917
+ socket = session.instance_variable_get(:@socket)
1918
+
1919
+ assert_true(session.tcp_keepalive)
1920
+ assert_equal(Socket::SO_KEEPALIVE, socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).optname)
1921
+ end
1922
+
1911
1923
  private
1912
1924
 
1913
1925
  def check_query_get(query)
@@ -388,6 +388,45 @@ e61RBaxk5OHOA0bLtvJblV6NL72ZEZhX60wAWbrOPhpT
388
388
  assert_equal(store, store.add_cert(OpenSSL::X509::Certificate.new(VERIFY_TEST_CERT_LOCALHOST)))
389
389
  end
390
390
 
391
+ def test_tcp_keepalive
392
+ @client.tcp_keepalive = true
393
+ @client.ssl_config.add_trust_ca(path('ca-chain.pem'))
394
+ @client.get_content(@url)
395
+
396
+ # expecting HTTP keepalive caches the socket
397
+ session = @client.instance_variable_get(:@session_manager).send(:get_cached_session, HTTPClient::Site.new(URI.parse(@url)))
398
+ socket = session.instance_variable_get(:@socket).instance_variable_get(:@socket)
399
+
400
+ assert_true(session.tcp_keepalive)
401
+ if RUBY_ENGINE == 'jruby'
402
+ assert_true(socket.getKeepAlive())
403
+ else
404
+ assert_equal(Socket::SO_KEEPALIVE, socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).optname)
405
+ end
406
+ end
407
+
408
+ def test_timeout
409
+ url = "https://localhost:#{serverport}/"
410
+ @client.ssl_config.add_trust_ca(path('ca-chain.pem'))
411
+ assert_equal('sleep', @client.get_content(url + 'sleep?sec=2'))
412
+ @client.receive_timeout = 1
413
+ @client.reset_all
414
+ assert_equal('sleep', @client.get_content(url + 'sleep?sec=0'))
415
+
416
+ start = Time.now
417
+ assert_raise(HTTPClient::ReceiveTimeoutError) do
418
+ @client.get_content(url + 'sleep?sec=5')
419
+ end
420
+ if Time.now - start > 3
421
+ # before #342 it detected timeout when IO was freed
422
+ fail 'timeout does not work'
423
+ end
424
+
425
+ @client.receive_timeout = 3
426
+ @client.reset_all
427
+ assert_equal('sleep', @client.get_content(url + 'sleep?sec=2'))
428
+ end
429
+
391
430
  private
392
431
 
393
432
  def cert(filename)
@@ -420,7 +459,7 @@ private
420
459
  :SSLCertName => nil
421
460
  )
422
461
  @serverport = @server.config[:Port]
423
- [:hello].each do |sym|
462
+ [:hello, :sleep].each do |sym|
424
463
  @server.mount(
425
464
  "/#{sym}",
426
465
  WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
@@ -490,6 +529,13 @@ private
490
529
  res.body = "hello"
491
530
  end
492
531
 
532
+ def do_sleep(req, res)
533
+ sec = req.query['sec'].to_i
534
+ sleep sec
535
+ res['content-type'] = 'text/html'
536
+ res.body = "sleep"
537
+ end
538
+
493
539
  def start_server_thread(server)
494
540
  t = Thread.new {
495
541
  Thread.current.abort_on_exception = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.2.4
4
+ version: 2.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hiroshi Nakamura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-10 00:00:00.000000000 Z
11
+ date: 2016-12-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: nahi@ruby-lang.org
@@ -69,6 +69,7 @@ files:
69
69
  - test/helper.rb
70
70
  - test/htdigest
71
71
  - test/htpasswd
72
+ - test/jruby_ssl_socket/test_pemutils.rb
72
73
  - test/runner.rb
73
74
  - test/server.cert
74
75
  - test/server.key
@@ -103,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
104
  version: '0'
104
105
  requirements: []
105
106
  rubyforge_project:
106
- rubygems_version: 2.5.1
107
+ rubygems_version: 2.6.8
107
108
  signing_key:
108
109
  specification_version: 4
109
110
  summary: gives something like the functionality of libwww-perl (LWP) in Ruby