httpclient 2.8.1 → 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: c4ffce5e3ce378196b7441498f0a93969da6086f
4
- data.tar.gz: 91ba9e1062f6515828ae1983ca28eb0a2d40a75d
3
+ metadata.gz: 45217dcc777d36d71246dd468e40b79caad351d6
4
+ data.tar.gz: afcf1a175414e0a1dde95eb5823b0fa339655d9c
5
5
  SHA512:
6
- metadata.gz: 743acf9501f271b66d4b7dcc25812ea766f81d4e3eae6f5a4784ce157a3db7f81a386c6d8a7cced0b87cb464040752997047dad0d627cc94fb9451371f34a44b
7
- data.tar.gz: 1e14c7fdd6ec23d895310ca70f3a96a6bd44910371f6221c33095fa4302344da15574a109e800493481e8477dbbb33ce4c4d2cb0661e271c0846d3b8674cf8ba
6
+ metadata.gz: f5ae105eb3b269d67521a35446e3518b359e7a359c0c8a14500ef9e9ff8c4681c20b103bb4d5a2f96cdd9caa337fc149f39df3754bb9f3185b6914f284adfdc0
7
+ data.tar.gz: 005d1769b6906e0c107ba63e7a178c4d73aae59198ed3efe866e8722fdadcf4c4142c3724c167b6db5f8a45cb03f517083164c18fdd1aa65074dc4ab362bc9de
@@ -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
@@ -316,6 +333,7 @@ unless defined?(SSLSocket)
316
333
  return if cert_source == :default
317
334
  if cert_source.respond_to?(:to_pem)
318
335
  pem = cert_source.to_pem
336
+ load_pem(pem)
319
337
  elsif File.directory?(cert_source)
320
338
  warn("#{cert_source}: directory not yet supported")
321
339
  return
@@ -326,7 +344,8 @@ unless defined?(SSLSocket)
326
344
  when /-----BEGIN CERTIFICATE-----/
327
345
  pem = ''
328
346
  when /-----END CERTIFICATE-----/
329
- break
347
+ load_pem(pem)
348
+ # keep parsing in case where multiple certificates in a file
330
349
  else
331
350
  if pem
332
351
  pem << line
@@ -334,9 +353,6 @@ unless defined?(SSLSocket)
334
353
  end
335
354
  end
336
355
  end
337
- cert = PEMUtils.read_certificate(pem)
338
- @size += 1
339
- @trust_store.setCertificateEntry("cert_#{@size}", cert)
340
356
  end
341
357
 
342
358
  def trust_store
@@ -346,6 +362,14 @@ unless defined?(SSLSocket)
346
362
  @trust_store
347
363
  end
348
364
  end
365
+
366
+ private
367
+
368
+ def load_pem(pem)
369
+ cert = PEMUtils.read_certificate(pem)
370
+ @size += 1
371
+ @trust_store.setCertificateEntry("cert_#{@size}", cert)
372
+ end
349
373
  end
350
374
 
351
375
  # Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
@@ -433,26 +457,56 @@ unless defined?(SSLSocket)
433
457
  end
434
458
 
435
459
  def self.create_socket(session)
436
- site = session.proxy || session.dest
437
- 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
438
468
  begin
439
469
  if session.proxy
470
+ site = session.proxy || session.dest
471
+ socket = JavaSocketWrap.connect(Socket.new, site, opts)
440
472
  session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
441
473
  end
474
+ new(socket, session.dest, session.ssl_config, opts)
442
475
  rescue
443
- socket.close
476
+ socket.close if socket
444
477
  raise
445
478
  end
446
- new(socket, session.dest, session.ssl_config, session.debug_dev)
447
479
  end
448
480
 
449
481
  DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
450
- def initialize(socket, dest, config, debug_dev = nil)
482
+ def initialize(socket, dest, config, opts = {})
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)
451
502
  if config.ssl_version == :auto
452
- ssl_version = DEFAULT_SSL_PROTOCOL
503
+ DEFAULT_SSL_PROTOCOL
453
504
  else
454
- ssl_version = config.ssl_version.to_s.tr('_', '.')
505
+ config.ssl_version.to_s.tr('_', '.')
455
506
  end
507
+ end
508
+
509
+ def create_ssl_context(config)
456
510
  unless config.cert_store_crl_items.empty?
457
511
  raise NotImplementedError.new('Manual CRL configuration is not yet supported')
458
512
  end
@@ -481,36 +535,24 @@ unless defined?(SSLSocket)
481
535
  tmf.init(trust_store)
482
536
  tm = tmf.getTrustManagers
483
537
 
484
- ctx = SSLContext.getInstance(ssl_version)
538
+ ctx = SSLContext.getInstance(java_ssl_version(config))
485
539
  ctx.init(km, tm, nil)
486
540
  if config.timeout
487
541
  ctx.getClientSessionContext.setSessionTimeout(config.timeout)
488
542
  end
543
+ ctx
544
+ end
489
545
 
546
+ def create_ssl_socket(socket, dest, config, opts)
547
+ ctx = create_ssl_context(config)
490
548
  factory = ctx.getSocketFactory
491
- begin
549
+ if socket
492
550
  ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
493
- ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
494
- if config.ciphers != SSLConfig::CIPHERS_DEFAULT
495
- ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
496
- end
497
- ssl_socket.startHandshake
498
- ssl_session = ssl_socket.getSession
499
- @peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
500
- if $DEBUG
501
- warn("Protocol version: #{ssl_session.getProtocol}")
502
- warn("Cipher: #{ssl_socket.getSession.getCipherSuite}")
503
- end
504
- post_connection_check(dest.host, @peer_cert)
505
- rescue java.security.GeneralSecurityException => e
506
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
507
- rescue javax.net.ssl.SSLException => e
508
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
509
- rescue java.net.SocketException => e
510
- raise OpenSSL::SSL::SSLError.new(e.getMessage)
551
+ else
552
+ ssl_socket = factory.createSocket
553
+ JavaSocketWrap.connect(ssl_socket, dest, opts)
511
554
  end
512
-
513
- super(ssl_socket, debug_dev)
555
+ ssl_socket
514
556
  end
515
557
 
516
558
  def peer_cert
@@ -519,8 +561,23 @@ unless defined?(SSLSocket)
519
561
 
520
562
  private
521
563
 
522
- def post_connection_check(hostname, wrap_cert)
523
- BrowserCompatHostnameVerifier.new.verify(hostname, wrap_cert.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)
576
+ if !@config.verify?
577
+ return
578
+ else
579
+ BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
580
+ end
524
581
  end
525
582
  end
526
583
 
@@ -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 RUBY_ENGINE == 'jruby'
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.
@@ -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
@@ -44,16 +44,21 @@ class HTTPClient
44
44
  class Store
45
45
  attr_reader :_httpclient_cert_store_items
46
46
 
47
- def initialize(*a, &b)
48
- super(*a, &b)
47
+ # TODO: use prepend instead when we drop JRuby + 1.9.x support
48
+ wrapped = {}
49
+
50
+ wrapped[:initialize] = instance_method(:initialize)
51
+ define_method(:initialize) do |*args|
52
+ wrapped[:initialize].bind(self).call(*args)
49
53
  @_httpclient_cert_store_items = [ENV['SSL_CERT_FILE'] || :default]
50
54
  end
51
55
 
52
56
  [:add_cert, :add_file, :add_path].each do |m|
53
- wrapped = instance_method(m)
57
+ wrapped[m] = instance_method(m)
54
58
  define_method(m) do |cert|
55
- wrapped.bind(self).call(cert)
59
+ res = wrapped[m].bind(self).call(cert)
56
60
  @_httpclient_cert_store_items << cert
61
+ res
57
62
  end
58
63
  end
59
64
  end
@@ -61,55 +66,76 @@ class HTTPClient
61
66
  end
62
67
  end
63
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
+
64
88
  CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
65
89
 
66
90
  # Which TLS protocol version (also called method) will be used. Defaults
67
- # 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
68
92
  # with always the highest available protocol being used).
69
93
  # String name of OpenSSL's SSL version method name: TLSv1_2, TLSv1_1, TLSv1,
70
94
  # SSLv2, SSLv23, SSLv3 or :auto (and nil) to allow version negotiation (default).
71
95
  # See {OpenSSL::SSL::SSLContext::METHODS} for a list of available versions
72
96
  # in your specific Ruby environment.
73
- attr_reader :ssl_version
97
+ attr_config :ssl_version
74
98
  # OpenSSL::X509::Certificate:: certificate for SSL client authentication.
75
99
  # nil by default. (no client authentication)
76
- attr_reader :client_cert
100
+ attr_config :client_cert
77
101
  # OpenSSL::PKey::PKey:: private key for SSL client authentication.
78
102
  # nil by default. (no client authentication)
79
- attr_reader :client_key
80
- 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
81
107
 
82
108
  # A number which represents OpenSSL's verify mode. Default value is
83
109
  # OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
84
- attr_reader :verify_mode
110
+ attr_config :verify_mode
85
111
  # A number of verify depth. Certification path which length is longer than
86
112
  # this depth is not allowed.
87
113
  # CAUTION: this is OpenSSL specific option and ignored on JRuby.
88
- attr_reader :verify_depth
114
+ attr_config :verify_depth
89
115
  # A callback handler for custom certificate verification. nil by default.
90
116
  # If the handler is set, handler.call is invoked just after general
91
117
  # OpenSSL's verification. handler.call is invoked with 2 arguments,
92
118
  # ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
93
119
  # OpenSSL::X509::StoreContext.
94
- attr_reader :verify_callback
120
+ attr_config :verify_callback
95
121
  # SSL timeout in sec. nil by default.
96
- attr_reader :timeout
122
+ attr_config :timeout
97
123
  # A number of OpenSSL's SSL options. Default value is
98
124
  # OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
99
125
  # CAUTION: this is OpenSSL specific option and ignored on JRuby.
100
126
  # Use ssl_version to specify the TLS version you want to use.
101
- attr_reader :options
127
+ attr_config :options
102
128
  # A String of OpenSSL's cipher configuration. Default value is
103
129
  # ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
104
130
  # See ciphers(1) man in OpenSSL for more detail.
105
- attr_reader :ciphers
131
+ attr_config :ciphers
106
132
 
107
133
  # OpenSSL::X509::X509::Store used for verification. You can reset the
108
134
  # store with clear_cert_store and set the new store with cert_store=.
109
135
  attr_reader :cert_store # don't use if you don't know what it is.
110
136
 
111
137
  # For server side configuration. Ignore this.
112
- attr_reader :client_ca # :nodoc:
138
+ attr_config :client_ca # :nodoc:
113
139
 
114
140
  # These array keeps original files/dirs that was added to @cert_store
115
141
  def cert_store_items; @cert_store._httpclient_cert_store_items; end
@@ -121,7 +147,7 @@ class HTTPClient
121
147
  @client = client
122
148
  @cert_store = X509::Store.new
123
149
  @cert_store_crl_items = []
124
- @client_cert = @client_key = @client_ca = nil
150
+ @client_cert = @client_key = @client_key_pass = @client_ca = nil
125
151
  @verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
126
152
  @verify_depth = nil
127
153
  @verify_callback = nil
@@ -139,42 +165,18 @@ class HTTPClient
139
165
  @cacerts_loaded = false
140
166
  end
141
167
 
142
- # Sets SSL version method String. Possible values: "SSLv2" for SSL2,
143
- # "SSLv3" for SSL3 and TLS1.x, "SSLv23" for SSL3 with fallback to SSL2.
144
- def ssl_version=(ssl_version)
145
- @ssl_version = ssl_version
146
- change_notify
147
- end
148
-
149
- # Sets certificate (OpenSSL::X509::Certificate) for SSL client
150
- # authentication.
151
- # client_key and client_cert must be a pair.
152
- #
153
- # Calling this method resets all existing sessions.
154
- def client_cert=(client_cert)
155
- @client_cert = client_cert
156
- change_notify
157
- end
158
-
159
- # Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
160
- # client_key and client_cert must be a pair.
161
- #
162
- # Calling this method resets all existing sessions.
163
- def client_key=(client_key)
164
- @client_key = client_key
165
- change_notify
166
- end
167
-
168
168
  # Sets certificate and private key for SSL client authentication.
169
169
  # cert_file:: must be a filename of PEM/DER formatted file.
170
170
  # key_file:: must be a filename of PEM/DER formatted file. Key must be an
171
171
  # RSA key. If you want to use other PKey algorithm,
172
172
  # use client_key=.
173
173
  #
174
- # Calling this method resets all existing sessions.
174
+ # Calling this method resets all existing sessions if value is changed.
175
175
  def set_client_cert_file(cert_file, key_file, pass = nil)
176
- @client_cert, @client_key, @client_key_pass = cert_file, key_file, pass
177
- 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
178
180
  end
179
181
 
180
182
  # Sets OpenSSL's default trusted CA certificates. Generally, OpenSSL is
@@ -211,9 +213,12 @@ class HTTPClient
211
213
  #
212
214
  # Calling this method resets all existing sessions.
213
215
  def cert_store=(cert_store)
214
- @cacerts_loaded = true # avoid lazy override
215
- @cert_store = cert_store
216
- 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
217
222
  end
218
223
 
219
224
  # Sets trust anchor certificate(s) for verification.
@@ -271,62 +276,6 @@ class HTTPClient
271
276
  end
272
277
  alias set_crl add_crl
273
278
 
274
- # Sets verify mode of OpenSSL. New value must be a combination of
275
- # constants OpenSSL::SSL::VERIFY_*
276
- #
277
- # Calling this method resets all existing sessions.
278
- def verify_mode=(verify_mode)
279
- @verify_mode = verify_mode
280
- change_notify
281
- end
282
-
283
- # Sets verify depth. New value must be a number.
284
- #
285
- # Calling this method resets all existing sessions.
286
- def verify_depth=(verify_depth)
287
- @verify_depth = verify_depth
288
- change_notify
289
- end
290
-
291
- # Sets callback handler for custom certificate verification.
292
- # See verify_callback.
293
- #
294
- # Calling this method resets all existing sessions.
295
- def verify_callback=(verify_callback)
296
- @verify_callback = verify_callback
297
- change_notify
298
- end
299
-
300
- # Sets SSL timeout in sec.
301
- #
302
- # Calling this method resets all existing sessions.
303
- def timeout=(timeout)
304
- @timeout = timeout
305
- change_notify
306
- end
307
-
308
- # Sets SSL options. New value must be a combination of # constants
309
- # OpenSSL::SSL::OP_*
310
- #
311
- # Calling this method resets all existing sessions.
312
- def options=(options)
313
- @options = options
314
- change_notify
315
- end
316
-
317
- # Sets cipher configuration. New value must be a String.
318
- #
319
- # Calling this method resets all existing sessions.
320
- def ciphers=(ciphers)
321
- @ciphers = ciphers
322
- change_notify
323
- end
324
-
325
- def client_ca=(client_ca) # :nodoc:
326
- @client_ca = client_ca
327
- change_notify
328
- end
329
-
330
279
  def verify?
331
280
  @verify_mode && (@verify_mode & OpenSSL::SSL::VERIFY_PEER != 0)
332
281
  end
@@ -466,7 +415,6 @@ class HTTPClient
466
415
 
467
416
  # Use 2048 bit certs trust anchor
468
417
  def load_cacerts(cert_store)
469
- ver = OpenSSL::OPENSSL_VERSION
470
418
  file = File.join(File.dirname(__FILE__), 'cacert.pem')
471
419
  add_trust_ca_to_store(cert_store, file)
472
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.1'
2
+ VERSION = '2.8.3'
3
3
  end
data/lib/httpclient.rb CHANGED
@@ -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.
@@ -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)
data/test/test_ssl.rb CHANGED
@@ -80,6 +80,16 @@ end
80
80
  assert(str.scan(/^hello$/)[0])
81
81
  end
82
82
 
83
+ def test_verification_without_httpclient
84
+ 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-----"
85
+ raw_ca_cert = "-----BEGIN CERTIFICATE-----\nMIIDYjCCAkqgAwIBAgIBATANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB\nGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5\nIENBMB4XDTE2MDgxMDE3MjA1NFoXDTE4MDgxMDE3MjA1NFowQjETMBEGCgmSJomT\n8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEQMA4GA1UEAwwH\nUnVieSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKGwyM3Ejtl\npo7CqaDlS71gDZn3gm6IwWpmRMLJofSI9LCwAbjijSC2HvO0xUWoYW40FbzjnnEi\ngszsWyPwuQIx9t0bhuAyllNIfImmkaQkrikXKBKzia4jPnbc4iXPnfjuThjESFWl\ntfbN6y1B5TjKhD1KelfakUO+iMu8WlIA9NKQZYfJ/F3QSpP5Iqb3KN/jVifFbDV8\nbAl3Ln4rT2kTCKrZZcl1jmWsJv8jBw6+P7hk0/Mu0JeHAITsjbNbpHd8UXpCfbVs\nsNGZrBU4uJdZ2YTG+Y27/t25jFNQwb+TWbvig7rfdX2sjssuxa00BBxarC08tIVj\nZprM37KcNn8CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAQYwHQYDVR0OBBYEFA2/NjMnJd6YaHFsDw0xcuiUOc/KMB8GA1UdIwQYMBYEFA2/\nNjMnJd6YaHFsDw0xcuiUOc/KMA0GCSqGSIb3DQEBCwUAA4IBAQAJSOw49XqvUll0\n3vU9EAO6yUdeZSsQENIfYbRMQgapbnN1vTyrUjPZkGC5hIE1pVdoHtEoUEICxIwy\nr6BKxiSLBDLp+rvIuDdzMkXIWdUVvTZguVRyKtM2gfnpsPLpVnv+stBmAW2SMyxm\nkymhOpkjdv3He+45uorB3tdfBS9VVomDEUJdg38UE1b5eXRQ3D6gG0iCPFzKszXg\nLoAYhGxtjCJaKlbzduMK0YO6aelgW1+XnVIKcA7DJ9egk5d/dFZBPFfwumwr9hTH\nh7/fp3Fr87weI+CkfmFyJZrsEBlXJBVuvPesMVHTh3Whm5kmCdWcBJU0QmSq42ZL\n72U0PXLR\n-----END CERTIFICATE-----"
86
+ ca_cert = ::OpenSSL::X509::Certificate.new(raw_ca_cert)
87
+ cert = ::OpenSSL::X509::Certificate.new(raw_cert)
88
+ store = ::OpenSSL::X509::Store.new
89
+ store.add_cert(ca_cert)
90
+ assert(store.verify(cert))
91
+ end
92
+
83
93
  def test_verification
84
94
  cfg = @client.ssl_config
85
95
  cfg.verify_callback = method(:verify_callback).to_proc
@@ -165,6 +175,13 @@ end
165
175
  #
166
176
  cfg.cert_store.add_cert(cert('subca.cert'))
167
177
  assert_equal("hello", @client.get_content(@url))
178
+ cfg.clear_cert_store
179
+ begin
180
+ @client.get(@url)
181
+ assert(false)
182
+ rescue OpenSSL::SSL::SSLError => ssle
183
+ assert_match(/(certificate verify failed|unable to find valid certification path to requested target)/, ssle.message)
184
+ end
168
185
  end
169
186
 
170
187
  if defined?(HTTPClient::JRubySSLSocket)
@@ -217,18 +234,14 @@ else
217
234
  end
218
235
  end
219
236
 
220
- # SSL_CERT_FILE does not work with recent jruby-openssl.
221
- # You should not depend on SSL_CERT_FILE on JRuby
222
- if !defined?(JRUBY_VERSION)
223
- def test_set_default_paths
224
- assert_raise(OpenSSL::SSL::SSLError) do
225
- @client.get(@url)
226
- end
227
- escape_env do
228
- ENV['SSL_CERT_FILE'] = File.join(DIR, 'ca-chain.pem')
229
- @client.ssl_config.set_default_paths
230
- @client.get(@url)
231
- end
237
+ def test_set_default_paths
238
+ assert_raise(OpenSSL::SSL::SSLError) do
239
+ @client.get(@url)
240
+ end
241
+ escape_env do
242
+ ENV['SSL_CERT_FILE'] = File.join(DIR, 'ca-chain.pem')
243
+ @client.ssl_config.set_default_paths
244
+ @client.get(@url)
232
245
  end
233
246
  end
234
247
 
@@ -262,6 +275,158 @@ end
262
275
  end
263
276
  end
264
277
 
278
+ VERIFY_TEST_CERT_LOCALHOST = OpenSSL::X509::Certificate.new(<<-EOS)
279
+ -----BEGIN CERTIFICATE-----
280
+ MIIB9jCCAV+gAwIBAgIJAIH8Gsm4PcNKMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
281
+ BAMMCWxvY2FsaG9zdDAeFw0xNjA4MTgxMDI2MDVaFw00NDAxMDMxMDI2MDVaMBQx
282
+ EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
283
+ p7D8q0lcx5EZEV5+zPnQsxrbft5xyhH/MCStbH46DRATGPNSOaLRCG5r8gTKQzpD
284
+ 4swGrQFYe2ienQ+7o4aEHErsXp4O/EmDKeiXWWrMqPr23r3HOBDebuynC/sCwy7N
285
+ epnX9u1VLB03eo+suj4d86OoOF+o11t9ZP+GA29Rsf8CAwEAAaNQME4wHQYDVR0O
286
+ BBYEFIxsJuPVvd5KKFcAvHGSeKSsWiUJMB8GA1UdIwQYMBaAFIxsJuPVvd5KKFcA
287
+ vHGSeKSsWiUJMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAMJaVCrrM
288
+ SM2I06Vr4BL+jtDFhZh3HmJFEDpwEFQ5Y9hduwdUGRBGCpkuea3fE2FKwWW9gLM1
289
+ w7rFMzYFtCEqm78dJWIU79MRy0wjO4LgtYfoikgBh6JKWuV5ed/+L3sLyLG0ZTtv
290
+ lrD7lzDtXgwvj007PxDoYRp3JwYzKRmTbH8=
291
+ -----END CERTIFICATE-----
292
+ EOS
293
+
294
+ VERIFY_TEST_CERT_FOO_DOMAIN = OpenSSL::X509::Certificate.new(<<-EOS)
295
+ -----BEGIN CERTIFICATE-----
296
+ MIIB8jCCAVugAwIBAgIJAL/od7Whx7VTMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
297
+ BAMMB2Zvby5jb20wHhcNMTYwODE4MTAyMzUyWhcNNDQwMTAzMTAyMzUyWjASMRAw
298
+ DgYDVQQDDAdmb28uY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnsPyr
299
+ SVzHkRkRXn7M+dCzGtt+3nHKEf8wJK1sfjoNEBMY81I5otEIbmvyBMpDOkPizAat
300
+ AVh7aJ6dD7ujhoQcSuxeng78SYMp6JdZasyo+vbevcc4EN5u7KcL+wLDLs16mdf2
301
+ 7VUsHTd6j6y6Ph3zo6g4X6jXW31k/4YDb1Gx/wIDAQABo1AwTjAdBgNVHQ4EFgQU
302
+ jGwm49W93kooVwC8cZJ4pKxaJQkwHwYDVR0jBBgwFoAUjGwm49W93kooVwC8cZJ4
303
+ pKxaJQkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQCVKTvfxx+yezuR
304
+ 5WpVKw1E9qabKOYFB5TqdHMHreRubMJTaoZC+YzhcCwtyLlAA9+axKINAiMM8T+z
305
+ jjfOHQSa2GS2TaaVDJWmXIgsAlEbjd2BEiQF0LZYGJRG9pyq0WbTV+CyFdrghjcO
306
+ xX/t7OG7NfOG9dhv3J+5SX10S5V5Dg==
307
+ -----END CERTIFICATE-----
308
+ EOS
309
+
310
+ VERIFY_TEST_CERT_ALT_NAME = OpenSSL::X509::Certificate.new(<<-EOS)
311
+ -----BEGIN CERTIFICATE-----
312
+ MIICDDCCAXWgAwIBAgIJAOxXY4nOwxhGMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
313
+ BAMMCWxvY2FsaG9zdDAeFw0xNjA4MTgxMDM0NTJaFw00NDAxMDMxMDM0NTJaMBQx
314
+ EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
315
+ p7D8q0lcx5EZEV5+zPnQsxrbft5xyhH/MCStbH46DRATGPNSOaLRCG5r8gTKQzpD
316
+ 4swGrQFYe2ienQ+7o4aEHErsXp4O/EmDKeiXWWrMqPr23r3HOBDebuynC/sCwy7N
317
+ epnX9u1VLB03eo+suj4d86OoOF+o11t9ZP+GA29Rsf8CAwEAAaNmMGQwFAYDVR0R
318
+ BA0wC4IJKi5mb28uY29tMB0GA1UdDgQWBBSMbCbj1b3eSihXALxxknikrFolCTAf
319
+ BgNVHSMEGDAWgBSMbCbj1b3eSihXALxxknikrFolCTAMBgNVHRMEBTADAQH/MA0G
320
+ CSqGSIb3DQEBCwUAA4GBADJlKNFuOnsDIhHGW72HuQw4naN6lM3eZE9JJ+UF/XIF
321
+ ghGtgqw+00Yy5wMFc1K2Wm4p5NymmDfC/P1FOe34bpxt9/IWm6mEoIWoodC3N4Cm
322
+ PtnSS1/CRWzVIPGMglTGGDcUc70tfeAWgyTxgcNQd4vTFtnN0f0RDdaXa8kfKMTw
323
+ -----END CERTIFICATE-----
324
+ EOS
325
+
326
+ VERIFY_TEST_PKEY = OpenSSL::PKey::RSA.new(<<-EOS)
327
+ -----BEGIN RSA PRIVATE KEY-----
328
+ MIICXQIBAAKBgQCnsPyrSVzHkRkRXn7M+dCzGtt+3nHKEf8wJK1sfjoNEBMY81I5
329
+ otEIbmvyBMpDOkPizAatAVh7aJ6dD7ujhoQcSuxeng78SYMp6JdZasyo+vbevcc4
330
+ EN5u7KcL+wLDLs16mdf27VUsHTd6j6y6Ph3zo6g4X6jXW31k/4YDb1Gx/wIDAQAB
331
+ AoGAe0RHx+WKtQx8/96VmTl951qzxMPho2etTYd4kAsNwzJwx2N9qu57eBYrdWF+
332
+ CQMYievucFhP4Y+bINtC1Eb6btz9TCUwjCfeIxfGRoFf3cxVmxlsRJJmN1kSZlu1
333
+ yYlcMVuP4noeFIMQBRrt5pyLCx2Z9A01NCQT4Y6VoREBIeECQQDWeNhsL6xkrmdB
334
+ M9+zl+SqHdNKhgKwMdp74+UNnAV9I8GB7bGlOWhc83aqMLgS+JBDFXcmNF/KawTR
335
+ zcnkod5xAkEAyClFgr3lZQSnwUwoA/AOcyW0+H63taaaXS/g8n3H8ENK6kL4ldUx
336
+ IgCk2ekbQ5Y3S2WScIGXNxMOza9MlsOvbwJAPUtoPvMZB+U4KVBT/JXKijvf6QqH
337
+ tidpU8L78XnHr84KPcHa5WeUxgvmvBkUYoebYzC9TrPlNIqFZBi2PJtuYQJBAMda
338
+ E5j7eJT75fhm2RPS6xFT5MH5sw6AOA3HucrJ63AoFVzsBpl0E9NBwO4ndLgDzF6T
339
+ cx4Kc4iuunewuB8QFpECQQCfvsHCjIJ/X4kiqeBzxDq2GR/oDgQkOzY+4H9U7Lwl
340
+ e61RBaxk5OHOA0bLtvJblV6NL72ZEZhX60wAWbrOPhpT
341
+ -----END RSA PRIVATE KEY-----
342
+ EOS
343
+
344
+ def test_post_connection_check
345
+ teardown_server
346
+ setup_server_with_server_cert(nil, VERIFY_TEST_CERT_LOCALHOST, VERIFY_TEST_PKEY)
347
+ file = Tempfile.new('cert')
348
+ File.write(file.path, VERIFY_TEST_CERT_LOCALHOST.to_pem)
349
+ @client.ssl_config.add_trust_ca(file.path)
350
+ assert_nothing_raised do
351
+ @client.get("https://localhost:#{serverport}/hello")
352
+ end
353
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
354
+ assert_nothing_raised do
355
+ @client.get("https://localhost:#{serverport}/hello")
356
+ end
357
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
358
+
359
+ teardown_server
360
+ setup_server_with_server_cert(nil, VERIFY_TEST_CERT_FOO_DOMAIN, VERIFY_TEST_PKEY)
361
+ File.write(file.path, VERIFY_TEST_CERT_FOO_DOMAIN.to_pem)
362
+ @client.ssl_config.add_trust_ca(file.path)
363
+ assert_raises(OpenSSL::SSL::SSLError) do
364
+ @client.get("https://localhost:#{serverport}/hello")
365
+ end
366
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
367
+ assert_nothing_raised do
368
+ @client.get("https://localhost:#{serverport}/hello")
369
+ end
370
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
371
+
372
+ teardown_server
373
+ setup_server_with_server_cert(nil, VERIFY_TEST_CERT_ALT_NAME, VERIFY_TEST_PKEY)
374
+ File.write(file.path, VERIFY_TEST_CERT_ALT_NAME.to_pem)
375
+ @client.ssl_config.add_trust_ca(file.path)
376
+ assert_raises(OpenSSL::SSL::SSLError) do
377
+ @client.get("https://localhost:#{serverport}/hello")
378
+ end
379
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
380
+ assert_nothing_raised do
381
+ @client.get("https://localhost:#{serverport}/hello")
382
+ end
383
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
384
+ end
385
+
386
+ def test_x509_store_add_cert_prepend
387
+ store = OpenSSL::X509::Store.new
388
+ assert_equal(store, store.add_cert(OpenSSL::X509::Certificate.new(VERIFY_TEST_CERT_LOCALHOST)))
389
+ end
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
+
265
430
  private
266
431
 
267
432
  def cert(filename)
@@ -294,7 +459,7 @@ private
294
459
  :SSLCertName => nil
295
460
  )
296
461
  @serverport = @server.config[:Port]
297
- [:hello].each do |sym|
462
+ [:hello, :sleep].each do |sym|
298
463
  @server.mount(
299
464
  "/#{sym}",
300
465
  WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
@@ -332,11 +497,45 @@ private
332
497
  @server_thread = start_server_thread(@server)
333
498
  end
334
499
 
500
+ def setup_server_with_server_cert(ca_cert, server_cert, server_key)
501
+ logger = Logger.new(STDERR)
502
+ logger.level = Logger::Severity::FATAL # avoid logging SSLError (ERROR level)
503
+ @server = WEBrick::HTTPServer.new(
504
+ :BindAddress => "localhost",
505
+ :Logger => logger,
506
+ :Port => 0,
507
+ :AccessLog => [],
508
+ :DocumentRoot => DIR,
509
+ :SSLEnable => true,
510
+ :SSLCACertificateFile => ca_cert,
511
+ :SSLCertificate => server_cert,
512
+ :SSLPrivateKey => server_key,
513
+ :SSLVerifyClient => nil,
514
+ :SSLClientCA => nil,
515
+ :SSLCertName => nil
516
+ )
517
+ @serverport = @server.config[:Port]
518
+ [:hello].each do |sym|
519
+ @server.mount(
520
+ "/#{sym}",
521
+ WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
522
+ )
523
+ end
524
+ @server_thread = start_server_thread(@server)
525
+ end
526
+
335
527
  def do_hello(req, res)
336
528
  res['content-type'] = 'text/html'
337
529
  res.body = "hello"
338
530
  end
339
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
+
340
539
  def start_server_thread(server)
341
540
  t = Thread.new {
342
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.1
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-08-07 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.4.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