httpclient 2.8.2.4 → 2.8.3

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
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