diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ec4ac9f..723bcd0d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,3 +63,25 @@ jobs: cache-version: 2 - name: Run tests run: bundle exec rake --trace RUBYOPT="${{ matrix.ruby-opt }}" + + jruby: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Java + uses: actions/setup-java@v6 + with: + distribution: temurin + java-version: '21' + - name: Set up JRuby + uses: ruby/setup-ruby@v1 + with: + ruby-version: jruby-10.1.2.0 + bundler-cache: true + cache-version: 2 + - name: Run tests + run: bundle exec rake --trace RUBYOPT="-rbundler/setup" + - name: Run JRuby-specific tests + run: bundle exec ruby -Ilib test/jruby_ssl_socket/test_pemutils.rb + - name: Run JRuby socket timeout tests + run: bundle exec ruby -Ilib test/jruby_ssl_socket/test_socket_timeout.rb diff --git a/lib/httpclient/jruby_ssl_socket.rb b/lib/httpclient/jruby_ssl_socket.rb index 4a30db66..ae2a8b0d 100644 --- a/lib/httpclient/jruby_ssl_socket.rb +++ b/lib/httpclient/jruby_ssl_socket.rb @@ -128,6 +128,8 @@ def fill @bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size] end size + rescue java.net.SocketTimeoutException => e + raise HTTPClient::ReceiveTimeoutError.new(e.getMessage) rescue java.io.IOException => e raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}") end diff --git a/test/helper.rb b/test/helper.rb index 5ecbb85a..e58c2eb7 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -9,6 +9,38 @@ require 'cgi' require 'webrick/httputils' +if RUBY_ENGINE == 'jruby' + require 'delegate' + + module JRubyWEBrickSSLReadBuffer + class ReadableIO < SimpleDelegator + def initialize(socket) + @ssl_socket = socket + super(socket.to_io) + end + + def wait_readable(timeout = nil) + # JRuby may buffer request bytes while completing the TLS handshake. + @ssl_socket.pending > 0 || super + end + end + + module HTTPServerPatch + def run(sock) + if sock.respond_to?(:pending) + readable_io = ReadableIO.new(sock) + sock.define_singleton_method(:to_io) { readable_io } + end + super + ensure + sock.singleton_class.remove_method(:to_io) if readable_io + end + end + end + + WEBrick::HTTPServer.prepend(JRubyWEBrickSSLReadBuffer::HTTPServerPatch) +end + module Helper Port = 17171 diff --git a/test/jruby_ssl_socket/test_socket_timeout.rb b/test/jruby_ssl_socket/test_socket_timeout.rb new file mode 100644 index 00000000..8b854431 --- /dev/null +++ b/test/jruby_ssl_socket/test_socket_timeout.rb @@ -0,0 +1,18 @@ +require File.expand_path('../helper', File.dirname(__FILE__)) + + +class JRubySocketTimeoutTest < Test::Unit::TestCase + def test_read_timeout_is_reported_as_receive_timeout + socket = HTTPClient::JavaSocketWrap.allocate + input = Object.new + input.define_singleton_method(:read) do |_buffer| + raise Java::JavaNet::SocketTimeoutException.new('Read timed out') + end + socket.instance_variable_set(:@instr, input) + socket.instance_variable_set(:@buf, Java::byte[HTTPClient::JavaSocketWrap::BUF_SIZE].new) + + assert_raise(HTTPClient::ReceiveTimeoutError) do + socket.send(:fill) + end + end +end diff --git a/test/test_ssl.rb b/test/test_ssl.rb index 35cd23d2..c8372744 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -332,7 +332,10 @@ def test_ciphers assert_match(/No appropriate protocol/, ssle.message) end # - cfg.ciphers = %w(TLS_RSA_WITH_AES_128_CBC_SHA) + # CI's Temurin 21 disables static-RSA suites such as + # TLS_RSA_WITH_AES_128_CBC_SHA in jdk.tls.disabledAlgorithms. Use an enabled + # ECDHE suite to verify explicitly configured ciphers can be negotiated. + cfg.ciphers = %w(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) assert_equal("hello", @client.get_content(@url)) # cfg.ciphers = HTTPClient::SSLConfig::CIPHERS_DEFAULT @@ -386,7 +389,15 @@ def test_no_sslv3 end end - if OPENSSL_VERSION < 3 + if RUBY_ENGINE == 'jruby' + def test_disallow_tlsv1 + @client.ssl_config.ssl_version = :TLSv1 + @client.ssl_config.verify_mode = nil + assert_raise(OpenSSL::SSL::SSLError) do + @client.get(@url) + end + end + elsif OPENSSL_VERSION < 3 def test_allow_tlsv1 teardown_server setup_server_with_ssl_version(:TLSv1) @@ -408,7 +419,7 @@ def test_disallow_tlsv1 end def test_use_higher_TLS - omit('TODO: it does not pass with Java 7 or old openssl') + omit('TODO: it does not pass with Java 7 or old openssl') unless RUBY_ENGINE == 'jruby' teardown_server setup_server_with_ssl_version('TLSv1_2') assert_nothing_raised do