httpclient 2.3.0.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +85 -0
  3. data/bin/httpclient +18 -6
  4. data/bin/jsonclient +85 -0
  5. data/lib/http-access2.rb +1 -1
  6. data/lib/httpclient.rb +262 -88
  7. data/lib/httpclient/auth.rb +269 -244
  8. data/lib/httpclient/cacert.pem +3952 -0
  9. data/lib/httpclient/cacert1024.pem +3866 -0
  10. data/lib/httpclient/connection.rb +1 -1
  11. data/lib/httpclient/cookie.rb +161 -514
  12. data/lib/httpclient/http.rb +57 -21
  13. data/lib/httpclient/include_client.rb +2 -0
  14. data/lib/httpclient/jruby_ssl_socket.rb +588 -0
  15. data/lib/httpclient/session.rb +259 -317
  16. data/lib/httpclient/ssl_config.rb +141 -188
  17. data/lib/httpclient/ssl_socket.rb +150 -0
  18. data/lib/httpclient/timeout.rb +1 -1
  19. data/lib/httpclient/util.rb +62 -1
  20. data/lib/httpclient/version.rb +1 -1
  21. data/lib/httpclient/webagent-cookie.rb +459 -0
  22. data/lib/jsonclient.rb +63 -0
  23. data/lib/oauthclient.rb +2 -1
  24. data/sample/jsonclient.rb +67 -0
  25. data/sample/oauth_twitter.rb +4 -4
  26. data/test/{ca-chain.cert → ca-chain.pem} +0 -0
  27. data/test/client-pass.key +18 -0
  28. data/test/helper.rb +10 -8
  29. data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
  30. data/test/test_auth.rb +175 -4
  31. data/test/test_cookie.rb +147 -243
  32. data/test/test_http-access2.rb +17 -16
  33. data/test/test_httpclient.rb +458 -77
  34. data/test/test_jsonclient.rb +80 -0
  35. data/test/test_ssl.rb +341 -17
  36. data/test/test_webagent-cookie.rb +465 -0
  37. metadata +57 -55
  38. data/README.txt +0 -721
  39. data/lib/httpclient/cacert.p7s +0 -1858
  40. data/lib/httpclient/cacert_sha1.p7s +0 -1858
  41. data/sample/oauth_salesforce_10.rb +0 -63
@@ -1,5 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
4
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
5
  #
4
6
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
7
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -10,6 +12,7 @@ require 'time'
10
12
  if defined?(Encoding::ASCII_8BIT)
11
13
  require 'open-uri' # for encoding
12
14
  end
15
+ require 'httpclient/util'
13
16
 
14
17
 
15
18
  # A namespace module for HTTP Message definitions used by HTTPClient.
@@ -93,6 +96,7 @@ module HTTP
93
96
  # p res.header['last-modified'].first
94
97
  #
95
98
  class Message
99
+ include HTTPClient::Util
96
100
 
97
101
  CRLF = "\r\n"
98
102
 
@@ -196,6 +200,7 @@ module HTTP
196
200
  @request_uri = uri || NIL_URI
197
201
  @request_query = query
198
202
  @request_absolute_uri = false
203
+ self
199
204
  end
200
205
 
201
206
  # Initialize this instance as a response.
@@ -207,6 +212,7 @@ module HTTP
207
212
  @request_uri = req.request_uri
208
213
  @request_query = req.request_query
209
214
  end
215
+ self
210
216
  end
211
217
 
212
218
  # Sets status code and reason phrase.
@@ -393,9 +399,9 @@ module HTTP
393
399
  if @http_version >= '1.1' and get('Host').empty?
394
400
  if @request_uri.port == @request_uri.default_port
395
401
  # GFE/1.3 dislikes default port number (returns 404)
396
- set('Host', "#{@request_uri.host}")
402
+ set('Host', "#{@request_uri.hostname}")
397
403
  else
398
- set('Host', "#{@request_uri.host}:#{@request_uri.port}")
404
+ set('Host', "#{@request_uri.hostname}:#{@request_uri.port}")
399
405
  end
400
406
  end
401
407
  end
@@ -439,6 +445,8 @@ module HTTP
439
445
  attr_reader :size
440
446
  # maxbytes of IO#read for streaming request. See DEFAULT_CHUNK_SIZE.
441
447
  attr_accessor :chunk_size
448
+ # Hash that keeps IO positions
449
+ attr_accessor :positions
442
450
 
443
451
  # Default value for chunk_size
444
452
  DEFAULT_CHUNK_SIZE = 1024 * 16
@@ -458,6 +466,7 @@ module HTTP
458
466
  @positions = {}
459
467
  set_content(body, boundary)
460
468
  @chunk_size = DEFAULT_CHUNK_SIZE
469
+ self
461
470
  end
462
471
 
463
472
  # Initialize this instance as a response.
@@ -470,6 +479,7 @@ module HTTP
470
479
  else
471
480
  @size = nil
472
481
  end
482
+ self
473
483
  end
474
484
 
475
485
  # Dumps message body to given dev.
@@ -479,13 +489,15 @@ module HTTP
479
489
  # reason. (header is dumped to dev, too)
480
490
  # If no dev (the second argument) given, this method returns a dumped
481
491
  # String.
492
+ #
493
+ # assert: @size is not nil
482
494
  def dump(header = '', dev = '')
483
495
  if @body.is_a?(Parts)
484
496
  dev << header
485
497
  @body.parts.each do |part|
486
498
  if Message.file?(part)
487
499
  reset_pos(part)
488
- dump_file(part, dev)
500
+ dump_file(part, dev, @body.sizes[part])
489
501
  else
490
502
  dev << part
491
503
  end
@@ -493,7 +505,7 @@ module HTTP
493
505
  elsif Message.file?(@body)
494
506
  dev << header
495
507
  reset_pos(@body)
496
- dump_file(@body, dev)
508
+ dump_file(@body, dev, @size)
497
509
  elsif @body
498
510
  dev << header + @body
499
511
  else
@@ -561,10 +573,14 @@ module HTTP
561
573
  io.pos = @positions[io] if @positions.key?(io)
562
574
  end
563
575
 
564
- def dump_file(io, dev)
576
+ def dump_file(io, dev, sz)
565
577
  buf = ''
566
- while !io.read(@chunk_size, buf).nil?
578
+ rest = sz
579
+ while rest > 0
580
+ n = io.read([rest, @chunk_size].min, buf)
581
+ raise ArgumentError.new("Illegal size value: #size returns #{sz} but cannot read") if n.nil?
567
582
  dev << buf
583
+ rest -= n.bytesize
568
584
  end
569
585
  end
570
586
 
@@ -589,10 +605,12 @@ module HTTP
589
605
 
590
606
  class Parts
591
607
  attr_reader :size
608
+ attr_reader :sizes
592
609
 
593
610
  def initialize
594
611
  @body = []
595
- @size = 0
612
+ @sizes = {}
613
+ @size = 0 # total
596
614
  @as_stream = false
597
615
  end
598
616
 
@@ -601,15 +619,18 @@ module HTTP
601
619
  @as_stream = true
602
620
  @body << part
603
621
  if part.respond_to?(:lstat)
604
- @size += part.lstat.size
622
+ sz = part.lstat.size
623
+ add_size(part, sz)
605
624
  elsif part.respond_to?(:size)
606
625
  if sz = part.size
607
- @size += sz
626
+ add_size(part, sz)
608
627
  else
628
+ @sizes.clear
609
629
  @size = nil
610
630
  end
611
631
  else
612
632
  # use chunked upload
633
+ @sizes.clear
613
634
  @size = nil
614
635
  end
615
636
  elsif @body[-1].is_a?(String)
@@ -628,6 +649,15 @@ module HTTP
628
649
  [@body.join]
629
650
  end
630
651
  end
652
+
653
+ private
654
+
655
+ def add_size(part, sz)
656
+ if @size
657
+ @sizes[part] = sz
658
+ @size += sz
659
+ end
660
+ end
631
661
  end
632
662
 
633
663
  def build_query_multipart_str(query, boundary)
@@ -670,8 +700,9 @@ module HTTP
670
700
 
671
701
  def params_from_file(value)
672
702
  params = {}
703
+ original_filename = value.respond_to?(:original_filename) ? value.original_filename : nil
673
704
  path = value.respond_to?(:path) ? value.path : nil
674
- params['filename'] = File.basename(path || '')
705
+ params['filename'] = original_filename || File.basename(path || '')
675
706
  # Creation time is not available from File::Stat
676
707
  if value.respond_to?(:mtime)
677
708
  params['modification-date'] = value.mtime.rfc822
@@ -778,6 +809,8 @@ module HTTP
778
809
  case path
779
810
  when /\.txt$/i
780
811
  'text/plain'
812
+ when /\.xml$/i
813
+ 'text/xml'
781
814
  when /\.(htm|html)$/i
782
815
  'text/html'
783
816
  when /\.doc$/i
@@ -842,11 +875,11 @@ module HTTP
842
875
  query.each { |attr, value|
843
876
  left = escape(attr.to_s) << '='
844
877
  if values = Array.try_convert(value)
845
- values.each { |value|
846
- if value.respond_to?(:read)
847
- value = value.read
878
+ values.each { |v|
879
+ if v.respond_to?(:read)
880
+ v = v.read
848
881
  end
849
- pairs.push(left + escape(value.to_s))
882
+ pairs.push(left + escape(v.to_s))
850
883
  }
851
884
  else
852
885
  if value.respond_to?(:read)
@@ -906,12 +939,17 @@ module HTTP
906
939
  # used for retrieving the response.
907
940
  attr_accessor :peer_cert
908
941
 
942
+ # The other Message object when this Message is generated instead of
943
+ # the Message because of redirection, negotiation, or format conversion.
944
+ attr_accessor :previous
945
+
909
946
  # Creates a Message. This method should be used internally.
910
947
  # Use Message.new_connect_request, Message.new_request or
911
948
  # Message.new_response instead.
912
949
  def initialize # :nodoc:
913
950
  @http_header = Headers.new
914
951
  @http_body = @peer_cert = nil
952
+ @previous = nil
915
953
  end
916
954
 
917
955
  # Dumps message (header and body) to given dev.
@@ -947,12 +985,12 @@ module HTTP
947
985
 
948
986
  VERSION_WARNING = 'Message#version (Float) is deprecated. Use Message#http_version (String) instead.'
949
987
  def version
950
- warn(VERSION_WARNING)
988
+ warning(VERSION_WARNING)
951
989
  @http_header.http_version.to_f
952
990
  end
953
991
 
954
992
  def version=(version)
955
- warn(VERSION_WARNING)
993
+ warning(VERSION_WARNING)
956
994
  @http_header.http_version = version
957
995
  end
958
996
 
@@ -1021,10 +1059,8 @@ module HTTP
1021
1059
  unless set_cookies.empty?
1022
1060
  uri = http_header.request_uri
1023
1061
  set_cookies.map { |str|
1024
- cookie = WebAgent::Cookie.new
1025
- cookie.parse(str, uri)
1026
- cookie
1027
- }
1062
+ WebAgent::Cookie.parse(str, uri)
1063
+ }.flatten
1028
1064
  end
1029
1065
  end
1030
1066
 
@@ -36,6 +36,8 @@
36
36
  # are also provided. Widget.http_client is identical to Widget.new.http_client
37
37
  #
38
38
  #
39
+ require 'httpclient'
40
+
39
41
  class HTTPClient
40
42
  module IncludeClient
41
43
 
@@ -0,0 +1,588 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+
9
+ require 'java'
10
+ require 'httpclient/ssl_config'
11
+
12
+
13
+ class HTTPClient
14
+
15
+ unless defined?(SSLSocket)
16
+
17
+ class JavaSocketWrap
18
+ java_import 'java.net.InetSocketAddress'
19
+ java_import 'java.io.BufferedInputStream'
20
+
21
+ BUF_SIZE = 1024 * 16
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
+
35
+ def initialize(socket, debug_dev = nil)
36
+ @socket = socket
37
+ @debug_dev = debug_dev
38
+ @outstr = @socket.getOutputStream
39
+ @instr = BufferedInputStream.new(@socket.getInputStream)
40
+ @buf = (' ' * BUF_SIZE).to_java_bytes
41
+ @bufstr = ''
42
+ end
43
+
44
+ def close
45
+ @socket.close
46
+ end
47
+
48
+ def closed?
49
+ @socket.isClosed
50
+ end
51
+
52
+ def eof?
53
+ @socket.isClosed
54
+ end
55
+
56
+ def gets(rs)
57
+ while (size = @bufstr.index(rs)).nil?
58
+ if fill() == -1
59
+ size = @bufstr.size
60
+ break
61
+ end
62
+ end
63
+ str = @bufstr.slice!(0, size + rs.size)
64
+ debug(str)
65
+ str
66
+ end
67
+
68
+ def read(size, buf = nil)
69
+ while @bufstr.size < size
70
+ if fill() == -1
71
+ break
72
+ end
73
+ end
74
+ str = @bufstr.slice!(0, size)
75
+ debug(str)
76
+ if buf
77
+ buf.replace(str)
78
+ else
79
+ str
80
+ end
81
+ end
82
+
83
+ def readpartial(size, buf = nil)
84
+ while @bufstr.size == 0
85
+ if fill() == -1
86
+ raise EOFError.new('end of file reached')
87
+ end
88
+ end
89
+ str = @bufstr.slice!(0, size)
90
+ debug(str)
91
+ if buf
92
+ buf.replace(str)
93
+ else
94
+ str
95
+ end
96
+ end
97
+
98
+ def <<(str)
99
+ rv = @outstr.write(str.to_java_bytes)
100
+ debug(str)
101
+ rv
102
+ end
103
+
104
+ def flush
105
+ @socket.flush
106
+ end
107
+
108
+ def sync
109
+ true
110
+ end
111
+
112
+ def sync=(sync)
113
+ unless sync
114
+ raise "sync = false is not supported. This option was introduced for backward compatibility just in case."
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def fill
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}")
129
+ end
130
+ end
131
+
132
+ def debug(str)
133
+ @debug_dev << str if @debug_dev && str
134
+ end
135
+ end
136
+
137
+ class JRubySSLSocket < JavaSocketWrap
138
+ java_import 'java.io.ByteArrayInputStream'
139
+ java_import 'java.io.InputStreamReader'
140
+ java_import 'java.net.Socket'
141
+ java_import 'java.security.KeyStore'
142
+ java_import 'java.security.cert.Certificate'
143
+ java_import 'java.security.cert.CertificateFactory'
144
+ java_import 'javax.net.ssl.KeyManagerFactory'
145
+ java_import 'javax.net.ssl.SSLContext'
146
+ java_import 'javax.net.ssl.SSLSocketFactory'
147
+ java_import 'javax.net.ssl.TrustManager'
148
+ java_import 'javax.net.ssl.TrustManagerFactory'
149
+ java_import 'javax.net.ssl.X509TrustManager'
150
+ java_import 'org.jruby.ext.openssl.x509store.PEMInputOutput'
151
+
152
+ class JavaCertificate
153
+ attr_reader :cert
154
+
155
+ def initialize(cert)
156
+ @cert = cert
157
+ end
158
+
159
+ def subject
160
+ @cert.getSubjectDN
161
+ end
162
+
163
+ def to_text
164
+ @cert.toString
165
+ end
166
+
167
+ def to_pem
168
+ '(not in PEM format)'
169
+ end
170
+ end
171
+
172
+ class SSLStoreContext
173
+ attr_reader :current_cert, :chain, :error_depth, :error, :error_string
174
+
175
+ def initialize(current_cert, chain, error_depth, error, error_string)
176
+ @current_cert, @chain, @error_depth, @error, @error_string =
177
+ current_cert, chain, error_depth, error, error_string
178
+ end
179
+ end
180
+
181
+ class JSSEVerifyCallback
182
+ def initialize(verify_callback)
183
+ @verify_callback = verify_callback
184
+ end
185
+
186
+ def call(is_ok, chain, error_depth = -1, error = -1, error_string = '(unknown)')
187
+ if @verify_callback
188
+ ruby_chain = chain.map { |cert|
189
+ JavaCertificate.new(cert)
190
+ }.reverse
191
+ # NOTE: The order depends on provider implementation
192
+ ruby_chain.each do |cert|
193
+ is_ok = @verify_callback.call(
194
+ is_ok,
195
+ SSLStoreContext.new(cert, ruby_chain, error_depth, error, error_string)
196
+ )
197
+ end
198
+ end
199
+ is_ok
200
+ end
201
+ end
202
+
203
+ class VerifyNoneTrustManagerFactory
204
+ class VerifyNoneTrustManager
205
+ include X509TrustManager
206
+
207
+ def initialize(verify_callback)
208
+ @verify_callback = JSSEVerifyCallback.new(verify_callback)
209
+ end
210
+
211
+ def checkServerTrusted(chain, authType)
212
+ @verify_callback.call(true, chain)
213
+ end
214
+
215
+ def checkClientTrusted(chain, authType); end
216
+ def getAcceptedIssuers; end
217
+ end
218
+
219
+ def initialize(verify_callback = nil)
220
+ @verify_callback = verify_callback
221
+ end
222
+
223
+ def init(trustStore)
224
+ @managers = [VerifyNoneTrustManager.new(@verify_callback)].to_java(X509TrustManager)
225
+ end
226
+
227
+ def getTrustManagers
228
+ @managers
229
+ end
230
+ end
231
+
232
+ class SystemTrustManagerFactory
233
+ class SystemTrustManager
234
+ include X509TrustManager
235
+
236
+ def initialize(original, verify_callback)
237
+ @original = original
238
+ @verify_callback = JSSEVerifyCallback.new(verify_callback)
239
+ end
240
+
241
+ def checkServerTrusted(chain, authType)
242
+ is_ok = false
243
+ excn = nil
244
+ # TODO can we detect the depth from excn?
245
+ error_depth = -1
246
+ error = nil
247
+ error_message = nil
248
+ begin
249
+ @original.checkServerTrusted(chain, authType)
250
+ is_ok = true
251
+ rescue java.security.cert.CertificateException => excn
252
+ is_ok = false
253
+ error = excn.class.name
254
+ error_message = excn.getMessage
255
+ end
256
+ is_ok = @verify_callback.call(is_ok, chain, error_depth, error, error_message)
257
+ unless is_ok
258
+ excn ||= OpenSSL::SSL::SSLError.new('verifycallback failed')
259
+ raise excn
260
+ end
261
+ end
262
+
263
+ def checkClientTrusted(chain, authType); end
264
+ def getAcceptedIssuers; end
265
+ end
266
+
267
+ def initialize(verify_callback = nil)
268
+ @verify_callback = verify_callback
269
+ end
270
+
271
+ def init(trust_store)
272
+ tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
273
+ tmf.java_method(:init, [KeyStore]).call(trust_store)
274
+ @original = tmf.getTrustManagers.find { |tm|
275
+ tm.is_a?(X509TrustManager)
276
+ }
277
+ @managers = [SystemTrustManager.new(@original, @verify_callback)].to_java(X509TrustManager)
278
+ end
279
+
280
+ def getTrustManagers
281
+ @managers
282
+ end
283
+ end
284
+
285
+ module PEMUtils
286
+ def self.read_certificate(pem)
287
+ cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
288
+ der = cert.unpack('m*').first
289
+ cf = CertificateFactory.getInstance('X.509')
290
+ cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
291
+ end
292
+
293
+ def self.read_private_key(pem, password)
294
+ if password
295
+ password = password.unpack('C*').to_java(:char)
296
+ end
297
+ PEMInputOutput.read_private_key(InputStreamReader.new(ByteArrayInputStream.new(pem.to_java_bytes)), password)
298
+ end
299
+ end
300
+
301
+ class KeyStoreLoader
302
+ PASSWORD = 16.times.map { rand(256) }.to_java(:char)
303
+
304
+ def initialize
305
+ @keystore = KeyStore.getInstance('JKS')
306
+ @keystore.load(nil)
307
+ end
308
+
309
+ def add(cert_source, key_source, password)
310
+ cert_str = cert_source.respond_to?(:to_pem) ? cert_source.to_pem : File.read(cert_source.to_s)
311
+ cert = PEMUtils.read_certificate(cert_str)
312
+ @keystore.setCertificateEntry('client_cert', cert)
313
+ key_str = key_source.respond_to?(:to_pem) ? key_source.to_pem : File.read(key_source.to_s)
314
+ key_pair = PEMUtils.read_private_key(key_str, password)
315
+ @keystore.setKeyEntry('client_key', key_pair.getPrivate, PASSWORD, [cert].to_java(Certificate))
316
+ end
317
+
318
+ def keystore
319
+ @keystore
320
+ end
321
+ end
322
+
323
+ class TrustStoreLoader
324
+ attr_reader :size
325
+
326
+ def initialize
327
+ @trust_store = KeyStore.getInstance('JKS')
328
+ @trust_store.load(nil)
329
+ @size = 0
330
+ end
331
+
332
+ def add(cert_source)
333
+ return if cert_source == :default
334
+ if cert_source.respond_to?(:to_pem)
335
+ pem = cert_source.to_pem
336
+ load_pem(pem)
337
+ elsif File.directory?(cert_source)
338
+ warn("#{cert_source}: directory not yet supported")
339
+ return
340
+ else
341
+ pem = nil
342
+ File.read(cert_source).each_line do |line|
343
+ case line
344
+ when /-----BEGIN CERTIFICATE-----/
345
+ pem = ''
346
+ when /-----END CERTIFICATE-----/
347
+ load_pem(pem)
348
+ # keep parsing in case where multiple certificates in a file
349
+ else
350
+ if pem
351
+ pem << line
352
+ end
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ def trust_store
359
+ if @size == 0
360
+ nil
361
+ else
362
+ @trust_store
363
+ end
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
373
+ end
374
+
375
+ # Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
376
+ class BrowserCompatHostnameVerifier
377
+ BAD_COUNTRY_2LDS = %w(ac co com ed edu go gouv gov info lg ne net or org).sort
378
+ require 'ipaddr'
379
+
380
+ def extract_sans(cert, subject_type)
381
+ sans = cert.getSubjectAlternativeNames rescue nil
382
+ if sans.nil?
383
+ return nil
384
+ end
385
+ sans.find_all { |san|
386
+ san.first.to_i == subject_type
387
+ }.map { |san|
388
+ san[1]
389
+ }
390
+ end
391
+
392
+ def extract_cn(cert)
393
+ subject = cert.getSubjectX500Principal()
394
+ if subject
395
+ subject_dn = javax.naming.ldap.LdapName.new(subject.toString)
396
+ subject_dn.getRdns.to_a.reverse.each do |rdn|
397
+ attributes = rdn.toAttributes
398
+ cn = attributes.get('cn')
399
+ if cn
400
+ if value = cn.get
401
+ return value.to_s
402
+ end
403
+ end
404
+ end
405
+ end
406
+ end
407
+
408
+ def ipaddr?(addr)
409
+ !(IPAddr.new(addr) rescue nil).nil?
410
+ end
411
+
412
+ def verify(hostname, cert)
413
+ is_ipaddr = ipaddr?(hostname)
414
+ sans = extract_sans(cert, is_ipaddr ? 7 : 2)
415
+ cn = extract_cn(cert)
416
+ if sans
417
+ sans.each do |san|
418
+ return true if match_identify(hostname, san)
419
+ end
420
+ raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match any of the subject alternative names: #{sans}")
421
+ elsif cn
422
+ return true if match_identify(hostname, cn)
423
+ raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match common name of the certificate subject: #{cn}")
424
+ end
425
+ raise OpenSSL::SSL::SSLError.new("Certificate subject for for <#{hostname}> doesn't contain a common name and does not have alternative names")
426
+ end
427
+
428
+ def match_identify(hostname, identity)
429
+ if hostname.nil?
430
+ return false
431
+ end
432
+ hostname = hostname.downcase
433
+ identity = identity.downcase
434
+ parts = identity.split('.')
435
+ if parts.length >= 3 && parts.first.end_with?('*') && valid_country_wildcard(parts)
436
+ create_wildcard_regexp(identity) =~ hostname
437
+ else
438
+ hostname == identity
439
+ end
440
+ end
441
+
442
+ def create_wildcard_regexp(value)
443
+ # Escape first then search '\*' for meta-char interpolation
444
+ labels = value.split('.').map { |e| Regexp.escape(e) }
445
+ # Handle '*'s only at the left-most label, exclude A-label and U-label
446
+ labels[0].gsub!(/\\\*/, '[^.]+') if !labels[0].start_with?('xn\-\-') and labels[0].ascii_only?
447
+ /\A#{labels.join('\.')}\z/i
448
+ end
449
+
450
+ def valid_country_wildcard(parts)
451
+ if parts.length != 3 || parts[2].length != 2
452
+ true
453
+ else
454
+ !BAD_COUNTRY_2LDS.include?(parts[1])
455
+ end
456
+ end
457
+ end
458
+
459
+ def self.create_socket(session)
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
468
+ begin
469
+ if session.proxy
470
+ site = session.proxy || session.dest
471
+ socket = JavaSocketWrap.connect(Socket.new, site, opts)
472
+ session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
473
+ end
474
+ new(socket, session.dest, session.ssl_config, opts)
475
+ rescue
476
+ socket.close if socket
477
+ raise
478
+ end
479
+ end
480
+
481
+ DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
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)
502
+ if config.ssl_version == :auto
503
+ DEFAULT_SSL_PROTOCOL
504
+ else
505
+ config.ssl_version.to_s.tr('_', '.')
506
+ end
507
+ end
508
+
509
+ def create_ssl_context(config)
510
+ unless config.cert_store_crl_items.empty?
511
+ raise NotImplementedError.new('Manual CRL configuration is not yet supported')
512
+ end
513
+
514
+ km = nil
515
+ if config.client_cert && config.client_key
516
+ loader = KeyStoreLoader.new
517
+ loader.add(config.client_cert, config.client_key, config.client_key_pass)
518
+ kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
519
+ kmf.init(loader.keystore, KeyStoreLoader::PASSWORD)
520
+ km = kmf.getKeyManagers
521
+ end
522
+
523
+ trust_store = nil
524
+ verify_callback = config.verify_callback || config.method(:default_verify_callback)
525
+ if !config.verify?
526
+ tmf = VerifyNoneTrustManagerFactory.new(verify_callback)
527
+ else
528
+ tmf = SystemTrustManagerFactory.new(verify_callback)
529
+ loader = TrustStoreLoader.new
530
+ config.cert_store_items.each do |item|
531
+ loader.add(item)
532
+ end
533
+ trust_store = loader.trust_store
534
+ end
535
+ tmf.init(trust_store)
536
+ tm = tmf.getTrustManagers
537
+
538
+ ctx = SSLContext.getInstance(java_ssl_version(config))
539
+ ctx.init(km, tm, nil)
540
+ if config.timeout
541
+ ctx.getClientSessionContext.setSessionTimeout(config.timeout)
542
+ end
543
+ ctx
544
+ end
545
+
546
+ def create_ssl_socket(socket, dest, config, opts)
547
+ ctx = create_ssl_context(config)
548
+ factory = ctx.getSocketFactory
549
+ if socket
550
+ ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
551
+ else
552
+ ssl_socket = factory.createSocket
553
+ JavaSocketWrap.connect(ssl_socket, dest, opts)
554
+ end
555
+ ssl_socket
556
+ end
557
+
558
+ def peer_cert
559
+ @peer_cert
560
+ end
561
+
562
+ private
563
+
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
581
+ end
582
+ end
583
+
584
+ SSLSocket = JRubySSLSocket
585
+
586
+ end
587
+
588
+ end