httpclient 2.8.2.4 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/hexdump.rb +12 -12
- data/lib/httpclient/http.rb +8 -8
- data/lib/httpclient/jruby_ssl_socket.rb +90 -39
- data/lib/httpclient/session.rb +13 -7
- data/lib/httpclient/ssl_config.rb +75 -123
- data/lib/httpclient/ssl_socket.rb +26 -24
- data/lib/httpclient/util.rb +1 -1
- data/lib/httpclient/version.rb +1 -1
- data/lib/httpclient.rb +7 -1
- data/lib/jsonclient.rb +8 -5
- data/sample/auth.rb +1 -1
- data/sample/generate_test_keys.rb +99 -0
- data/test/ca-chain.pem +32 -36
- data/test/ca.cert +16 -19
- data/test/ca.key +27 -0
- data/test/ca.srl +1 -0
- data/test/client-pass.key +30 -18
- data/test/client.cert +17 -16
- data/test/client.key +25 -13
- data/test/fixtures/verify.alt.cert +20 -0
- data/test/fixtures/verify.foo.cert +20 -0
- data/test/fixtures/verify.key +27 -0
- data/test/fixtures/verify.localhost.cert +20 -0
- data/test/helper.rb +5 -7
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/server.cert +16 -15
- data/test/server.key +25 -13
- data/test/subca.cert +16 -17
- data/test/subca.key +27 -0
- data/test/subca.srl +1 -0
- data/test/test_auth.rb +21 -17
- data/test/test_hexdump.rb +1 -2
- data/test/test_http-access2.rb +31 -23
- data/test/test_httpclient.rb +69 -58
- data/test/test_jsonclient.rb +18 -0
- data/test/test_ssl.rb +99 -90
- metadata +32 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e1df0b78f53d502000683d048be1316f156247e5e17995f5fb04a125302bdb4e
|
4
|
+
data.tar.gz: d533eeaf2333f2f35820441a74cdf84075e5227d4fb0648f4e2ea4d2099bd511
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94024e0c5b5bd08800d9b3989151ab919869fabcaed65e4a375490fde0f6ea3b5bfc3ad4a5a9803bb057c5b8d36efc10c6ae5ade8a974c665d443730c63bc7ba
|
7
|
+
data.tar.gz: 768ae9ad96a73740675aca861a81075af3164a4b8875f81368a3228ecfdf523fe6c13cd9355bbe8ddd9155031ff1f908d8ec85e1bd112e0cd31d8b26d809dd6a
|
data/lib/hexdump.rb
CHANGED
@@ -11,13 +11,13 @@ module HexDump
|
|
11
11
|
result = []
|
12
12
|
while raw = str.slice(offset, 16) and raw.length > 0
|
13
13
|
# data field
|
14
|
-
data = ''
|
14
|
+
data = ''.dup
|
15
15
|
for v in raw.unpack('N* a*')
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
if v.kind_of? Integer
|
17
|
+
data << sprintf("%08x ", v)
|
18
|
+
else
|
19
|
+
v.each_byte {|c| data << sprintf("%02x", c) }
|
20
|
+
end
|
21
21
|
end
|
22
22
|
# text field
|
23
23
|
text = raw.tr("\000-\037\177-\377", ".")
|
@@ -25,12 +25,12 @@ module HexDump
|
|
25
25
|
offset += 16
|
26
26
|
# omit duplicate line
|
27
27
|
if /^(#{regex_quote_n(raw)})+/n =~ str[offset .. -1]
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
result << sprintf("%08x ...", offset)
|
29
|
+
offset += $&.length
|
30
|
+
# should print at the end
|
31
|
+
if offset == str.length
|
32
|
+
result << sprintf("%08x %-36s %s", offset-16, data, text)
|
33
|
+
end
|
34
34
|
end
|
35
35
|
end
|
36
36
|
result
|
data/lib/httpclient/http.rb
CHANGED
@@ -238,7 +238,7 @@ module HTTP
|
|
238
238
|
if defined?(Encoding::ASCII_8BIT)
|
239
239
|
def set_body_encoding
|
240
240
|
if type = self.content_type
|
241
|
-
OpenURI::Meta.init(o = '')
|
241
|
+
OpenURI::Meta.init(o = ''.dup)
|
242
242
|
o.meta_add_field('content-type', type)
|
243
243
|
@body_encoding = o.encoding
|
244
244
|
end
|
@@ -491,7 +491,7 @@ module HTTP
|
|
491
491
|
# String.
|
492
492
|
#
|
493
493
|
# assert: @size is not nil
|
494
|
-
def dump(header = '', dev = '')
|
494
|
+
def dump(header = '', dev = ''.dup)
|
495
495
|
if @body.is_a?(Parts)
|
496
496
|
dev << header
|
497
497
|
@body.parts.each do |part|
|
@@ -521,7 +521,7 @@ module HTTP
|
|
521
521
|
# reason. (header is dumped to dev, too)
|
522
522
|
# If no dev (the second argument) given, this method returns a dumped
|
523
523
|
# String.
|
524
|
-
def dump_chunked(header = '', dev = '')
|
524
|
+
def dump_chunked(header = '', dev = ''.dup)
|
525
525
|
dev << header
|
526
526
|
if @body.is_a?(Parts)
|
527
527
|
@body.parts.each do |part|
|
@@ -574,7 +574,7 @@ module HTTP
|
|
574
574
|
end
|
575
575
|
|
576
576
|
def dump_file(io, dev, sz)
|
577
|
-
buf = ''
|
577
|
+
buf = ''.dup
|
578
578
|
rest = sz
|
579
579
|
while rest > 0
|
580
580
|
n = io.read([rest, @chunk_size].min, buf)
|
@@ -585,7 +585,7 @@ module HTTP
|
|
585
585
|
end
|
586
586
|
|
587
587
|
def dump_chunks(io, dev)
|
588
|
-
buf = ''
|
588
|
+
buf = ''.dup
|
589
589
|
while !io.read(@chunk_size, buf).nil?
|
590
590
|
dev << dump_chunk(buf)
|
591
591
|
end
|
@@ -618,8 +618,8 @@ module HTTP
|
|
618
618
|
if Message.file?(part)
|
619
619
|
@as_stream = true
|
620
620
|
@body << part
|
621
|
-
if part.respond_to?(:
|
622
|
-
sz = part.
|
621
|
+
if part.respond_to?(:stat)
|
622
|
+
sz = part.stat.size
|
623
623
|
add_size(part, sz)
|
624
624
|
elsif part.respond_to?(:size)
|
625
625
|
if sz = part.size
|
@@ -954,7 +954,7 @@ module HTTP
|
|
954
954
|
|
955
955
|
# Dumps message (header and body) to given dev.
|
956
956
|
# dev needs to respond to <<.
|
957
|
-
def dump(dev = '')
|
957
|
+
def dump(dev = ''.dup)
|
958
958
|
str = @http_header.dump + CRLF
|
959
959
|
if @http_header.chunked
|
960
960
|
dev = @http_body.dump_chunked(str, dev)
|
@@ -15,9 +15,27 @@ 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.normalize_timeout(timeout)
|
24
|
+
[Java::JavaLang::Integer::MAX_VALUE, timeout].min
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.connect(socket, site, opts = {})
|
28
|
+
socket_addr = InetSocketAddress.new(site.host, site.port)
|
29
|
+
if opts[:connect_timeout]
|
30
|
+
socket.connect(socket_addr, normalize_timeout(opts[:connect_timeout]))
|
31
|
+
else
|
32
|
+
socket.connect(socket_addr)
|
33
|
+
end
|
34
|
+
socket.setSoTimeout(normalize_timeout(opts[:so_timeout])) if opts[:so_timeout]
|
35
|
+
socket.setKeepAlive(true) if opts[:tcp_keepalive]
|
36
|
+
socket
|
37
|
+
end
|
38
|
+
|
21
39
|
def initialize(socket, debug_dev = nil)
|
22
40
|
@socket = socket
|
23
41
|
@debug_dev = debug_dev
|
@@ -39,7 +57,6 @@ unless defined?(SSLSocket)
|
|
39
57
|
@socket.isClosed
|
40
58
|
end
|
41
59
|
|
42
|
-
|
43
60
|
def gets(rs)
|
44
61
|
while (size = @bufstr.index(rs)).nil?
|
45
62
|
if fill() == -1
|
@@ -105,11 +122,15 @@ unless defined?(SSLSocket)
|
|
105
122
|
private
|
106
123
|
|
107
124
|
def fill
|
108
|
-
|
109
|
-
|
110
|
-
|
125
|
+
begin
|
126
|
+
size = @instr.read(@buf)
|
127
|
+
if size > 0
|
128
|
+
@bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
|
129
|
+
end
|
130
|
+
size
|
131
|
+
rescue java.io.IOException => e
|
132
|
+
raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
|
111
133
|
end
|
112
|
-
size
|
113
134
|
end
|
114
135
|
|
115
136
|
def debug(str)
|
@@ -267,8 +288,8 @@ unless defined?(SSLSocket)
|
|
267
288
|
|
268
289
|
module PEMUtils
|
269
290
|
def self.read_certificate(pem)
|
270
|
-
|
271
|
-
der =
|
291
|
+
cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
|
292
|
+
der = cert.unpack('m*').first
|
272
293
|
cf = CertificateFactory.getInstance('X.509')
|
273
294
|
cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
|
274
295
|
end
|
@@ -440,27 +461,58 @@ unless defined?(SSLSocket)
|
|
440
461
|
end
|
441
462
|
|
442
463
|
def self.create_socket(session)
|
443
|
-
|
444
|
-
|
464
|
+
opts = {
|
465
|
+
:connect_timeout => session.connect_timeout * 1000,
|
466
|
+
# send_timeout is ignored in JRuby
|
467
|
+
:so_timeout => session.receive_timeout * 1000,
|
468
|
+
:tcp_keepalive => session.tcp_keepalive,
|
469
|
+
:debug_dev => session.debug_dev
|
470
|
+
}
|
471
|
+
socket = nil
|
445
472
|
begin
|
446
473
|
if session.proxy
|
474
|
+
site = session.proxy || session.dest
|
475
|
+
socket = JavaSocketWrap.connect(Socket.new, site, opts)
|
447
476
|
session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
|
448
477
|
end
|
478
|
+
new(socket, session.dest, session.ssl_config, opts)
|
449
479
|
rescue
|
450
|
-
socket.close
|
480
|
+
socket.close if socket
|
451
481
|
raise
|
452
482
|
end
|
453
|
-
new(socket, session.dest, session.ssl_config, session.debug_dev)
|
454
483
|
end
|
455
484
|
|
456
485
|
DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
|
457
|
-
def initialize(socket, dest, config,
|
486
|
+
def initialize(socket, dest, config, opts = {})
|
458
487
|
@config = config
|
488
|
+
begin
|
489
|
+
@ssl_socket = create_ssl_socket(socket, dest, config, opts)
|
490
|
+
ssl_version = java_ssl_version(config)
|
491
|
+
@ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
|
492
|
+
if config.ciphers != SSLConfig::CIPHERS_DEFAULT
|
493
|
+
@ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
|
494
|
+
end
|
495
|
+
ssl_connect(dest.host)
|
496
|
+
rescue java.security.GeneralSecurityException => e
|
497
|
+
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
498
|
+
rescue java.net.SocketTimeoutException => e
|
499
|
+
raise HTTPClient::ConnectTimeoutError.new(e.getMessage)
|
500
|
+
rescue java.io.IOException => e
|
501
|
+
raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
|
502
|
+
end
|
503
|
+
|
504
|
+
super(@ssl_socket, opts[:debug_dev])
|
505
|
+
end
|
506
|
+
|
507
|
+
def java_ssl_version(config)
|
459
508
|
if config.ssl_version == :auto
|
460
|
-
|
509
|
+
DEFAULT_SSL_PROTOCOL
|
461
510
|
else
|
462
|
-
|
511
|
+
config.ssl_version.to_s.tr('_', '.')
|
463
512
|
end
|
513
|
+
end
|
514
|
+
|
515
|
+
def create_ssl_context(config)
|
464
516
|
unless config.cert_store_crl_items.empty?
|
465
517
|
raise NotImplementedError.new('Manual CRL configuration is not yet supported')
|
466
518
|
end
|
@@ -489,36 +541,24 @@ unless defined?(SSLSocket)
|
|
489
541
|
tmf.init(trust_store)
|
490
542
|
tm = tmf.getTrustManagers
|
491
543
|
|
492
|
-
ctx = SSLContext.getInstance(
|
544
|
+
ctx = SSLContext.getInstance(java_ssl_version(config))
|
493
545
|
ctx.init(km, tm, nil)
|
494
546
|
if config.timeout
|
495
547
|
ctx.getClientSessionContext.setSessionTimeout(config.timeout)
|
496
548
|
end
|
549
|
+
ctx
|
550
|
+
end
|
497
551
|
|
552
|
+
def create_ssl_socket(socket, dest, config, opts)
|
553
|
+
ctx = create_ssl_context(config)
|
498
554
|
factory = ctx.getSocketFactory
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
end
|
505
|
-
ssl_socket.startHandshake
|
506
|
-
ssl_session = ssl_socket.getSession
|
507
|
-
@peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
|
508
|
-
if $DEBUG
|
509
|
-
warn("Protocol version: #{ssl_session.getProtocol}")
|
510
|
-
warn("Cipher: #{ssl_socket.getSession.getCipherSuite}")
|
511
|
-
end
|
512
|
-
post_connection_check(dest.host, @peer_cert)
|
513
|
-
rescue java.security.GeneralSecurityException => e
|
514
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
515
|
-
rescue javax.net.ssl.SSLException => e
|
516
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
517
|
-
rescue java.net.SocketException => e
|
518
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
555
|
+
unless socket
|
556
|
+
# Create a plain socket first to set connection timeouts on,
|
557
|
+
# then wrap it in a SSL socket so that SNI gets setup on it.
|
558
|
+
socket = javax.net.SocketFactory.getDefault.createSocket
|
559
|
+
JavaSocketWrap.connect(socket, dest, opts)
|
519
560
|
end
|
520
|
-
|
521
|
-
super(ssl_socket, debug_dev)
|
561
|
+
factory.createSocket(socket, dest.host, dest.port, true)
|
522
562
|
end
|
523
563
|
|
524
564
|
def peer_cert
|
@@ -527,11 +567,22 @@ unless defined?(SSLSocket)
|
|
527
567
|
|
528
568
|
private
|
529
569
|
|
530
|
-
def
|
570
|
+
def ssl_connect(hostname)
|
571
|
+
@ssl_socket.startHandshake
|
572
|
+
ssl_session = @ssl_socket.getSession
|
573
|
+
@peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
|
574
|
+
if $DEBUG
|
575
|
+
warn("Protocol version: #{ssl_session.getProtocol}")
|
576
|
+
warn("Cipher: #{@ssl_socket.getSession.getCipherSuite}")
|
577
|
+
end
|
578
|
+
post_connection_check(hostname)
|
579
|
+
end
|
580
|
+
|
581
|
+
def post_connection_check(hostname)
|
531
582
|
if !@config.verify?
|
532
583
|
return
|
533
584
|
else
|
534
|
-
BrowserCompatHostnameVerifier.new.verify(hostname,
|
585
|
+
BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
|
535
586
|
end
|
536
587
|
end
|
537
588
|
end
|
data/lib/httpclient/session.rb
CHANGED
@@ -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
|
24
|
+
if defined? JRUBY_VERSION
|
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
|
616
|
-
raise
|
623
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
617
624
|
rescue SocketError => e
|
618
|
-
e.message
|
619
|
-
raise
|
625
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
620
626
|
end
|
621
627
|
socket
|
622
628
|
end
|
@@ -944,7 +950,7 @@ class HTTPClient
|
|
944
950
|
end
|
945
951
|
|
946
952
|
def empty_bin_str
|
947
|
-
str = ''
|
953
|
+
str = ''.dup
|
948
954
|
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
|
949
955
|
str
|
950
956
|
end
|