httpclient 2.6.0.1 → 2.7.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 +4 -4
- data/README.md +1 -1
- data/bin/httpclient +5 -1
- data/lib/http-access2.rb +1 -1
- data/lib/httpclient.rb +37 -5
- data/lib/httpclient/auth.rb +3 -3
- data/lib/httpclient/cacert.pem +3952 -0
- data/lib/httpclient/{cacert.p7s → cacert1024.pem} +0 -0
- data/lib/httpclient/connection.rb +1 -1
- data/lib/httpclient/cookie.rb +12 -4
- data/lib/httpclient/http.rb +1 -1
- data/lib/httpclient/jruby_ssl_socket.rb +526 -0
- data/lib/httpclient/session.rb +161 -244
- data/lib/httpclient/ssl_config.rb +54 -17
- data/lib/httpclient/ssl_socket.rb +149 -0
- data/lib/httpclient/timeout.rb +1 -1
- data/lib/httpclient/util.rb +23 -1
- data/lib/httpclient/version.rb +1 -1
- data/lib/oauthclient.rb +1 -1
- data/test/{ca-chain.cert → ca-chain.pem} +0 -0
- data/test/helper.rb +7 -5
- data/test/test_auth.rb +27 -8
- data/test/test_cookie.rb +2 -2
- data/test/test_httpclient.rb +57 -21
- data/test/test_ssl.rb +70 -21
- data/test/test_webagent-cookie.rb +2 -2
- metadata +13 -10
data/lib/httpclient/session.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# HTTPClient - HTTP client library.
|
2
|
-
# Copyright (C) 2000-
|
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;
|
@@ -20,6 +20,11 @@ require 'zlib'
|
|
20
20
|
require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
|
21
21
|
require 'httpclient/ssl_config'
|
22
22
|
require 'httpclient/http'
|
23
|
+
if defined?(JRuby)
|
24
|
+
require 'httpclient/jruby_ssl_socket'
|
25
|
+
else
|
26
|
+
require 'httpclient/ssl_socket'
|
27
|
+
end
|
23
28
|
|
24
29
|
|
25
30
|
class HTTPClient
|
@@ -288,131 +293,6 @@ class HTTPClient
|
|
288
293
|
end
|
289
294
|
|
290
295
|
|
291
|
-
# Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
|
292
|
-
class SSLSocketWrap
|
293
|
-
def initialize(socket, context, debug_dev = nil)
|
294
|
-
unless SSLEnabled
|
295
|
-
raise ConfigurationError.new('Ruby/OpenSSL module is required')
|
296
|
-
end
|
297
|
-
@context = context
|
298
|
-
@socket = socket
|
299
|
-
@ssl_socket = create_openssl_socket(@socket)
|
300
|
-
@debug_dev = debug_dev
|
301
|
-
end
|
302
|
-
|
303
|
-
def ssl_connect(hostname = nil)
|
304
|
-
if hostname && @ssl_socket.respond_to?(:hostname=)
|
305
|
-
@ssl_socket.hostname = hostname
|
306
|
-
end
|
307
|
-
@ssl_socket.connect
|
308
|
-
end
|
309
|
-
|
310
|
-
def post_connection_check(host)
|
311
|
-
verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
|
312
|
-
if verify_mode == OpenSSL::SSL::VERIFY_NONE
|
313
|
-
return
|
314
|
-
elsif @ssl_socket.peer_cert.nil? and
|
315
|
-
check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
|
316
|
-
raise OpenSSL::SSL::SSLError.new('no peer cert')
|
317
|
-
end
|
318
|
-
hostname = host.host
|
319
|
-
if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
|
320
|
-
@ssl_socket.post_connection_check(hostname)
|
321
|
-
else
|
322
|
-
@context.post_connection_check(@ssl_socket.peer_cert, hostname)
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
def ssl_version
|
327
|
-
@ssl_socket.ssl_version if @ssl_socket.respond_to?(:ssl_version)
|
328
|
-
end
|
329
|
-
|
330
|
-
def ssl_cipher
|
331
|
-
@ssl_socket.cipher
|
332
|
-
end
|
333
|
-
|
334
|
-
def ssl_state
|
335
|
-
@ssl_socket.state
|
336
|
-
end
|
337
|
-
|
338
|
-
def peer_cert
|
339
|
-
@ssl_socket.peer_cert
|
340
|
-
end
|
341
|
-
|
342
|
-
def close
|
343
|
-
@ssl_socket.close
|
344
|
-
@socket.close
|
345
|
-
end
|
346
|
-
|
347
|
-
def closed?
|
348
|
-
@socket.closed?
|
349
|
-
end
|
350
|
-
|
351
|
-
def eof?
|
352
|
-
@ssl_socket.eof?
|
353
|
-
end
|
354
|
-
|
355
|
-
def gets(*args)
|
356
|
-
str = @ssl_socket.gets(*args)
|
357
|
-
debug(str)
|
358
|
-
str
|
359
|
-
end
|
360
|
-
|
361
|
-
def read(*args)
|
362
|
-
str = @ssl_socket.read(*args)
|
363
|
-
debug(str)
|
364
|
-
str
|
365
|
-
end
|
366
|
-
|
367
|
-
def readpartial(*args)
|
368
|
-
str = @ssl_socket.readpartial(*args)
|
369
|
-
debug(str)
|
370
|
-
str
|
371
|
-
end
|
372
|
-
|
373
|
-
def <<(str)
|
374
|
-
rv = @ssl_socket.write(str)
|
375
|
-
debug(str)
|
376
|
-
rv
|
377
|
-
end
|
378
|
-
|
379
|
-
def flush
|
380
|
-
@ssl_socket.flush
|
381
|
-
end
|
382
|
-
|
383
|
-
def sync
|
384
|
-
@ssl_socket.sync
|
385
|
-
end
|
386
|
-
|
387
|
-
def sync=(sync)
|
388
|
-
@ssl_socket.sync = sync
|
389
|
-
end
|
390
|
-
|
391
|
-
private
|
392
|
-
|
393
|
-
def check_mask(value, mask)
|
394
|
-
value & mask == mask
|
395
|
-
end
|
396
|
-
|
397
|
-
def create_openssl_socket(socket)
|
398
|
-
ssl_socket = nil
|
399
|
-
if OpenSSL::SSL.const_defined?("SSLContext")
|
400
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
401
|
-
@context.set_context(ctx)
|
402
|
-
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
403
|
-
else
|
404
|
-
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
|
405
|
-
@context.set_context(ssl_socket)
|
406
|
-
end
|
407
|
-
ssl_socket
|
408
|
-
end
|
409
|
-
|
410
|
-
def debug(str)
|
411
|
-
@debug_dev << str if @debug_dev && str
|
412
|
-
end
|
413
|
-
end
|
414
|
-
|
415
|
-
|
416
296
|
# Wraps up a Socket for method interception.
|
417
297
|
module SocketWrap
|
418
298
|
def initialize(socket, *args)
|
@@ -432,20 +312,20 @@ class HTTPClient
|
|
432
312
|
@socket.eof?
|
433
313
|
end
|
434
314
|
|
435
|
-
def gets(
|
436
|
-
@socket.gets(
|
315
|
+
def gets(rs)
|
316
|
+
@socket.gets(rs)
|
437
317
|
end
|
438
318
|
|
439
|
-
def read(
|
440
|
-
@socket.read(
|
319
|
+
def read(size, buf = nil)
|
320
|
+
@socket.read(size, buf)
|
441
321
|
end
|
442
322
|
|
443
|
-
def readpartial(
|
323
|
+
def readpartial(size, buf = nil)
|
444
324
|
# StringIO doesn't support :readpartial
|
445
325
|
if @socket.respond_to?(:readpartial)
|
446
|
-
@socket.readpartial(
|
326
|
+
@socket.readpartial(size, buf)
|
447
327
|
else
|
448
|
-
@socket.read(
|
328
|
+
@socket.read(size, buf)
|
449
329
|
end
|
450
330
|
end
|
451
331
|
|
@@ -481,19 +361,19 @@ class HTTPClient
|
|
481
361
|
debug("! CONNECTION CLOSED\n")
|
482
362
|
end
|
483
363
|
|
484
|
-
def gets(
|
364
|
+
def gets(rs)
|
485
365
|
str = super
|
486
366
|
debug(str)
|
487
367
|
str
|
488
368
|
end
|
489
369
|
|
490
|
-
def read(
|
370
|
+
def read(size, buf = nil)
|
491
371
|
str = super
|
492
372
|
debug(str)
|
493
373
|
str
|
494
374
|
end
|
495
375
|
|
496
|
-
def readpartial(
|
376
|
+
def readpartial(size, buf = nil)
|
497
377
|
str = super
|
498
378
|
debug(str)
|
499
379
|
str
|
@@ -533,6 +413,10 @@ class HTTPClient
|
|
533
413
|
def <<(str)
|
534
414
|
# ignored
|
535
415
|
end
|
416
|
+
|
417
|
+
def peer_cert
|
418
|
+
nil
|
419
|
+
end
|
536
420
|
end
|
537
421
|
|
538
422
|
|
@@ -679,18 +563,8 @@ class HTTPClient
|
|
679
563
|
begin
|
680
564
|
read_header if @state == :META
|
681
565
|
return nil if @state != :DATA
|
682
|
-
if @
|
683
|
-
|
684
|
-
# - zlib 1.2.5 Manual
|
685
|
-
# http://www.zlib.net/manual.html#Advanced
|
686
|
-
# > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
|
687
|
-
# > windowBits to enable zlib and gzip decoding with automatic header detection,
|
688
|
-
# > or add 16 to decode only the gzip format
|
689
|
-
inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
690
|
-
original_block = block
|
691
|
-
block = Proc.new { |buf|
|
692
|
-
original_block.call(inflate_stream.inflate(buf))
|
693
|
-
}
|
566
|
+
if @transparent_gzip_decompression
|
567
|
+
block = content_inflater_block(@content_encoding, block)
|
694
568
|
end
|
695
569
|
if @chunked
|
696
570
|
read_body_chunked(&block)
|
@@ -713,8 +587,122 @@ class HTTPClient
|
|
713
587
|
nil
|
714
588
|
end
|
715
589
|
|
590
|
+
def create_socket(host, port)
|
591
|
+
socket = nil
|
592
|
+
begin
|
593
|
+
@debug_dev << "! CONNECT TO #{host}:#{port}\n" if @debug_dev
|
594
|
+
clean_host = host.delete("[]")
|
595
|
+
if @socket_local == Site::EMPTY
|
596
|
+
socket = TCPSocket.new(clean_host, port)
|
597
|
+
else
|
598
|
+
clean_local = @socket_local.host.delete("[]")
|
599
|
+
socket = TCPSocket.new(clean_host, port, clean_local, @socket_local.port)
|
600
|
+
end
|
601
|
+
if @debug_dev
|
602
|
+
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
603
|
+
socket.extend(DebugSocket)
|
604
|
+
socket.debug_dev = @debug_dev
|
605
|
+
end
|
606
|
+
rescue SystemCallError => e
|
607
|
+
e.message << " (#{host}:#{port})"
|
608
|
+
raise
|
609
|
+
rescue SocketError => e
|
610
|
+
e.message << " (#{host}:#{port})"
|
611
|
+
raise
|
612
|
+
end
|
613
|
+
socket
|
614
|
+
end
|
615
|
+
|
616
|
+
def create_loopback_socket(host, port, str)
|
617
|
+
@debug_dev << "! CONNECT TO #{host}:#{port}\n" if @debug_dev
|
618
|
+
socket = LoopBackSocket.new(host, port, str)
|
619
|
+
if @debug_dev
|
620
|
+
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
621
|
+
socket.extend(DebugSocket)
|
622
|
+
socket.debug_dev = @debug_dev
|
623
|
+
end
|
624
|
+
if https?(@dest) && @proxy
|
625
|
+
connect_ssl_proxy(socket, Util.urify(@dest.to_s))
|
626
|
+
end
|
627
|
+
socket
|
628
|
+
end
|
629
|
+
|
630
|
+
def connect_ssl_proxy(socket, uri)
|
631
|
+
req = HTTP::Message.new_connect_request(uri)
|
632
|
+
@client.request_filter.each do |filter|
|
633
|
+
filter.filter_request(req)
|
634
|
+
end
|
635
|
+
set_header(req)
|
636
|
+
req.dump(socket)
|
637
|
+
socket.flush unless @socket_sync
|
638
|
+
res = HTTP::Message.new_response('')
|
639
|
+
parse_header(socket)
|
640
|
+
res.http_version, res.status, res.reason = @version, @status, @reason
|
641
|
+
@headers.each do |key, value|
|
642
|
+
res.header.set(key.to_s, value)
|
643
|
+
end
|
644
|
+
commands = @client.request_filter.collect { |filter|
|
645
|
+
filter.filter_response(req, res)
|
646
|
+
}
|
647
|
+
if commands.find { |command| command == :retry }
|
648
|
+
raise RetryableResponse.new(res)
|
649
|
+
end
|
650
|
+
unless @status == 200
|
651
|
+
raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
716
655
|
private
|
717
656
|
|
657
|
+
# This inflater allows deflate compression with/without zlib header
|
658
|
+
class LenientInflater
|
659
|
+
def initialize
|
660
|
+
@inflater = Zlib::Inflate.new(Zlib::MAX_WBITS)
|
661
|
+
@first = true
|
662
|
+
end
|
663
|
+
|
664
|
+
def inflate(body)
|
665
|
+
if @first
|
666
|
+
first_inflate(body)
|
667
|
+
else
|
668
|
+
@inflater.inflate(body)
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
private
|
673
|
+
|
674
|
+
def first_inflate(body)
|
675
|
+
@first = false
|
676
|
+
begin
|
677
|
+
@inflater.inflate(body)
|
678
|
+
rescue Zlib::DataError
|
679
|
+
# fallback to deflate without zlib header
|
680
|
+
@inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
681
|
+
@inflater.inflate(body)
|
682
|
+
end
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
def content_inflater_block(content_encoding, block)
|
687
|
+
case content_encoding
|
688
|
+
when 'gzip', 'x-gzip'
|
689
|
+
# zlib itself has a functionality to decompress gzip stream.
|
690
|
+
# - zlib 1.2.5 Manual
|
691
|
+
# http://www.zlib.net/manual.html#Advanced
|
692
|
+
# > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
|
693
|
+
# > windowBits to enable zlib and gzip decoding with automatic header detection,
|
694
|
+
# > or add 16 to decode only the gzip format
|
695
|
+
inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
696
|
+
when 'deflate'
|
697
|
+
inflate_stream = LenientInflater.new
|
698
|
+
else
|
699
|
+
return block
|
700
|
+
end
|
701
|
+
Proc.new { |buf|
|
702
|
+
block.call(inflate_stream.inflate(buf))
|
703
|
+
}
|
704
|
+
end
|
705
|
+
|
718
706
|
def set_header(req)
|
719
707
|
if @requested_version
|
720
708
|
if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
|
@@ -744,29 +732,14 @@ class HTTPClient
|
|
744
732
|
retry_number = 0
|
745
733
|
begin
|
746
734
|
timeout(@connect_timeout, ConnectTimeoutError) do
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
begin
|
755
|
-
@socket.ssl_connect(@dest.host)
|
756
|
-
ensure
|
757
|
-
if $DEBUG
|
758
|
-
warn("Protocol version: #{@socket.ssl_version}")
|
759
|
-
warn("Cipher: #{@socket.ssl_cipher.inspect}")
|
760
|
-
warn("State: #{@socket.ssl_state}")
|
761
|
-
end
|
762
|
-
end
|
763
|
-
@socket.post_connection_check(@dest)
|
764
|
-
@ssl_peer_cert = @socket.peer_cert
|
765
|
-
end
|
735
|
+
if str = @test_loopback_http_response.shift
|
736
|
+
@socket = create_loopback_socket(site.host, site.port, str)
|
737
|
+
elsif https?(@dest)
|
738
|
+
@socket = SSLSocket.create_socket(self)
|
739
|
+
@ssl_peer_cert = @socket.peer_cert
|
740
|
+
else
|
741
|
+
@socket = create_socket(site.host, site.port)
|
766
742
|
end
|
767
|
-
# Use Ruby internal buffering instead of passing data immediately
|
768
|
-
# to the underlying layer
|
769
|
-
# => we need to to call explicitly flush on the socket
|
770
743
|
@socket.sync = @socket_sync
|
771
744
|
end
|
772
745
|
rescue RetryableResponse
|
@@ -788,71 +761,13 @@ class HTTPClient
|
|
788
761
|
@state = :WAIT
|
789
762
|
end
|
790
763
|
|
791
|
-
def create_socket(site)
|
792
|
-
socket = nil
|
793
|
-
begin
|
794
|
-
@debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
|
795
|
-
clean_host = site.host.delete("[]")
|
796
|
-
if str = @test_loopback_http_response.shift
|
797
|
-
socket = LoopBackSocket.new(clean_host, site.port, str)
|
798
|
-
elsif @socket_local == Site::EMPTY
|
799
|
-
socket = TCPSocket.new(clean_host, site.port)
|
800
|
-
else
|
801
|
-
clean_local = @socket_local.host.delete("[]")
|
802
|
-
socket = TCPSocket.new(clean_host, site.port, clean_local, @socket_local.port)
|
803
|
-
end
|
804
|
-
if @debug_dev
|
805
|
-
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
806
|
-
socket.extend(DebugSocket)
|
807
|
-
socket.debug_dev = @debug_dev
|
808
|
-
end
|
809
|
-
rescue SystemCallError => e
|
810
|
-
e.message << " (#{site})"
|
811
|
-
raise
|
812
|
-
rescue SocketError => e
|
813
|
-
e.message << " (#{site})"
|
814
|
-
raise
|
815
|
-
end
|
816
|
-
socket
|
817
|
-
end
|
818
|
-
|
819
|
-
# wrap socket with OpenSSL.
|
820
|
-
def create_ssl_socket(raw_socket)
|
821
|
-
SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev)
|
822
|
-
end
|
823
|
-
|
824
|
-
def connect_ssl_proxy(socket, uri)
|
825
|
-
req = HTTP::Message.new_connect_request(uri)
|
826
|
-
@client.request_filter.each do |filter|
|
827
|
-
filter.filter_request(req)
|
828
|
-
end
|
829
|
-
set_header(req)
|
830
|
-
req.dump(@socket)
|
831
|
-
@socket.flush unless @socket_sync
|
832
|
-
res = HTTP::Message.new_response('')
|
833
|
-
parse_header
|
834
|
-
res.http_version, res.status, res.reason = @version, @status, @reason
|
835
|
-
@headers.each do |key, value|
|
836
|
-
res.header.set(key.to_s, value)
|
837
|
-
end
|
838
|
-
commands = @client.request_filter.collect { |filter|
|
839
|
-
filter.filter_response(req, res)
|
840
|
-
}
|
841
|
-
if commands.find { |command| command == :retry }
|
842
|
-
raise RetryableResponse.new(res)
|
843
|
-
end
|
844
|
-
unless @status == 200
|
845
|
-
raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
|
846
|
-
end
|
847
|
-
end
|
848
|
-
|
849
764
|
# Read status block.
|
850
765
|
def read_header
|
851
766
|
@content_length = nil
|
852
767
|
@chunked = false
|
853
|
-
@
|
768
|
+
@content_encoding = nil
|
854
769
|
@chunk_length = 0
|
855
|
-
parse_header
|
770
|
+
parse_header(@socket)
|
856
771
|
# Header of the request has been parsed.
|
857
772
|
@state = :DATA
|
858
773
|
req = @requests.shift
|
@@ -868,12 +783,12 @@ class HTTPClient
|
|
868
783
|
end
|
869
784
|
|
870
785
|
StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
|
871
|
-
def parse_header
|
786
|
+
def parse_header(socket)
|
872
787
|
timeout(@receive_timeout, ReceiveTimeoutError) do
|
873
788
|
initial_line = nil
|
874
789
|
begin
|
875
790
|
begin
|
876
|
-
initial_line =
|
791
|
+
initial_line = socket.gets("\n")
|
877
792
|
if initial_line.nil?
|
878
793
|
close
|
879
794
|
raise KeepAliveDisconnected.new(self)
|
@@ -896,7 +811,7 @@ class HTTPClient
|
|
896
811
|
@next_connection = HTTP::Message.keep_alive_enabled?(@version)
|
897
812
|
@headers = []
|
898
813
|
while true
|
899
|
-
line =
|
814
|
+
line = socket.gets("\n")
|
900
815
|
unless line
|
901
816
|
raise BadResponseError.new('unexpected EOF')
|
902
817
|
end
|
@@ -908,7 +823,7 @@ class HTTPClient
|
|
908
823
|
last << line.strip
|
909
824
|
else
|
910
825
|
key, value = line.strip.split(/\s*:\s*/, 2)
|
911
|
-
|
826
|
+
parse_content_header(key, value)
|
912
827
|
@headers << [key, value]
|
913
828
|
end
|
914
829
|
end
|
@@ -921,18 +836,20 @@ class HTTPClient
|
|
921
836
|
((status >= 100 && status < 200) || status == 204 || status == 304)
|
922
837
|
end
|
923
838
|
|
924
|
-
def
|
839
|
+
def parse_content_header(key, value)
|
925
840
|
key = key.downcase
|
926
|
-
|
841
|
+
case key
|
842
|
+
when 'content-length'
|
927
843
|
@content_length = value.to_i
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
844
|
+
when 'content-encoding'
|
845
|
+
@content_encoding = value.downcase
|
846
|
+
when 'transfer-encoding'
|
847
|
+
if value.downcase == 'chunked'
|
848
|
+
@chunked = true
|
849
|
+
@chunk_length = 0
|
850
|
+
@content_length = nil
|
851
|
+
end
|
852
|
+
when 'connection', 'proxy-connection'
|
936
853
|
if value.downcase == 'keep-alive'
|
937
854
|
@next_connection = true
|
938
855
|
else
|