httpclient 2.6.0.1 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -6,13 +6,13 @@ require 'http-cookie'
6
6
 
7
7
  class HTTPClient
8
8
  class CookieManager
9
- attr_reader :format
9
+ attr_reader :format, :jar
10
10
  attr_accessor :cookies_file
11
11
 
12
- def initialize(cookies_file = nil, format = WebAgentSaver)
12
+ def initialize(cookies_file = nil, format = WebAgentSaver, jar = HTTP::CookieJar.new)
13
13
  @cookies_file = cookies_file
14
14
  @format = format
15
- @jar = HTTP::CookieJar.new
15
+ @jar = jar
16
16
  load_cookies if @cookies_file
17
17
  end
18
18
 
@@ -174,6 +174,7 @@ class WebAgent
174
174
  CookieManager = ::HTTPClient::CookieManager
175
175
 
176
176
  class Cookie < HTTP::Cookie
177
+ @@domain_warned = false
177
178
  @@warned = false
178
179
 
179
180
  def url
@@ -194,7 +195,7 @@ class WebAgent
194
195
  alias original_domain domain
195
196
 
196
197
  def domain
197
- warn('Cookie#domain returns dot-less domain name now. Use Cookie#dot_domain if you need "." at the beginning.')
198
+ domain_warning
198
199
  self.original_domain
199
200
  end
200
201
 
@@ -205,6 +206,13 @@ class WebAgent
205
206
 
206
207
  private
207
208
 
209
+ def domain_warning
210
+ unless @@domain_warned
211
+ warn('Cookie#domain returns dot-less domain name now. Use Cookie#dot_domain if you need "." at the beginning.')
212
+ @@domain_warned = true
213
+ end
214
+ end
215
+
208
216
  def deprecated(old, new)
209
217
  unless @@warned
210
218
  warn("WebAgent::Cookie is deprecated and will be replaced with HTTP::Cookie in the near future. Please use Cookie##{new} instead of Cookie##{old} for the replacement.")
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  # HTTPClient - HTTP client library.
4
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
4
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
5
5
  #
6
6
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
7
7
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -0,0 +1,526 @@
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.io.BufferedInputStream'
19
+ BUF_SIZE = 1024 * 16
20
+
21
+ def initialize(socket, debug_dev = nil)
22
+ @socket = socket
23
+ @debug_dev = debug_dev
24
+ @outstr = @socket.getOutputStream
25
+ @instr = BufferedInputStream.new(@socket.getInputStream)
26
+ @buf = (' ' * BUF_SIZE).to_java_bytes
27
+ @bufstr = ''
28
+ end
29
+
30
+ def close
31
+ @socket.close
32
+ end
33
+
34
+ def closed?
35
+ @socket.isClosed
36
+ end
37
+
38
+ def eof?
39
+ @socket.isClosed
40
+ end
41
+
42
+
43
+ def gets(rs)
44
+ while (size = @bufstr.index(rs)).nil?
45
+ if fill() == -1
46
+ size = @bufstr.size
47
+ break
48
+ end
49
+ end
50
+ str = @bufstr.slice!(0, size + rs.size)
51
+ debug(str)
52
+ str
53
+ end
54
+
55
+ def read(size, buf = nil)
56
+ while @bufstr.size < size
57
+ if fill() == -1
58
+ break
59
+ end
60
+ end
61
+ str = @bufstr.slice!(0, size)
62
+ debug(str)
63
+ if buf
64
+ buf.replace(str)
65
+ else
66
+ str
67
+ end
68
+ end
69
+
70
+ def readpartial(size, buf = nil)
71
+ while @bufstr.size == 0
72
+ if fill() == -1
73
+ raise EOFError.new('end of file reached')
74
+ end
75
+ end
76
+ str = @bufstr.slice!(0, size)
77
+ debug(str)
78
+ if buf
79
+ buf.replace(str)
80
+ else
81
+ str
82
+ end
83
+ end
84
+
85
+ def <<(str)
86
+ rv = @outstr.write(str.to_java_bytes)
87
+ debug(str)
88
+ rv
89
+ end
90
+
91
+ def flush
92
+ @socket.flush
93
+ end
94
+
95
+ def sync
96
+ true
97
+ end
98
+
99
+ def sync=(sync)
100
+ unless sync
101
+ raise "sync = false is not supported. This option was introduced for backward compatibility just in case."
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def fill
108
+ size = @instr.read(@buf)
109
+ if size > 0
110
+ @bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
111
+ end
112
+ size
113
+ end
114
+
115
+ def debug(str)
116
+ @debug_dev << str if @debug_dev && str
117
+ end
118
+ end
119
+
120
+ class JRubySSLSocket < JavaSocketWrap
121
+ java_import 'java.io.ByteArrayInputStream'
122
+ java_import 'java.io.InputStreamReader'
123
+ java_import 'java.net.Socket'
124
+ java_import 'java.security.KeyStore'
125
+ java_import 'java.security.cert.Certificate'
126
+ java_import 'java.security.cert.CertificateFactory'
127
+ java_import 'javax.net.ssl.KeyManagerFactory'
128
+ java_import 'javax.net.ssl.SSLContext'
129
+ java_import 'javax.net.ssl.SSLSocketFactory'
130
+ java_import 'javax.net.ssl.TrustManager'
131
+ java_import 'javax.net.ssl.TrustManagerFactory'
132
+ java_import 'javax.net.ssl.X509TrustManager'
133
+ java_import 'org.jruby.ext.openssl.x509store.PEMInputOutput'
134
+
135
+ class JavaCertificate
136
+ attr_reader :cert
137
+
138
+ def initialize(cert)
139
+ @cert = cert
140
+ end
141
+
142
+ def subject
143
+ @cert.getSubjectDN
144
+ end
145
+
146
+ def to_text
147
+ @cert.toString
148
+ end
149
+
150
+ def to_pem
151
+ '(not in PEM format)'
152
+ end
153
+ end
154
+
155
+ class SSLStoreContext
156
+ attr_reader :current_cert, :chain, :error_depth, :error, :error_string
157
+
158
+ def initialize(current_cert, chain, error_depth, error, error_string)
159
+ @current_cert, @chain, @error_depth, @error, @error_string =
160
+ current_cert, chain, error_depth, error, error_string
161
+ end
162
+ end
163
+
164
+ class JSSEVerifyCallback
165
+ def initialize(verify_callback)
166
+ @verify_callback = verify_callback
167
+ end
168
+
169
+ def call(is_ok, chain, error_depth = -1, error = -1, error_string = '(unknown)')
170
+ if @verify_callback
171
+ ruby_chain = chain.map { |cert|
172
+ JavaCertificate.new(cert)
173
+ }.reverse
174
+ # NOTE: The order depends on provider implementation
175
+ ruby_chain.each do |cert|
176
+ is_ok = @verify_callback.call(
177
+ is_ok,
178
+ SSLStoreContext.new(cert, ruby_chain, error_depth, error, error_string)
179
+ )
180
+ end
181
+ end
182
+ is_ok
183
+ end
184
+ end
185
+
186
+ class VerifyNoneTrustManagerFactory
187
+ class VerifyNoneTrustManager
188
+ include X509TrustManager
189
+
190
+ def initialize(verify_callback)
191
+ @verify_callback = JSSEVerifyCallback.new(verify_callback)
192
+ end
193
+
194
+ def checkServerTrusted(chain, authType)
195
+ @verify_callback.call(true, chain)
196
+ end
197
+
198
+ def checkClientTrusted(chain, authType); end
199
+ def getAcceptedIssuers; end
200
+ end
201
+
202
+ def initialize(verify_callback = nil)
203
+ @verify_callback = verify_callback
204
+ end
205
+
206
+ def init(trustStore)
207
+ @managers = [VerifyNoneTrustManager.new(@verify_callback)].to_java(X509TrustManager)
208
+ end
209
+
210
+ def getTrustManagers
211
+ @managers
212
+ end
213
+ end
214
+
215
+ class SystemTrustManagerFactory
216
+ class SystemTrustManager
217
+ include X509TrustManager
218
+
219
+ def initialize(original, verify_callback)
220
+ @original = original
221
+ @verify_callback = JSSEVerifyCallback.new(verify_callback)
222
+ end
223
+
224
+ def checkServerTrusted(chain, authType)
225
+ is_ok = false
226
+ excn = nil
227
+ # TODO can we detect the depth from excn?
228
+ error_depth = -1
229
+ error = nil
230
+ error_message = nil
231
+ begin
232
+ @original.checkServerTrusted(chain, authType)
233
+ is_ok = true
234
+ rescue java.security.cert.CertificateException => excn
235
+ is_ok = false
236
+ error = excn.class.name
237
+ error_message = excn.getMessage
238
+ end
239
+ is_ok = @verify_callback.call(is_ok, chain, error_depth, error, error_message)
240
+ unless is_ok
241
+ excn ||= OpenSSL::SSL::SSLError.new('verifycallback failed')
242
+ raise excn
243
+ end
244
+ end
245
+
246
+ def checkClientTrusted(chain, authType); end
247
+ def getAcceptedIssuers; end
248
+ end
249
+
250
+ def initialize(verify_callback = nil)
251
+ @verify_callback = verify_callback
252
+ end
253
+
254
+ def init(trust_store)
255
+ tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
256
+ tmf.java_method(:init, [KeyStore]).call(trust_store)
257
+ @original = tmf.getTrustManagers.find { |tm|
258
+ tm.is_a?(X509TrustManager)
259
+ }
260
+ @managers = [SystemTrustManager.new(@original, @verify_callback)].to_java(X509TrustManager)
261
+ end
262
+
263
+ def getTrustManagers
264
+ @managers
265
+ end
266
+ end
267
+
268
+ module PEMUtils
269
+ def self.read_certificate(pem)
270
+ pem = pem.sub(/-----BEGIN CERTIFICATE-----/, '').sub(/-----END CERTIFICATE-----/, '')
271
+ der = pem.unpack('m*').first
272
+ cf = CertificateFactory.getInstance('X.509')
273
+ cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
274
+ end
275
+
276
+ def self.read_private_key(pem, password)
277
+ if password
278
+ password = password.unpack('C*').to_java(:char)
279
+ end
280
+ PEMInputOutput.read_private_key(InputStreamReader.new(ByteArrayInputStream.new(pem.to_java_bytes)), password)
281
+ end
282
+ end
283
+
284
+ class KeyStoreLoader
285
+ PASSWORD = 16.times.map { rand(256) }.to_java(:char)
286
+
287
+ def initialize
288
+ @keystore = KeyStore.getInstance('JKS')
289
+ @keystore.load(nil)
290
+ end
291
+
292
+ def add(cert_file, key_file, password)
293
+ cert_str = cert_file.respond_to?(:to_pem) ? cert_file.to_pem : File.read(cert_file.to_s)
294
+ cert = PEMUtils.read_certificate(cert_str)
295
+ @keystore.setCertificateEntry('client_cert', cert)
296
+ key_str = key_file.respond_to?(:to_pem) ? key_file.to_pem : File.read(key_file.to_s)
297
+ key_pair = PEMUtils.read_private_key(key_str, password)
298
+ @keystore.setKeyEntry('client_key', key_pair.getPrivate, PASSWORD, [cert].to_java(Certificate))
299
+ end
300
+
301
+ def keystore
302
+ @keystore
303
+ end
304
+ end
305
+
306
+ class TrustStoreLoader
307
+ attr_reader :size
308
+
309
+ def initialize
310
+ @trust_store = KeyStore.getInstance('JKS')
311
+ @trust_store.load(nil)
312
+ @size = 0
313
+ end
314
+
315
+ def add(file_or_dir)
316
+ return if file_or_dir == :default
317
+ if File.directory?(file_or_dir)
318
+ warn('directory not yet supported')
319
+ else
320
+ pem = nil
321
+ File.read(file_or_dir).each_line do |line|
322
+ case line
323
+ when /-----BEGIN CERTIFICATE-----/
324
+ pem = ''
325
+ when /-----END CERTIFICATE-----/
326
+ cert = PEMUtils.read_certificate(pem)
327
+ @size += 1
328
+ @trust_store.setCertificateEntry("cert_#{@size}", cert)
329
+ else
330
+ if pem
331
+ pem << line
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ def trust_store
339
+ if @size == 0
340
+ nil
341
+ else
342
+ @trust_store
343
+ end
344
+ end
345
+ end
346
+
347
+ # Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
348
+ class BrowserCompatHostnameVerifier
349
+ BAD_COUNTRY_2LDS = %w(ac co com ed edu go gouv gov info lg ne net or org).sort
350
+ require 'ipaddr'
351
+
352
+ def extract_sans(cert, subject_type)
353
+ sans = cert.getSubjectAlternativeNames rescue nil
354
+ if sans.nil?
355
+ return nil
356
+ end
357
+ sans.find_all { |san|
358
+ san.first.to_i == subject_type
359
+ }.map { |san|
360
+ san[1]
361
+ }
362
+ end
363
+
364
+ def extract_cn(cert)
365
+ subject = cert.getSubjectX500Principal()
366
+ if subject
367
+ subject_dn = javax.naming.ldap.LdapName.new(subject.toString)
368
+ subject_dn.getRdns.to_a.reverse.each do |rdn|
369
+ attributes = rdn.toAttributes
370
+ cn = attributes.get('cn')
371
+ if cn
372
+ if value = cn.get
373
+ return value.to_s
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end
379
+
380
+ def ipaddr?(addr)
381
+ !(IPAddr.new(addr) rescue nil).nil?
382
+ end
383
+
384
+ def verify(hostname, cert)
385
+ is_ipaddr = ipaddr?(hostname)
386
+ sans = extract_sans(cert, is_ipaddr ? 7 : 2)
387
+ cn = extract_cn(cert)
388
+ if sans
389
+ sans.each do |san|
390
+ return true if match_identify(hostname, san)
391
+ end
392
+ raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match any of the subject alternative names: #{sans}")
393
+ elsif cn
394
+ return true if match_identify(hostname, cn)
395
+ raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match common name of the certificate subject: #{cn}")
396
+ end
397
+ raise OpenSSL::SSL::SSLError.new("Certificate subject for for <#{hostname}> doesn't contain a common name and does not have alternative names")
398
+ end
399
+
400
+ def match_identify(hostname, identity)
401
+ if hostname.nil?
402
+ return false
403
+ end
404
+ hostname = hostname.downcase
405
+ identity = identity.downcase
406
+ parts = identity.split('.')
407
+ if parts.length >= 3 && parts.first.end_with?('*') && valid_country_wildcard(parts)
408
+ create_wildcard_regexp(identity) =~ hostname
409
+ else
410
+ hostname == identity
411
+ end
412
+ end
413
+
414
+ def create_wildcard_regexp(value)
415
+ # Escape first then search '\*' for meta-char interpolation
416
+ labels = value.split('.').map { |e| Regexp.escape(e) }
417
+ # Handle '*'s only at the left-most label, exclude A-label and U-label
418
+ labels[0].gsub!(/\\\*/, '[^.]+') if !labels[0].start_with?('xn\-\-') and labels[0].ascii_only?
419
+ /\A#{labels.join('\.')}\z/i
420
+ end
421
+
422
+ def valid_country_wildcard(parts)
423
+ if parts.length != 3 || parts[2].length != 2
424
+ true
425
+ else
426
+ !BAD_COUNTRY_2LDS.include?(parts[1])
427
+ end
428
+ end
429
+ end
430
+
431
+ def self.create_socket(session)
432
+ site = session.proxy || session.dest
433
+ socket = Socket.new(site.host, site.port)
434
+ begin
435
+ if session.proxy
436
+ session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
437
+ end
438
+ rescue
439
+ socket.close
440
+ raise
441
+ end
442
+ new(socket, session.dest, session.ssl_config, session.debug_dev)
443
+ end
444
+
445
+ def initialize(socket, dest, config, debug_dev = nil)
446
+ if config.ssl_version == :auto
447
+ ssl_version = 'TLSv1'
448
+ else
449
+ ssl_version = config.to_s.gsub(/_/, '.')
450
+ end
451
+ unless config.cert_store_crl_items.empty?
452
+ raise NotImplementedError.new('Manual CRL configuration is not yet supported')
453
+ end
454
+
455
+ km = nil
456
+ if config.client_cert && config.client_key
457
+ loader = KeyStoreLoader.new
458
+ loader.add(config.client_cert, config.client_key, config.client_key_pass)
459
+ kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
460
+ kmf.init(loader.keystore, KeyStoreLoader::PASSWORD)
461
+ km = kmf.getKeyManagers
462
+ end
463
+
464
+ trust_store = nil
465
+ verify_callback = config.verify_callback || config.method(:default_verify_callback)
466
+ if config.verify_mode == nil
467
+ tmf = VerifyNoneTrustManagerFactory.new(verify_callback)
468
+ else
469
+ tmf = SystemTrustManagerFactory.new(verify_callback)
470
+ loader = TrustStoreLoader.new
471
+ config.cert_store_items.each do |item|
472
+ loader.add(item)
473
+ end
474
+ trust_store = loader.trust_store
475
+ end
476
+ tmf.init(trust_store)
477
+ tm = tmf.getTrustManagers
478
+
479
+ ctx = SSLContext.getInstance(ssl_version)
480
+ ctx.init(km, tm, nil)
481
+ if config.timeout
482
+ ctx.getClientSessionContext.setSessionTimeout(config.timeout)
483
+ end
484
+
485
+ factory = ctx.getSocketFactory
486
+ begin
487
+ ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
488
+ ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String))
489
+ if config.ciphers != SSLConfig::CIPHERS_DEFAULT
490
+ ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
491
+ end
492
+ ssl_socket.startHandshake
493
+ @peer_cert = JavaCertificate.new(ssl_socket.getSession.getPeerCertificates.first)
494
+ @ciphersuite = ssl_socket.getSession.getCipherSuite
495
+ post_connection_check(dest.host, @peer_cert)
496
+ rescue java.security.GeneralSecurityException => e
497
+ raise OpenSSL::SSL::SSLError.new(e.getMessage)
498
+ rescue javax.net.ssl.SSLException => e
499
+ raise OpenSSL::SSL::SSLError.new(e.getMessage)
500
+ rescue java.net.SocketException => e
501
+ raise OpenSSL::SSL::SSLError.new(e.getMessage)
502
+ end
503
+
504
+ super(ssl_socket, debug_dev)
505
+ end
506
+
507
+ def peer_cert
508
+ @peer_cert
509
+ end
510
+
511
+ def ciphersuite
512
+ @ciphersuite
513
+ end
514
+
515
+ private
516
+
517
+ def post_connection_check(hostname, wrap_cert)
518
+ BrowserCompatHostnameVerifier.new.verify(hostname, wrap_cert.cert)
519
+ end
520
+ end
521
+
522
+ SSLSocket = JRubySSLSocket
523
+
524
+ end
525
+
526
+ end