httpclient 2.8.1 → 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: 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