httpclient 2.8.2.4 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|