From 4c7948fb7ffd8dd1ccdee532362129899b747d91 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 24 Dec 2025 01:12:46 +0900 Subject: [PATCH 1/9] ssl: add :nodoc: to OpenSSL::Buffering::Buffer The Buffer class is an implementation detail. It will be removed once ruby/openssl starts to require Ruby 3.4 or later, where String#append_as_bytes is always available. --- lib/openssl/buffering.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index 1464a4292..cc1178716 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -22,8 +22,7 @@ module OpenSSL::Buffering include Enumerable - # A buffer which will retain binary encoding. - class Buffer < String + class Buffer < String # :nodoc: unless String.method_defined?(:append_as_bytes) alias_method :_append, :<< def append_as_bytes(string) From 27a60da6b0f74e60eb468e87938faeca9fcdca73 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 17 Dec 2025 14:34:41 +0900 Subject: [PATCH 2/9] ssl: align #ungetc and #ungetbyte with IO Allow OpenSSL::Buffering#ungetc to take String, not just Integer. Add OpenSSL::Buffering#ungetbyte with the consistent behavior with IO#ungetbyte in Ruby >= 1.9. --- lib/openssl/buffering.rb | 13 ++++++++- test/openssl/test_pair.rb | 57 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index cc1178716..ce5d1042e 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -323,7 +323,18 @@ def readchar # Has no effect on unbuffered reads (such as #sysread). def ungetc(c) - @rbuffer[0,0] = c.chr + @rbuffer[0, 0] = Integer === c ? c.chr : c + @rbuffer.force_encoding(Encoding::BINARY) + nil + end + + ## + # Pushes byte _c_ back onto the stream such that a subsequent buffered byte + # read will return it. + def ungetbyte(c) + @rbuffer[0, 0] = Integer === c ? (c & 0xff).chr : c + @rbuffer.force_encoding(Encoding::BINARY) + nil end ## diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index 8cb1060e4..2d74731c5 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -79,6 +79,63 @@ def test_readbyte } end + def test_ungetc + ssl_pair {|s1, s2| + s1 << "abc" + s1.close + assert_equal("a", s2.read(1)) + assert_nil(s2.ungetc("A")) + assert_equal("Abc", s2.read(3)) + assert_predicate(s2, :eof?) + + s2.ungetc("B") + assert_not_predicate(s2, :eof?) + assert_equal("B", s2.read) + + s2.ungetc("あ") # \xe3\x81\x82 + s = s2.read(2) + assert_equal("\xe3\x81".b, s) + assert_equal(Encoding::BINARY, s.encoding) + s2.ungetc("") + assert_equal("\x82".b, s2.read) + + s2.ungetc(1) + assert_equal("\x01".b, s2.read) + assert_raise(RangeError) { s2.ungetc(258) } + assert_raise(RangeError) { s2.ungetc(-1) } + } + end + + def test_ungetbyte + ssl_pair {|s1, s2| + s1 << "abc" + s1.close + + assert_equal("a", s2.read(1)) + assert_nil(s2.ungetbyte("A")) + assert_equal("Abc", s2.read(3)) + assert_predicate(s2, :eof?) + + s2.ungetbyte("B") + assert_not_predicate(s2, :eof?) + assert_equal("B", s2.read) + + s2.ungetbyte("あ") # \xe3\x81\x82 + s = s2.read(2) + assert_equal("\xe3\x81".b, s) + assert_equal(Encoding::BINARY, s.encoding) + s2.ungetbyte("") + assert_equal("\x82".b, s2.read) + + s2.ungetbyte(1) + assert_equal("\x01".b, s2.read) + s2.ungetbyte(258) + assert_equal("\x02".b, s2.read) + s2.ungetbyte(-1) + assert_equal("\xff".b, s2.read) + } + end + def test_gets ssl_pair {|s1, s2| s1 << "abc\n\n$def123ghi" From dfcfa1cfe267a3b4f69334ee5a6b39d1669d4a4f Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 17 Dec 2025 14:53:38 +0900 Subject: [PATCH 3/9] ssl: align #each_byte and #each_char with IO Let OpenSSL::Buffering#each_byte return an Enumerator if called without a block. Add the #each_char variant for compatibility with Ruby 1.9 IO. --- lib/openssl/buffering.rb | 17 +++++++++++++-- test/openssl/test_pair.rb | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index ce5d1042e..e5981a0cb 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -296,13 +296,26 @@ def getc read(1) end + ## + # Calls the given block once for each character in the stream. + + def each_char + return to_enum(__method__) unless block_given? + while c = getc + yield c + end + self + end + ## # Calls the given block once for each byte in the stream. def each_byte # :yields: byte - while c = getc - yield(c.ord) + return to_enum(__method__) unless block_given? + while c = getbyte + yield c end + self end ## diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index 2d74731c5..b387ed4fd 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -79,6 +79,52 @@ def test_readbyte } end + def test_each_char + ssl_pair {|s1, s2| + s1 << "abc" + s1.close + chars = [] + ret = s2.each_char { |c| chars << c } + assert_same(s2, ret) + assert_equal(["a", "b", "c"], chars) + chars = [] + s2.each_char { |c| chars << c } + assert_equal([], chars) + } + end + + def test_each_char_enumerator + ssl_pair {|s1, s2| + s1 << "abc" + s1.close + assert_equal(["a", "b", "c"], s2.each_char.to_a) + assert_equal([], s2.each_char.to_a) + } + end + + def test_each_byte + ssl_pair {|s1, s2| + s1 << "abc" + s1.close + bytes = [] + ret = s2.each_byte { |b| bytes << b } + assert_same(s2, ret) + assert_equal([97, 98, 99], bytes) + bytes = [] + s2.each_byte { |b| bytes << b } + assert_equal([], bytes) + } + end + + def test_each_byte_enumerator + ssl_pair {|s1, s2| + s1 << "abc" + s1.close + assert_equal([97, 98, 99], s2.each_byte.to_a) + assert_equal([], s2.each_byte.to_a) + } + end + def test_ungetc ssl_pair {|s1, s2| s1 << "abc" From 7df340c0088815a16cd615f997739595da2bd2ca Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 16 Dec 2025 03:31:00 +0900 Subject: [PATCH 4/9] ssl: align #gets and line-based methods with IO Rewrite OpenSSL::Buffering#gets to handle parameters as closely to core IO as possible. This includes changes: - Add support for the calling form"gets(limit), where the rs is omitted. - Add support for rs=nil (slurp mode). - Add support for rs="" (paragraph mode). - Enforce limit correctly when EOF is reached before rs. - Do not attempt to read from the socket when limit=0 is passed. - Let #each_line, #readlines, and #readline forward all arguments to #gets, not just the rs. --- lib/openssl/buffering.rb | 98 +++++++++++++++++++++------ test/openssl/test_pair.rb | 139 +++++++++++++++++++++++++++++++++++++- 2 files changed, 212 insertions(+), 25 deletions(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index e5981a0cb..45ae6ab38 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -224,44 +224,99 @@ def read_nonblock(maxlen, buf=nil, exception: true) # _limit_ is provided the result will not be longer than the given number of # bytes. # - # _eol_ may be a String or Regexp. + # _eol_ may be a String or Regexp. _eol_ defaults to +$/+. # - # Unlike IO#gets the line read will not be assigned to +$_+. + # Note that Regexp _eol_ is an extension to the standard IO#gets. This mode + # is incompatible with _chomp_ option. # - # Unlike IO#gets the separator must be provided if a limit is provided. + # Unlike IO#gets, the line read will not be assigned to +$_+. - def gets(eol=$/, limit=nil, chomp: false) - idx = @rbuffer.index(eol) - until @eof - break if idx - fill_rbuff - idx = @rbuffer.index(eol) + def gets(eol = $/, limit = nil, chomp: false) + if limit.nil? && Integer === eol + eol, limit = $/, eol end - if eol.is_a?(Regexp) - size = idx ? idx+$&.size : nil + limit = nil if limit && limit < 0 + return String.new if limit == 0 + + case eol + when nil + gets_slurp(limit) + when Regexp + gets_regexp(eol, limit) + when "" + swallow_newlines + ret = gets_string("\n\n", limit, chomp) + swallow_newlines + ret else - size = idx ? idx+eol.size : nil + gets_string(eol, limit, chomp) end - if size && limit && limit >= 0 - size = [size, limit].min + end + + private def gets_string(eol, limit, chomp) + pos = 0 + while true + break if idx = @rbuffer.index(eol, pos) + break if limit && @rbuffer.bytesize >= limit + pos = [0, @rbuffer.bytesize - eol.bytesize + 1].max + break if @eof + fill_rbuff + end + if idx + size = idx + eol.bytesize + size = [size, limit].min if limit + else + size = limit end line = consume_rbuff(size) - if chomp && line + if chomp && idx line.chomp!(eol) end line end + private def gets_regexp(eol, limit) + while true + break if idx = @rbuffer.index(eol) + break if limit && @rbuffer.bytesize >= limit + break if @eof + fill_rbuff + end + if idx + size = idx + $&.size + size = [size, limit].min if limit + else + size = limit + end + consume_rbuff(size) + end + + private def gets_slurp(limit) + ret = read(limit) + return nil if ret && ret.empty? + ret + end + + private def swallow_newlines + while true + @rbuffer.sub!(/\A\n+/, "") + break if @eof || !@rbuffer.empty? + fill_rbuff + end + end + ## # Executes the block for every line in the stream where lines are separated # by _eol_. # # See also #gets - def each(eol=$/) - while line = self.gets(eol) + def each(eol = $/, limit = nil, chomp: false) + return to_enum(__method__, eol, limit, chomp: chomp) unless block_given? + while line = gets(eol, limit, chomp: chomp) yield line end + self end alias each_line each @@ -270,9 +325,9 @@ def each(eol=$/) # # See also #gets - def readlines(eol=$/) + def readlines(eol = $/, limit = nil, chomp: false) ary = [] - while line = self.gets(eol) + while line = gets(eol, limit, chomp: chomp) ary << line end ary @@ -283,9 +338,8 @@ def readlines(eol=$/) # # Raises EOFError if at end of file. - def readline(eol=$/) - raise EOFError if eof? - gets(eol) + def readline(eol = $/, limit = nil, chomp: false) + gets(eol, limit, chomp: chomp) or raise EOFError end ## diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index b387ed4fd..fca42319a 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -184,15 +184,100 @@ def test_ungetbyte def test_gets ssl_pair {|s1, s2| - s1 << "abc\n\n$def123ghi" + s1 << "abc\n\n$def123ghijk\nlmno" s1.close ret = s2.gets assert_equal Encoding::BINARY, ret.encoding assert_equal "abc\n", ret assert_equal "\n$", s2.gets("$") - assert_equal "def123", s2.gets(/\d+/) - assert_equal "ghi", s2.gets + assert_equal "def123", s2.gets("123") + assert_equal "ghi", s2.gets(":", 3) + assert_equal "j", s2.gets(1) + assert_equal "k\n", s2.gets("\n", -1) + assert_equal "lmno", s2.gets(-1) assert_equal nil, s2.gets + assert_equal "", s2.gets(0) + } + # rs spans multiple sysreads + ssl_pair {|s1, s2| + s1 << "a" * 8192 + "b" * 16384 + s1.close + assert_equal("a" * 8192, s2.gets("b" * 10000, chomp: true)) + } + end + + def test_gets_rs_nil + ssl_pair {|s1, s2| + s1 << "abc\n\ndef" + s1.close + assert_equal("", s2.gets(nil, 0)) + assert_equal("a", s2.gets(nil, 1)) + assert_equal("bc\n\ndef", s2.gets(nil)) + + # At EOF + assert_equal("", s2.gets(nil, 0)) + assert_nil(s2.gets(nil)) + assert_nil(s2.gets(nil, 1)) + assert_nil(s2.gets(nil, -1)) + } + ssl_pair {|s1, s2| + s1 << "abc\n\ndef" + s1.close + assert_equal("abc\n\ndef", s2.gets(nil, -1)) + } + end + + def test_gets_rs_empty_leading_newlines + ssl_pair {|s1, s2| + s1 << "abc\n\ndef\n\nghi" + s1.close + assert_equal("a", s2.gets("", 1)) + assert_equal("bc", s2.gets("", 2)) + assert_equal("def\n\n", s2.gets("")) + assert_equal("ghi", s2.gets("")) + } + # Leading and trailing newlines are trimmed + ssl_pair {|s1, s2| + s1 << "\n\nabc\n\n\n" + s1.close + assert_equal("abc\n\n", s2.gets("")) + assert_predicate(s2, :eof?) + } + # Leading and trailing newlines do not count towards limit, and + # trailing newlines are still trimmed after limit is reached + ssl_pair {|s1, s2| + s1 << "\n\nabc\n\n\n\ndef" + s1.close + assert_equal("a", s2.gets("", 1)) + assert_equal("bc\n", s2.gets("", 3)) + assert_equal("def", s2.read) + } + # chomp: true + ssl_pair {|s1, s2| + s1 << "a\n\n\ndef\n\n\n" + s1.close + assert_equal("a\n", s2.gets("", 2, chomp: true)) + assert_equal("def", s2.gets("", chomp: true)) + } + # \r does not have any special meaning in paragraph mode + ssl_pair {|s1, s2| + s1 << "\r\nabc\r\n\r\ndef\r\n" + s1.close + assert_equal("\r\nabc\r\n\r\ndef\r\n", s2.gets("", chomp: true)) + assert_predicate(s2, :eof?) + } + end + + def test_gets_rs_regexp + ssl_pair {|s1, s2| + # OpenSSL::Buffering-specific behavior + next unless s2.is_a?(OpenSSL::Buffering) + + s1 << "abc\n\n$def123ghi" + s1.close + assert_equal("abc\n", s2.gets(/\d+/, 4)) + assert_equal("\n$def123", s2.gets(/\d+/)) + assert_equal("ghi", s2.gets(/\d+/)) } end @@ -207,6 +292,30 @@ def test_gets_chomp } end + def test_gets_chomp_rs + rs = ":" + ssl_pair {|s1, s2| + s1 << "aaa:bbb" + s1.close + + assert_equal "aaa", s2.gets(rs, chomp: true) + assert_equal "bbb", s2.gets(rs, chomp: true) + assert_nil s2.gets(rs, chomp: true) + } + end + + def test_gets_chomp_default_rs + ssl_pair {|s1, s2| + s1 << "aaa\r\nbbb\nccc" + s1.close + + assert_equal "aaa", s2.gets(chomp: true) + assert_equal "bbb", s2.gets(chomp: true) + assert_equal "ccc", s2.gets(chomp: true) + assert_nil s2.gets + } + end + def test_gets_eof_limit ssl_pair {|s1, s2| s1.write("hello") @@ -215,6 +324,29 @@ def test_gets_eof_limit } end + def test_each_line + ssl_pair {|s1, s2| + s1 << "a\nb\nc" + s1.close + lines = [] + ret = s2.each_line(chomp: true) { |line| lines << line } + assert_same(s2, ret) + assert_equal(["a", "b", "c"], lines) + lines = [] + s2.each_line { |line| lines << line } + assert_equal([], lines) + } + end + + def test_each_line_enumerator + ssl_pair {|s1, s2| + s1 << "a\nb\nc" + s1.close + assert_equal(["a", "b", "c"], s2.each_line(chomp: true).to_a) + assert_equal([], s2.each_line.to_a) + } + end + def test_readpartial ssl_pair {|s1, s2| s2.write "a\nbcd" @@ -243,6 +375,7 @@ def test_readall def test_readline ssl_pair {|s1, s2| s2.close + assert_equal("", s1.readline(0)) assert_raise(EOFError) { s1.readline } } end From 88945ff1545ad25d6043420a3ce6c2b46d419ce4 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 29 Dec 2025 02:07:57 +0900 Subject: [PATCH 5/9] ssl: align #write and #<< with IO IO#write and IO#<< convert the arguments to String with #to_s. OpenSSL::Buffering#write also documented the intended behavior, but the implementation does not actually convert. Fix this. OpenSSL::Buffering#puts and #print already correctly call #to_s. --- lib/openssl/buffering.rb | 3 ++- test/openssl/test_pair.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index 45ae6ab38..de76c3717 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -463,6 +463,7 @@ def do_write(s) def write(*s) s.inject(0) do |written, str| + str = str.to_s do_write(str) written + str.bytesize end @@ -515,7 +516,7 @@ def write_nonblock(s, exception: true) # +.to_s+ method. def <<(s) - do_write(s) + do_write(s.to_s) self end diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index fca42319a..ecb6428cf 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -585,6 +585,16 @@ def test_write_nonblock_retry } end + def test_write_ltlt_convert_to_s + ssl_pair {|s1, s2| + def (obj = Object.new).to_s() "obj" end + assert_equal(6, s1.write("str", obj)) + assert_same(s1, s1 << obj) + s1.close + assert_equal("strobjobj", s2.read) + } + end + def test_write_zero ssl_pair {|s1, s2| assert_equal 0, s2.write_nonblock('', exception: false) From c91cdadb1cf998bdc6d6284e179f2c4336561675 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 8 Jan 2026 19:48:03 +0900 Subject: [PATCH 6/9] ssl: align #syswrite and #syswrite_nonblock with IO IO#syswrite converts the argument with #to_s rather than #to_str. --- ext/openssl/ossl_ssl.c | 2 +- test/openssl/test_pair.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 91aadf0b6..45b836441 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -2211,7 +2211,7 @@ ossl_ssl_write_internal_safe(VALUE _args) static VALUE ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) { - StringValue(str); + str = rb_obj_as_string(str); int frozen = RB_OBJ_FROZEN(str); if (!frozen) { rb_str_locktmp(str); diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index ecb6428cf..763fba777 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -422,7 +422,7 @@ def test_sysread_and_syswrite assert_equal(str, buf) obj = Object.new - obj.define_singleton_method(:to_str) { str } + obj.define_singleton_method(:to_s) { str } s1.syswrite(obj) assert_equal(str, s2.sysread(str.bytesize)) } From 874f39fe77f0802752fa72ec681075168a45e77d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Wed, 24 Dec 2025 03:15:17 +0900 Subject: [PATCH 7/9] ssl: align #puts with IO Handle arrays and nested arrays in OpenSSL::Buffering#puts in a similar fashion as IO#puts. --- lib/openssl/buffering.rb | 23 ++++++++++++++++++----- test/openssl/test_pair.rb | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index de76c3717..b65061abf 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -521,18 +521,31 @@ def <<(s) end ## - # Writes _args_ to the stream along with a record separator. + # Writes _args_ to the stream along with a newline. # # See IO#puts for full details. def puts(*args) - s = Buffer.new if args.empty? - s.append_as_bytes("\n") + do_write("\n") + return nil end + s = Buffer.new args.each{|arg| - s.append_as_bytes(arg.to_s) - s.sub!(/(? Date: Wed, 24 Dec 2025 03:43:50 +0900 Subject: [PATCH 8/9] ssl: align #putc with IO OpenSSL::Buffering already supports other character IO methods such as #getc, #ungetc, or #readchar. Add the missing method. --- lib/openssl/buffering.rb | 15 +++++++++++++++ test/openssl/test_pair.rb | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index b65061abf..dc4ff3d16 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -551,6 +551,21 @@ def puts(*args) nil end + ## + # Writes character _ch_ to the stream. + # + # See IO#putc for full details. + + def putc(ch) + if String === ch + # Can be empty or more than 1 byte + do_write(ch[0, 1]) + else + do_write((ch & 0xff).chr) + end + ch + end + ## # Writes _args_ to the stream. # diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index 1dd84f270..7e6b4cfe1 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -410,6 +410,20 @@ def test_puts_empty } end + def test_putc + ssl_pair {|s1, s2| + ret = s1.putc("a") + assert_equal("a", ret) + s1.putc(98) + s1.putc(99 + 256) + s1.putc(100 - 256) + s1.putc("") + s1.putc("\u3042") # \xe3\x81\x82 + s1.close + assert_equal("abcd\u3042".b, s2.read) + } + end + def test_multibyte_read_write # German a umlaut auml = [%w{ C3 A4 }.join('')].pack('H*') From bc43561de0ac8db7afc35c1a361299c34ecfdb1d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 30 Dec 2025 18:08:57 +0900 Subject: [PATCH 9/9] ssl: run test_pair.rb tests against core IO objects Run assertions in test_pair.rb against a pair of real socket objects to avoid accidentally introducing incompatible changes in SSLSocket or OpenSSL::Buffering. --- test/openssl/test_pair.rb | 117 ++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index 7e6b4cfe1..fa3504fa1 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -4,44 +4,6 @@ return unless defined?(OpenSSL::SSL) -module OpenSSL::SSLPair - def ssl_pair - svr_dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") - ee_exts = [ - ["keyUsage", "keyEncipherment,digitalSignature", true], - ] - svr_key = OpenSSL::TestUtils::Fixtures.pkey("rsa-1") - svr_cert = issue_cert(svr_dn, svr_key, 1, ee_exts, nil, nil) - - host = "127.0.0.1" - svr = TCPServer.new(host, 0) - svr.setsockopt(:TCP, :NODELAY, 1) - port = svr.connect_address.ip_port - - tcps = nil - th = Thread.new { - tcps = svr.accept - sctx = OpenSSL::SSL::SSLContext.new - sctx.add_certificate(svr_cert, svr_key) - ssl = OpenSSL::SSL::SSLSocket.new(tcps, sctx) - ssl.accept - ssl - } - - tcpc = TCPSocket.new(host, port) - tcpc.setsockopt(:TCP, :NODELAY, 1) - c = OpenSSL::SSL::SSLSocket.new(tcpc) - c.connect - s = th.value - - yield c, s - ensure - tcpc&.close - tcps&.close - svr&.close - end -end - module OpenSSL::TestPairM def test_getc ssl_pair {|s1, s2| @@ -466,15 +428,12 @@ def test_sysread_and_syswrite def test_read_nonblock ssl_pair {|s1, s2| - err = nil - assert_raise(OpenSSL::SSL::SSLErrorWaitReadable) { - begin - s2.read_nonblock(10) - ensure - err = $! - end + err = assert_raise(IO::WaitReadable) { + s2.read_nonblock(10) } - assert_kind_of(IO::WaitReadable, err) + if s2.is_a?(OpenSSL::SSL::SSLSocket) + assert_instance_of(OpenSSL::SSL::SSLErrorWaitReadable, err) + end s1.write "abc\ndef\n" IO.select([s2]) assert_equal("ab", s2.read_nonblock(2)) @@ -595,14 +554,12 @@ def test_write_nonblock_with_buffered_data_no_exceptions def test_write_nonblock_retry ssl_pair {|s1, s2| # fill up a socket so we hit EAGAIN - written = String.new n = 0 buf = 'a' * 4099 case ret = s1.write_nonblock(buf, exception: false) when :wait_readable then break when :wait_writable then break when Integer - written << buf n += ret exp = buf.bytesize if ret != exp @@ -613,7 +570,7 @@ def test_write_nonblock_retry # make more space for subsequent write: readed = s2.read(n) - assert_equal written, readed + assert_equal "a"*n, readed # this fails if SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER is missing: buf2 = Marshal.load(Marshal.dump(buf)) @@ -677,7 +634,67 @@ def test_close_write end class OpenSSL::TestSSLPair < OpenSSL::TestCase - include OpenSSL::SSLPair include OpenSSL::TestPairM include OpenSSL::TestEOF + + def ssl_pair + svr_dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") + ee_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ] + svr_key = OpenSSL::TestUtils::Fixtures.pkey("rsa-1") + svr_cert = issue_cert(svr_dn, svr_key, 1, ee_exts, nil, nil) + + host = "127.0.0.1" + svr = TCPServer.new(host, 0) + svr.setsockopt(:TCP, :NODELAY, 1) + port = svr.connect_address.ip_port + + tcps = nil + th = Thread.new { + tcps = svr.accept + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(svr_cert, svr_key) + ssl = OpenSSL::SSL::SSLSocket.new(tcps, sctx) + ssl.accept + ssl + } + + tcpc = TCPSocket.new(host, port) + tcpc.setsockopt(:TCP, :NODELAY, 1) + c = OpenSSL::SSL::SSLSocket.new(tcpc) + c.connect + s = th.value + + yield c, s + ensure + tcpc&.close + tcps&.close + svr&.close + end +end + +class OpenSSL::TestSocketPair < OpenSSL::TestCase + include OpenSSL::TestPairM + include OpenSSL::TestEOF + + def ssl_pair + host = "127.0.0.1" + svr = TCPServer.new(host, 0) + svr.setsockopt(:TCP, :NODELAY, 1) + port = svr.connect_address.ip_port + + tcps = nil + th = Thread.new { tcps = svr.accept } + + tcpc = TCPSocket.new(host, port) + tcpc.setsockopt(:TCP, :NODELAY, 1) + th.join + + yield tcpc, tcps + ensure + tcpc&.close + tcps&.close + svr&.close + end end