* [PATCH] ssl: ensure rack.hijack users get "normal" IO methods
@ 2015-05-09 3:14 Eric Wong
0 siblings, 0 replies; only message in thread
From: Eric Wong @ 2015-05-09 3:14 UTC (permalink / raw)
To: yahns-public
We do not want rack.hijack users relying on kgio_* methods since
kgio is trying to make itself obsolete (as Ruby itself adopts
kgio features). This is a bit wonky since our common case tries
to minimize object allocation by only using the Kgio::Socket
derived class.
---
lib/yahns/openssl_client.rb | 25 ++++++++++++++++
test/helper.rb | 10 +++++++
test/test_rack_hijack.rb | 10 -------
test/test_ssl.rb | 72 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 107 insertions(+), 10 deletions(-)
diff --git a/lib/yahns/openssl_client.rb b/lib/yahns/openssl_client.rb
index 5842e97..ffa4b3e 100644
--- a/lib/yahns/openssl_client.rb
+++ b/lib/yahns/openssl_client.rb
@@ -8,6 +8,31 @@ require_relative 'sendfile_compat'
module Yahns::OpenSSLClient # :nodoc:
include Yahns::SendfileCompat
+ def self.included(cls)
+ # Forward these methods to OpenSSL::SSL::SSLSocket so hijackers
+ # can rely on stdlib methods instead of ugly kgio stuff that
+ # we hope to phase out.
+ # This is a bit weird, since OpenSSL::SSL::SSLSocket wraps
+ # our actual socket, too, so we must take care to not blindly
+ # use method_missing and cause infinite recursion
+ %w(sync= read write readpartial write_nonblock read_nonblock
+ print printf puts gets readlines readline getc
+ readchar ungetc eof eof? << flush
+ sysread syswrite).map!(&:to_sym).each do |m|
+ cls.__send__(:define_method, m) { |*a| @ssl.__send__(m, *a) }
+ end
+
+ # block captures, ugh, but nobody really uses them
+ %w(each each_line each_byte).map!(&:to_sym).each do |m|
+ cls.__send__(:define_method, m) { |*a, &b| @ssl.__send__(m, *a, &b) }
+ end
+ end
+
+ # this is special, called during IO initialization in Ruby
+ def sync
+ defined?(@ssl) ? @ssl.sync : super
+ end
+
def yahns_init_ssl(ssl_ctx)
@need_accept = true
@ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
diff --git a/test/helper.rb b/test/helper.rb
index 27adade..3e9f535 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -133,6 +133,16 @@ def require_exec(cmd)
false
end
+class DieIfUsed
+ def each
+ abort "body.each called after response hijack\n"
+ end
+
+ def close
+ abort "body.close called after response hijack\n"
+ end
+end
+
require 'yahns'
# needed for parallel (MT) tests)
diff --git a/test/test_rack_hijack.rb b/test/test_rack_hijack.rb
index 2cc6b2d..3e382eb 100644
--- a/test/test_rack_hijack.rb
+++ b/test/test_rack_hijack.rb
@@ -8,16 +8,6 @@ class TestRackHijack < Testcase
alias setup server_helper_setup
alias teardown server_helper_teardown
- class DieIfUsed
- def each
- abort "body.each called after response hijack\n"
- end
-
- def close
- abort "body.close called after response hijack\n"
- end
- end
-
HIJACK_APP = lambda { |env|
case env["PATH_INFO"]
when "/hijack_input"
diff --git a/test/test_ssl.rb b/test/test_ssl.rb
index 13c14f0..8f01ef7 100644
--- a/test/test_ssl.rb
+++ b/test/test_ssl.rb
@@ -71,6 +71,15 @@ AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC
end
end
client = ssl_client(host, port)
+ client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+ buf = ''
+ Timeout.timeout(60) do
+ buf << client.readpartial(111) until buf =~ /HI\z/
+ end
+ head, body = buf.split("\r\n\r\n", 2)
+ assert_equal "HI", body
+ assert_match %r{\AHTTP/1\.\d 200 OK\r\n}, head
+
client.write("GET / HTTP/1.0\r\n\r\n")
head, body = client.read.split("\r\n\r\n", 2)
assert_equal "HI", body
@@ -79,4 +88,67 @@ AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC
client.close if client
quit_wait(pid)
end
+
+ def test_ssl_hijack
+ err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
+ ctx = srv_ctx
+ pid = mkserver(cfg) do
+ cfg.instance_eval do
+ ru = lambda do |env|
+ io = env['rack.hijack'].call
+ Thread.new(io) do |s|
+ s.write "HTTP/1.1 201 Switching Protocols\r\n\r\n"
+ case req = s.gets
+ when "inspect\n"
+ s.puts(s.instance_variable_get(:@ssl).inspect)
+ when "remote_address\n"
+ s.puts(s.remote_address.inspect)
+ when "each\n"
+ line = ''
+ s.each do |l|
+ l.strip!
+ line << l
+ break if l == 'd'
+ end
+ s.puts line
+ when "missing\n"
+ begin
+ s.any_old_invalid_test_method
+ s.puts "FAIL"
+ rescue => e
+ s.puts "#{e.class}: #{e.message}"
+ end
+ when nil
+ s.close
+ else
+ p [ :ERR, req ]
+ end until s.closed?
+ end
+ [ 200, DieIfUsed, DieIfUsed ]
+ end
+ app(:rack, ru) { listen "#{host}:#{port}", ssl_ctx: ctx }
+ logger(Logger.new(err.path))
+ end
+ end
+ client = ssl_client(host, port)
+ client.write("GET / HTTP/1.0\r\n\r\n")
+
+ Timeout.timeout(60) do
+ assert_equal "HTTP/1.1 201 Switching Protocols\r\n", client.gets
+ assert_equal "\r\n", client.gets
+ client.puts "inspect"
+ assert_match %r{SSLSocket}, client.gets
+ client.puts "remote_address"
+ assert_equal client.to_io.local_address.inspect, client.gets.strip
+ client.puts "missing"
+ assert_match %r{NoMethodError}, client.gets
+
+ client.puts "each"
+ %w(a b c d).each { |x| client.puts(x) }
+ assert_equal "abcd", client.gets.strip
+ end
+ ensure
+ client.close if client
+ quit_wait(pid)
+ end
end if defined?(OpenSSL)
--
EW
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2015-05-09 3:14 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-05-09 3:14 [PATCH] ssl: ensure rack.hijack users get "normal" IO methods Eric Wong
Code repositories for project(s) associated with this public inbox
https://yhbt.net/yahns.git/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).