Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 13 additions & 26 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ jobs:
'3.3',
'3.4',
'4.0',
'head'
'head',
'jruby-10.1',
'jruby-head'
]
# Rake replaces RUBYOPT for the test subprocess; keep it in Bundler's context.
ruby-opt: ["-rbundler/setup"]
ruby-opt: ["-rbundler/setup --enable-frozen-string-literal --debug-frozen-string-literal"]
include:
# Ruby 2.5 through 3.1 have no prebuilt binaries for Ubuntu 26.04.
- ruby-version: '2.5'
Expand All @@ -49,39 +51,24 @@ jobs:
os: ubuntu-latest
- ruby-version: 'head'
os: ubuntu-latest
- ruby-version: '3.4'
ruby-opt: '-rbundler/setup --enable-frozen-string-literal --debug-frozen-string-literal'
- ruby-version: 'jruby-10.1'
os: ubuntu-latest
- ruby-version: 'jruby-head'
os: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
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
- uses: actions/checkout@v7
- name: Set up Java
if: startsWith(matrix.ruby-version, 'jruby-')
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
- name: Set up JRuby
java-version: '25'
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: jruby-10.1.2.0
ruby-version: ${{ matrix.ruby-version }}
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
run: bundle exec rake --trace RUBYOPT="${{ matrix.ruby-opt }}"
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ENV['CI_REPORTS'] = File.expand_path('./reports', File.dirname(__FILE__))
Rake::TestTask.new('test') do |test|
test.libs << 'lib'
test.verbose = true
test.test_files = Dir.glob('test/test_*.rb')
test.test_files = Dir.glob('test/**/test_*.rb')
end

unless ENV['CI']
Expand Down
4 changes: 2 additions & 2 deletions lib/httpclient/jruby_ssl_socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def initialize(socket, debug_dev = nil)
@outstr = @socket.getOutputStream
@instr = BufferedInputStream.new(@socket.getInputStream)
@buf = (' ' * BUF_SIZE).to_java_bytes
@bufstr = ''
@bufstr = ''.dup
end

def close
Expand Down Expand Up @@ -352,7 +352,7 @@ def add(cert_source)
File.read(cert_source).each_line do |line|
case line
when /-----BEGIN CERTIFICATE-----/
pem = ''
pem = ''.dup
when /-----END CERTIFICATE-----/
load_pem(pem)
# keep parsing in case where multiple certificates in a file
Expand Down
1 change: 1 addition & 0 deletions lib/httpclient/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ def debug(str)
if str && @debug_dev
if str.index("\0")
require 'hexdump'
str = str.dup
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
@debug_dev << HexDump.encode(str).join("\n")
else
Expand Down
22 changes: 21 additions & 1 deletion test/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
require 'webrick/httpproxy.rb'
require 'logger'
require 'stringio'
require 'cgi'
begin
require 'cgi/escape'
rescue LoadError
require 'cgi'
end
require 'webrick/httputils'

if RUBY_ENGINE == 'jruby'
require 'webrick/https'
require 'delegate'

module JRubyWEBrickSSLReadBuffer
Expand All @@ -36,9 +41,24 @@ def run(sock)
sock.singleton_class.remove_method(:to_io) if readable_io
end
end

module HTTPRequestPatch
def parse(socket = nil)
skip_peer_cert_chain = socket.respond_to?(:peer_cert_chain) &&
socket.respond_to?(:peer_cert) && socket.peer_cert.nil?
if skip_peer_cert_chain
# WEBrick asks JRuby for a peer chain even when no client certificate was sent.
socket.define_singleton_method(:peer_cert_chain) { nil }
end
super
ensure
socket.singleton_class.remove_method(:peer_cert_chain) if skip_peer_cert_chain
end
end
end

WEBrick::HTTPServer.prepend(JRubyWEBrickSSLReadBuffer::HTTPServerPatch)
WEBrick::HTTPRequest.prepend(JRubyWEBrickSSLReadBuffer::HTTPRequestPatch)
end


Expand Down
30 changes: 30 additions & 0 deletions test/jruby_ssl_socket/test_mutable_buffers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require File.expand_path('../helper', File.dirname(__FILE__))


class JRubyMutableBuffersTest < Test::Unit::TestCase
def setup
omit('JRuby-specific test') unless RUBY_ENGINE == 'jruby'
end

def test_socket_reads_using_mutable_buffer
input = Java::JavaIo::ByteArrayInputStream.new("hello\nworld".to_java_bytes)
output = Java::JavaIo::ByteArrayOutputStream.new
socket = Object.new
socket.define_singleton_method(:getInputStream) { input }
socket.define_singleton_method(:getOutputStream) { output }
wrapped_socket = HTTPClient::JavaSocketWrap.new(socket)

assert_equal("hello\n", wrapped_socket.gets("\n"))
assert_equal('wo', wrapped_socket.read(2))
assert_equal('rld', wrapped_socket.readpartial(3))
assert_raise(EOFError) { wrapped_socket.readpartial(1) }
end

def test_trust_store_loads_multiple_pem_certificates
loader = HTTPClient::JRubySSLSocket::TrustStoreLoader.new
loader.add(File.expand_path('../ca-chain.pem', File.dirname(__FILE__)))

assert_equal(2, loader.size)
assert_equal(2, loader.trust_store.size)
end
end
7 changes: 4 additions & 3 deletions test/jruby_ssl_socket/test_pemutils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@ class PEMUtilsTest < Test::Unit::TestCase
include Helper

def setup
omit('JRuby-specific test') unless RUBY_ENGINE == 'jruby'
@raw_cert = "-----BEGIN CERTIFICATE-----\nMIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB\nGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5\nIENBMB4XDTE2MDgxMDE3MjEzNFoXDTE3MDgxMDE3MjEzNFowSzETMBEGCgmSJomT\n8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEZMBcGA1UEAwwQ\nUnVieSBjZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAJCfsSXpSMpmZCVa+ZCM+QDgomnhDlvnrGDq6pasTaIspGTXgws+7r8Dt/cNe6EH\nHJpRH2cGRiO4yPcfcT9eS4X7k8OC4f33wHfACOmLu6LeoNE8ujmSk6L6WzLUI+sE\nnLZbFrXxoAo4XHsm8vEG9C+jEoXZ1p+47wrAGaDwDQTnzlMy4dT9pRQEJP2G/Rry\nUkuZn8SUWmh3/YS78iaSzsNF1cgE1ealHOrPPFDjiCGDaH/LHyUPYlbFSLZ/B7Qx\nLxi5sePLcywWq/EJrmWpgeVTDjtNijsdKv/A3qkY+fm/oD0pzt7XsfJaP9YKNyJO\nQFdxWZeiPcDF+Hwf+IwSr+kCAwEAAaMxMC8wDgYDVR0PAQH/BAQDAgeAMB0GA1Ud\nDgQWBBQNvzYzJyXemGhxbA8NMXLolDnPyjANBgkqhkiG9w0BAQsFAAOCAQEARIJV\noKejGlOTn71QutnNnu07UtTu0IHs6YqjYzzND+m4JXLN+wvYm72AFUG0b1L7dRg0\niK8XjQrlNQNVqP1Mc6tffchy20neOPOHeiO6qTdRU8P2S8D3Uwe+1qhgxjfE+cWc\nwZmWxYK4HA8c58PxWMqrkr2QqXDplG9KWLvOgrtPGiLLZcQSKhvvB63QzItHBDU6\nRayiJY3oPkK/HrIvFlySqFqzWmuyknkciOFywEHQMz/tcSFJ2QFpPj/tBz9VXohH\nZ8KscmfhZrTPBjo+ky1lz/WraWoz4LMiLnkC2ABczWLRSawu+v3Irx1NFJngt05e\npqwtqIUeg7j+JLiTaA==\n-----END CERTIFICATE-----"
end

def test_read_certificate
assert_nothing_raised do
binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(@raw_cert)
HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(@raw_cert)
end
end

def test_read_certificate_works_with_random_ascii_text_outside_begin_end
raw_cert_with_ascii = "some text before begin\n" + @raw_cert + "\nsome text after end"
assert_nothing_raised do
binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(raw_cert_with_ascii)
HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(raw_cert_with_ascii)
end
end

def test_read_certificate_uses_all_content_if_missing_begin_end
cert = @raw_cert.sub(/-----BEGIN CERTIFICATE-----/, '').sub(/-----END CERTIFICATE-----/, '')
assert_nothing_raised do
binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(cert)
HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(cert)
end
end

Expand Down
1 change: 1 addition & 0 deletions test/jruby_ssl_socket/test_socket_timeout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

class JRubySocketTimeoutTest < Test::Unit::TestCase
def test_read_timeout_is_reported_as_receive_timeout
omit('JRuby-specific test') unless RUBY_ENGINE == 'jruby'
socket = HTTPClient::JavaSocketWrap.allocate
input = Object.new
input.define_singleton_method(:read) do |_buffer|
Expand Down
9 changes: 8 additions & 1 deletion test/test_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
require 'digest/md5'
require 'rack'
require 'rack/lint'
require 'rack-ntlm'
# rack-ntlm-test-service 0.0.1 assigns an unused rescue variable while loading.
original_verbose = $VERBOSE
begin
$VERBOSE = nil
require 'rack-ntlm'
ensure
$VERBOSE = original_verbose
end

class TestAuth < Test::Unit::TestCase
include Helper
Expand Down Expand Up @@ -128,7 +135,7 @@
@active && @value ? @value.size : 0
end

def value

Check warning on line 138 in test/test_auth.rb

View workflow job for this annotation

GitHub Actions / test (jruby-head, -rbundler/setup --enable-frozen-string-literal --debug-frozen-string-literal)

method redefined; discarding old value
@value ||= ""
end
end
Expand Down
22 changes: 19 additions & 3 deletions test/test_httpclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ def test_debug_dev_stream
assert(!str.empty?)
end

data('mutable' => false, 'frozen' => true)
def test_debug_socket_does_not_mutate_binary_input(freeze_payload)
debug_dev = StringIO.new
socket = Object.new
socket.extend(HTTPClient::DebugSocket)
socket.debug_dev = debug_dev
payload = "\x00".dup
payload.freeze if freeze_payload

assert_nothing_raised { socket.send(:debug, payload) }
assert_equal("\x00", payload)
assert_equal(Encoding::UTF_8, payload.encoding)
assert(!debug_dev.string.empty?)
end

def test_protocol_version_http09
@client.protocol_version = 'HTTP/0.9'
@client.debug_dev = str = ''.dup
Expand Down Expand Up @@ -768,15 +783,16 @@ def test_get_async_for_largebody
if RUBY_VERSION > "1.9"
def test_post_async_with_default_internal
original_encoding = Encoding.default_internal
Encoding.default_internal = Encoding::UTF_8
# This test covers the legacy setter even though JRuby deprecates it.
silent { Encoding.default_internal = Encoding::UTF_8 }
begin
post_body = StringIO.new("こんにちは")
conn = @client.post_async(serverurl + 'servlet', post_body)
Thread.pass while !conn.finished?
res = conn.pop
assert_equal 'post,こんにちは', res.content.read
ensure
Encoding.default_internal = original_encoding
silent { Encoding.default_internal = original_encoding }
end
end
end
Expand Down Expand Up @@ -934,7 +950,7 @@ def test_post_with_file
STDOUT.sync = true
File.open(__FILE__) do |file|
res = @client.post(serverurl + 'servlet', {1=>2, 3=>file})
assert_match(/^Content-Disposition: form-data; name="1"\r\n/nm, res.content)
assert_match(/^Content-Disposition: form-data; name="1"\r\n/m, res.content.b)
assert_match(/^Content-Disposition: form-data; name="3";/, res.content)
assert_match(/FIND_TAG_IN_THIS_FILE/, res.content)
end
Expand Down
Loading