diff --git a/ChangeLog b/ChangeLog index 22f6812540..21414ece3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +Sun Mar 6 06:34:31 2005 GOTOU Yuuzou + + * ext/openssl/ossl_ssl.c (ossl_start_ssl): should wait for that + the underlying IO become readable or writable if the error was + SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. [ruby-dev:25795] + + * ext/openssl/ossl_ssl.c (ossl_ssl_read, ossl_ssl_write): ditto. + + * ext/openssl/lib/openssl/buffering.rb + (Buffering#consume_rbuf): pointless eof flag resetting is deleted. + (Buffering#read): should return an empty string if the specified + size is zero. + (Buffering#readpartial): new method. + (Buffering#readline): fix typo. + (Buffering#getc): return the first character of string correctly. + (Buffering#readchar): fix typo. + (Buffering#eof?): should read again it the input buffer is empty. + (Buffering#do_write): should rescue Errno::EAGAIN. + (Buffering#puts): use "\n" as the output field separator. + + * ext/openssl/extconf.rb: get rid of GNUmakefile generation. + + * text/openssl/test_pair.rb: test for IO like methods. + + * test/ruby/ut_eof.rb: test about empty file. + Sat Mar 5 17:48:31 2005 Hirokazu Yamamoto * dir.c (rb_glob): fixed mismatch of argument. diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index d587116c82..4517ec82fb 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -113,33 +113,5 @@ have_struct_member("EVP_CIPHER_CTX", "engine", "openssl/evp.h") have_struct_member("X509_ATTRIBUTE", "single", "openssl/x509.h") message "=== Checking done. ===\n" -$distcleanfiles << "GNUmakefile" << "dep" create_makefile("openssl") -if /gcc/ =~ CONFIG["CC"] - File.open("GNUmakefile", "w") {|f| - f.print < dep - -include dep -EOD - } -end message "Done.\n" diff --git a/ext/openssl/lib/openssl/buffering.rb b/ext/openssl/lib/openssl/buffering.rb index 7ad12d7203..39c442bfd1 100644 --- a/ext/openssl/lib/openssl/buffering.rb +++ b/ext/openssl/lib/openssl/buffering.rb @@ -41,7 +41,6 @@ module Buffering def consume_rbuff(size=nil) if @rbuffer.size == 0 - @eof = nil nil else size = @rbuffer.size unless size @@ -54,6 +53,14 @@ module Buffering public def read(size=nil, buf=nil) + if size == 0 + if buf + buf.clear + return buf + else + return "" + end + end fill_rbuff unless defined? @rbuffer @eof ||= nil until @eof @@ -68,6 +75,31 @@ module Buffering (size && ret.empty?) ? nil : ret end + def readpartial(maxlen, buf=nil) + if maxlen == 0 + if buf + buf.clear + return buf + else + return "" + end + end + if !defined?(@rbuffer) || @rbuffer.size == 0 + begin + return sysread(maxlen, buf) + rescue Errno::EAGAIN + retry + end + end + ret = consume_rbuff(maxlen) + if buf + buf.replace(ret) + ret = buf + end + raise EOFError if ret.empty? + ret + end + def gets(eol=$/) fill_rbuff unless defined? @rbuffer idx = @rbuffer.index(eol) @@ -101,13 +133,13 @@ module Buffering end def readline(eol=$/) - raise EOFErorr if eof? + raise EOFError if eof? gets(eol) end def getc c = read(1) - c ? c.to_i : nil + c ? c[0] : nil end def each_byte @@ -117,7 +149,7 @@ module Buffering end def readchar - raise EOFErorr if eof? + raise EOFError if eof? getc end @@ -127,6 +159,7 @@ module Buffering def eof? @eof ||= nil + fill_rbuff if !@eof && (!defined?(@rbuffer) || @rbuffer.size == 0) @eof && @rbuffer.size == 0 end alias eof eof? @@ -144,7 +177,12 @@ module Buffering remain = idx ? idx + $/.size : @wbuffer.length nwritten = 0 while remain > 0 - nwrote = syswrite(@wbuffer[nwritten,remain]) + str = @wbuffer[nwritten,remain] + begin + nwrote = syswrite(str) + rescue Errno::EAGAIN + retry + end remain -= nwrote nwritten += nwrote end @@ -166,10 +204,13 @@ module Buffering def puts(*args) s = "" + if args.empty? + s << "\n" + end args.each{|arg| s << arg.to_s - unless /#{$/}\z/o =~ s - s << $/ + if $/ && /\n\z/ !~ s + s << "\n" end } do_write(s) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index e85430e859..323dc97f83 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -433,52 +433,49 @@ ossl_ssl_setup(VALUE self) return Qtrue; } -static void -ossl_start_ssl(SSL *ssl, int (*func)()) +static VALUE +ossl_start_ssl(VALUE self, int (*func)()) { + SSL *ssl; + OpenFile *fptr; + VALUE cb; int ret; + Data_Get_Struct(self, SSL, ssl); + GetOpenFile(ossl_ssl_get_io(self), fptr); + cb = ossl_sslctx_get_verify_cb(ossl_ssl_get_ctx(self)); + SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)cb); for(;;){ if((ret = func(ssl)) > 0) break; switch(SSL_get_error(ssl, ret)){ case SSL_ERROR_WANT_WRITE: + rb_io_wait_writable(fptr->fd); + continue; case SSL_ERROR_WANT_READ: - rb_thread_schedule(); - continue; + rb_io_wait_readable(fptr->fd); + continue; + case SSL_ERROR_SYSCALL: + rb_sys_fail(0); default: ossl_raise(eSSLError, NULL); } } + + return self; } static VALUE ossl_ssl_connect(VALUE self) { - SSL *ssl; - VALUE cb; - ossl_ssl_setup(self); - Data_Get_Struct(self, SSL, ssl); - cb = ossl_sslctx_get_verify_cb(ossl_ssl_get_ctx(self)); - SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)cb); - ossl_start_ssl(ssl, SSL_connect); - - return self; + return ossl_start_ssl(self, SSL_connect); } static VALUE ossl_ssl_accept(VALUE self) { - SSL *ssl; - VALUE cb; - ossl_ssl_setup(self); - Data_Get_Struct(self, SSL, ssl); - cb = ossl_sslctx_get_verify_cb(ossl_ssl_get_ctx(self)); - SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)cb); - ossl_start_ssl(ssl, SSL_accept); - - return self; + return ossl_start_ssl(self, SSL_accept); } static VALUE @@ -512,8 +509,10 @@ ossl_ssl_read(int argc, VALUE *argv, VALUE self) case SSL_ERROR_ZERO_RETURN: rb_eof_error(); case SSL_ERROR_WANT_WRITE: + rb_io_wait_writable(fptr->fd); + continue; case SSL_ERROR_WANT_READ: - rb_thread_schedule(); + rb_io_wait_readable(fptr->fd); continue; case SSL_ERROR_SYSCALL: if(ERR_peek_error() == 0 && nread == 0) rb_eof_error(); @@ -542,9 +541,11 @@ ossl_ssl_write(VALUE self, VALUE str) { SSL *ssl; int nwrite = 0; + OpenFile *fptr; StringValue(str); Data_Get_Struct(self, SSL, ssl); + GetOpenFile(ossl_ssl_get_io(self), fptr); if (ssl) { for (;;){ @@ -553,11 +554,13 @@ ossl_ssl_write(VALUE self, VALUE str) case SSL_ERROR_NONE: goto end; case SSL_ERROR_WANT_WRITE: + rb_io_wait_writable(fptr->fd); + continue; case SSL_ERROR_WANT_READ: - rb_thread_schedule(); - continue; + rb_io_wait_readable(fptr->fd); + continue; case SSL_ERROR_SYSCALL: - rb_eof_error(); + rb_sys_fail(0); default: ossl_raise(eSSLError, "SSL_write:"); } diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb new file mode 100644 index 0000000000..7dd658b825 --- /dev/null +++ b/test/openssl/test_pair.rb @@ -0,0 +1,332 @@ +begin + require "openssl" +rescue LoadError +end +require 'test/unit' + +if defined?(OpenSSL) + +require 'socket' +dir = File.expand_path(__FILE__) +2.times {dir = File.dirname(dir)} +$:.replace([File.join(dir, "ruby")] | $:) +require 'ut_eof' + +module SSLPair + def server + host = "127.0.0.1" + port = 0 + key = OpenSSL::PKey::RSA.new(512) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 0 + name = OpenSSL::X509::Name.new([["C","JP"],["O","TEST"],["CN","localhost"]]) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + 3600 + cert.public_key = key.public_key + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE"), + ef.create_extension("subjectKeyIdentifier","hash"), + ef.create_extension("extendedKeyUsage","serverAuth"), + ef.create_extension("keyUsage", + "keyEncipherment,dataEncipherment,digitalSignature") + ] + ef.issuer_certificate = cert + cert.add_extension ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + cert.sign(key, OpenSSL::Digest::SHA1.new) + ctx = OpenSSL::SSL::SSLContext.new() + ctx.key = key + ctx.cert = cert + tcps = TCPServer.new(host, port) + ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) + return ssls + end + + def client(port) + host = "127.0.0.1" + ctx = OpenSSL::SSL::SSLContext.new() + s = TCPSocket.new(host, port) + ssl = OpenSSL::SSL::SSLSocket.new(s, ctx) + ssl.connect + ssl.sync_close = true + ssl + end + + def ssl_pair + ssls = server + th = Thread.new { + ns = ssls.accept + ssls.close + ns + } + port = ssls.to_io.addr[1] + c = client(port) + s = th.value + if block_given? + begin + yield c, s + ensure + c.close unless c.closed? + s.close unless s.closed? + end + else + return c, s + end + end +end + +class OpenSSL::TestEOF1 < Test::Unit::TestCase + include TestEOF + include SSLPair + + def open_file(content) + s1, s2 = ssl_pair + Thread.new { s2 << content; s2.close } + yield s1 + end +end + +class OpenSSL::TestEOF2 < Test::Unit::TestCase + include TestEOF + include SSLPair + + def open_file(content) + s1, s2 = ssl_pair + Thread.new { s1 << content; s1.close } + yield s2 + end +end + +class OpenSSL::TestPair < Test::Unit::TestCase + include SSLPair + + def test_getc + ssl_pair {|s1, s2| + s1 << "a" + assert_equal(?a, s2.getc) + } + end + + def test_readpartial + ssl_pair {|s1, s2| + s2.write "a\nbcd" + assert_equal("a\n", s1.gets) + assert_equal("bcd", s1.readpartial(10)) + s2.write "efg" + assert_equal("efg", s1.readpartial(10)) + s2.close + assert_raise(EOFError) { s1.readpartial(10) } + assert_raise(EOFError) { s1.readpartial(10) } + assert_equal("", s1.readpartial(0)) + } + end + + def test_readall + ssl_pair {|s1, s2| + s2.close + assert_equal("", s1.read) + } + end + + def test_readline + ssl_pair {|s1, s2| + s2.close + assert_raise(EOFError) { s1.readline } + } + end + + def test_puts_meta + ssl_pair {|s1, s2| + begin + old = $/ + $/ = '*' + s1.puts 'a' + ensure + $/ = old + end + s1.close + assert_equal("a\n", s2.read) + } + end + + def test_puts_empty + ssl_pair {|s1, s2| + s1.puts + s1.close + assert_equal("\n", s2.read) + } + end + +end + +end +begin + require "openssl" +rescue LoadError +end +require 'test/unit' + +if defined?(OpenSSL) + +require 'socket' +dir = File.expand_path(__FILE__) +2.times {dir = File.dirname(dir)} +$:.replace([File.join(dir, "ruby")] | $:) +require 'ut_eof' + +module SSLPair + def server + host = "127.0.0.1" + port = 0 + key = OpenSSL::PKey::RSA.new(512) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 0 + name = OpenSSL::X509::Name.new([["C","JP"],["O","TEST"],["CN","localhost"]]) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + 3600 + cert.public_key = key.public_key + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE"), + ef.create_extension("subjectKeyIdentifier","hash"), + ef.create_extension("extendedKeyUsage","serverAuth"), + ef.create_extension("keyUsage", + "keyEncipherment,dataEncipherment,digitalSignature") + ] + ef.issuer_certificate = cert + cert.add_extension ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + cert.sign(key, OpenSSL::Digest::SHA1.new) + ctx = OpenSSL::SSL::SSLContext.new() + ctx.key = key + ctx.cert = cert + tcps = TCPServer.new(host, port) + ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) + return ssls + end + + def client(port) + host = "127.0.0.1" + ctx = OpenSSL::SSL::SSLContext.new() + s = TCPSocket.new(host, port) + ssl = OpenSSL::SSL::SSLSocket.new(s, ctx) + ssl.connect + ssl.sync_close = true + ssl + end + + def ssl_pair + ssls = server + th = Thread.new { + ns = ssls.accept + ssls.close + ns + } + port = ssls.to_io.addr[1] + c = client(port) + s = th.value + if block_given? + begin + yield c, s + ensure + c.close unless c.closed? + s.close unless s.closed? + end + else + return c, s + end + end +end + +class OpenSSL::TestEOF1 < Test::Unit::TestCase + include TestEOF + include SSLPair + + def open_file(content) + s1, s2 = ssl_pair + Thread.new { s2 << content; s2.close } + yield s1 + end +end + +class OpenSSL::TestEOF2 < Test::Unit::TestCase + include TestEOF + include SSLPair + + def open_file(content) + s1, s2 = ssl_pair + Thread.new { s1 << content; s1.close } + yield s2 + end +end + +class OpenSSL::TestPair < Test::Unit::TestCase + include SSLPair + + def test_getc + ssl_pair {|s1, s2| + s1 << "a" + assert_equal(?a, s2.getc) + } + end + + def test_readpartial + ssl_pair {|s1, s2| + s2.write "a\nbcd" + assert_equal("a\n", s1.gets) + assert_equal("bcd", s1.readpartial(10)) + s2.write "efg" + assert_equal("efg", s1.readpartial(10)) + s2.close + assert_raise(EOFError) { s1.readpartial(10) } + assert_raise(EOFError) { s1.readpartial(10) } + assert_equal("", s1.readpartial(0)) + } + end + + def test_readall + ssl_pair {|s1, s2| + s2.close + assert_equal("", s1.read) + } + end + + def test_readline + ssl_pair {|s1, s2| + s2.close + assert_raise(EOFError) { s1.readline } + } + end + + def test_puts_meta + ssl_pair {|s1, s2| + begin + old = $/ + $/ = '*' + s1.puts 'a' + ensure + $/ = old + end + s1.close + assert_equal("a\n", s2.read) + } + end + + def test_puts_empty + ssl_pair {|s1, s2| + s1.puts + s1.close + assert_equal("\n", s2.read) + } + end + +end + +end diff --git a/test/ruby/ut_eof.rb b/test/ruby/ut_eof.rb index bf03771fc3..9aa020685b 100644 --- a/test/ruby/ut_eof.rb +++ b/test/ruby/ut_eof.rb @@ -83,6 +83,32 @@ module TestEOF } end + def test_eof_2 + open_file("") {|f| + assert_equal("", f.read) + assert(f.eof?) + } + end + + def test_eof_3 + open_file("") {|f| + assert(f.eof?) + } + end + + def test_eof_2 + open_file("") {|f| + assert_equal("", f.read) + assert(f.eof?) + } + end + + def test_eof_3 + open_file("") {|f| + assert(f.eof?) + } + end + module Seek def open_file_seek(content, pos) open_file(content) do |f|