From e9e6bd2779fc2aeaa53bbf166b2fcdfe798d9668 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 4 Jul 2024 10:55:23 +0900 Subject: [PATCH 01/15] Extract HTTP Proxy tests from test_open-uri.rb --- test/open-uri/test_open-uri.rb | 164 ----------------------- test/open-uri/test_proxy.rb | 234 +++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 164 deletions(-) create mode 100644 test/open-uri/test_proxy.rb diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 30e3b5c..4711bfa 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -267,170 +267,6 @@ def test_non_http_proxy } end - def test_proxy - with_http {|srv, dr, url| - proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) - proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| - proxy_auth_log << req.request_line - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr - proxy_url = "http://#{proxy_host}:#{proxy_port}/" - begin - proxy_thread = proxy.start - srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) - URI.open("#{url}/proxy", :proxy=>proxy_url) {|f| - assert_equal("200", f.status[0]) - assert_equal("proxy", f.read) - } - assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear - URI.open("#{url}/proxy", :proxy=>URI(proxy_url)) {|f| - assert_equal("200", f.status[0]) - assert_equal("proxy", f.read) - } - assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear - URI.open("#{url}/proxy", :proxy=>nil) {|f| - assert_equal("200", f.status[0]) - assert_equal("proxy", f.read) - } - assert_equal("", proxy_auth_log); proxy_auth_log.clear - assert_raise(ArgumentError) { - URI.open("#{url}/proxy", :proxy=>:invalid) {} - } - assert_equal("", proxy_auth_log); proxy_auth_log.clear - with_env("http_proxy"=>proxy_url) { - # should not use proxy for 127.0.0.0/8. - URI.open("#{url}/proxy") {|f| - assert_equal("200", f.status[0]) - assert_equal("proxy", f.read) - } - } - assert_equal("", proxy_auth_log); proxy_auth_log.clear - ensure - proxy.shutdown - proxy_thread.join - end - assert_equal("", proxy_log.string) - } - end - - def test_proxy_http_basic_authentication_failure - with_http {|srv, dr, url| - proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) - proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| - proxy_auth_log << req.request_line - if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" - raise WEBrick::HTTPStatus::ProxyAuthenticationRequired - end - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr - proxy_url = "http://#{proxy_host}:#{proxy_port}/" - begin - th = proxy.start - srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) - exc = assert_raise(OpenURI::HTTPError) { URI.open("#{url}/proxy", :proxy=>proxy_url) {} } - assert_equal("407", exc.io.status[0]) - assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear - ensure - proxy.shutdown - th.join - end - assert_match(/ERROR WEBrick::HTTPStatus::ProxyAuthenticationRequired/, proxy_log.string) - } - end - - def test_proxy_http_basic_authentication_success - with_http {|srv, dr, url| - proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) - proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| - proxy_auth_log << req.request_line - if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" - raise WEBrick::HTTPStatus::ProxyAuthenticationRequired - end - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr - proxy_url = "http://#{proxy_host}:#{proxy_port}/" - begin - th = proxy.start - srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) - URI.open("#{url}/proxy", - :proxy_http_basic_authentication=>[proxy_url, "user", "pass"]) {|f| - assert_equal("200", f.status[0]) - assert_equal("proxy", f.read) - } - assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear - assert_raise(ArgumentError) { - URI.open("#{url}/proxy", - :proxy_http_basic_authentication=>[true, "user", "pass"]) {} - } - assert_equal("", proxy_auth_log); proxy_auth_log.clear - ensure - proxy.shutdown - th.join - end - assert_equal("", proxy_log.string) - } - end - - def test_authenticated_proxy_http_basic_authentication_success - with_http {|srv, dr, url| - proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) - proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| - proxy_auth_log << req.request_line - if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" - raise WEBrick::HTTPStatus::ProxyAuthenticationRequired - end - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr - proxy_url = "http://user:pass@#{proxy_host}:#{proxy_port}/" - begin - th = proxy.start - srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) - URI.open("#{url}/proxy", :proxy => proxy_url) {|f| - assert_equal("200", f.status[0]) - assert_equal("proxy", f.read) - } - assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear - assert_equal("", proxy_auth_log); proxy_auth_log.clear - ensure - proxy.shutdown - th.join - end - assert_equal("", proxy_log.string) - } - end - def test_redirect with_http {|srv, dr, url| srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } diff --git a/test/open-uri/test_proxy.rb b/test/open-uri/test_proxy.rb new file mode 100644 index 0000000..486603a --- /dev/null +++ b/test/open-uri/test_proxy.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true +require 'test/unit' +require 'open-uri' +require 'webrick' +require 'webrick/httpproxy' +begin + require 'zlib' +rescue LoadError +end + +class TestOpenURIProxy < Test::Unit::TestCase + + NullLog = Object.new + def NullLog.<<(arg) + #puts arg if / INFO / !~ arg + end + + def with_http(log_tester=lambda {|log| assert_equal([], log) }) + log = [] + logger = WEBrick::Log.new(log, WEBrick::BasicLog::WARN) + Dir.mktmpdir {|dr| + srv = WEBrick::HTTPServer.new({ + :DocumentRoot => dr, + :ServerType => Thread, + :Logger => logger, + :AccessLog => [[NullLog, ""]], + :BindAddress => '127.0.0.1', + :Port => 0}) + _, port, _, host = srv.listeners[0].addr + server_thread = srv.start + server_thread2 = Thread.new { + server_thread.join + if log_tester + log_tester.call(log) + end + } + client_thread = Thread.new { + begin + yield srv, dr, "http://#{host}:#{port}", server_thread, log + ensure + srv.shutdown + end + } + assert_join_threads([client_thread, server_thread2]) + } + ensure + WEBrick::Utils::TimeoutHandler.terminate + end + + def with_env(h) + begin + old = {} + h.each_key {|k| old[k] = ENV[k] } + h.each {|k, v| ENV[k] = v } + yield + ensure + h.each_key {|k| ENV[k] = old[k] } + end + end + + def setup + @proxies = %w[http_proxy HTTP_PROXY ftp_proxy FTP_PROXY no_proxy] + @old_proxies = @proxies.map {|k| ENV[k] } + @proxies.each {|k| ENV[k] = nil } + end + + def teardown + @proxies.each_with_index {|k, i| ENV[k] = @old_proxies[i] } + end + + def test_proxy + with_http {|srv, dr, url| + proxy_log = StringIO.new(''.dup) + proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) + proxy_auth_log = ''.dup + proxy = WEBrick::HTTPProxyServer.new({ + :ServerType => Thread, + :Logger => proxy_logger, + :AccessLog => [[NullLog, ""]], + :ProxyAuthProc => lambda {|req, res| + proxy_auth_log << req.request_line + }, + :BindAddress => '127.0.0.1', + :Port => 0}) + _, proxy_port, _, proxy_host = proxy.listeners[0].addr + proxy_url = "http://#{proxy_host}:#{proxy_port}/" + begin + proxy_thread = proxy.start + srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) + URI.open("#{url}/proxy", :proxy=>proxy_url) {|f| + assert_equal("200", f.status[0]) + assert_equal("proxy", f.read) + } + assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear + URI.open("#{url}/proxy", :proxy=>URI(proxy_url)) {|f| + assert_equal("200", f.status[0]) + assert_equal("proxy", f.read) + } + assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear + URI.open("#{url}/proxy", :proxy=>nil) {|f| + assert_equal("200", f.status[0]) + assert_equal("proxy", f.read) + } + assert_equal("", proxy_auth_log); proxy_auth_log.clear + assert_raise(ArgumentError) { + URI.open("#{url}/proxy", :proxy=>:invalid) {} + } + assert_equal("", proxy_auth_log); proxy_auth_log.clear + with_env("http_proxy"=>proxy_url) { + # should not use proxy for 127.0.0.0/8. + URI.open("#{url}/proxy") {|f| + assert_equal("200", f.status[0]) + assert_equal("proxy", f.read) + } + } + assert_equal("", proxy_auth_log); proxy_auth_log.clear + ensure + proxy.shutdown + proxy_thread.join + end + assert_equal("", proxy_log.string) + } + end + + def test_proxy_http_basic_authentication_failure + with_http {|srv, dr, url| + proxy_log = StringIO.new(''.dup) + proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) + proxy_auth_log = ''.dup + proxy = WEBrick::HTTPProxyServer.new({ + :ServerType => Thread, + :Logger => proxy_logger, + :AccessLog => [[NullLog, ""]], + :ProxyAuthProc => lambda {|req, res| + proxy_auth_log << req.request_line + if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" + raise WEBrick::HTTPStatus::ProxyAuthenticationRequired + end + }, + :BindAddress => '127.0.0.1', + :Port => 0}) + _, proxy_port, _, proxy_host = proxy.listeners[0].addr + proxy_url = "http://#{proxy_host}:#{proxy_port}/" + begin + th = proxy.start + srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) + exc = assert_raise(OpenURI::HTTPError) { URI.open("#{url}/proxy", :proxy=>proxy_url) {} } + assert_equal("407", exc.io.status[0]) + assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear + ensure + proxy.shutdown + th.join + end + assert_match(/ERROR WEBrick::HTTPStatus::ProxyAuthenticationRequired/, proxy_log.string) + } + end + + def test_proxy_http_basic_authentication_success + with_http {|srv, dr, url| + proxy_log = StringIO.new(''.dup) + proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) + proxy_auth_log = ''.dup + proxy = WEBrick::HTTPProxyServer.new({ + :ServerType => Thread, + :Logger => proxy_logger, + :AccessLog => [[NullLog, ""]], + :ProxyAuthProc => lambda {|req, res| + proxy_auth_log << req.request_line + if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" + raise WEBrick::HTTPStatus::ProxyAuthenticationRequired + end + }, + :BindAddress => '127.0.0.1', + :Port => 0}) + _, proxy_port, _, proxy_host = proxy.listeners[0].addr + proxy_url = "http://#{proxy_host}:#{proxy_port}/" + begin + th = proxy.start + srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) + URI.open("#{url}/proxy", + :proxy_http_basic_authentication=>[proxy_url, "user", "pass"]) {|f| + assert_equal("200", f.status[0]) + assert_equal("proxy", f.read) + } + assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear + assert_raise(ArgumentError) { + URI.open("#{url}/proxy", + :proxy_http_basic_authentication=>[true, "user", "pass"]) {} + } + assert_equal("", proxy_auth_log); proxy_auth_log.clear + ensure + proxy.shutdown + th.join + end + assert_equal("", proxy_log.string) + } + end + + def test_authenticated_proxy_http_basic_authentication_success + with_http {|srv, dr, url| + proxy_log = StringIO.new(''.dup) + proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) + proxy_auth_log = ''.dup + proxy = WEBrick::HTTPProxyServer.new({ + :ServerType => Thread, + :Logger => proxy_logger, + :AccessLog => [[NullLog, ""]], + :ProxyAuthProc => lambda {|req, res| + proxy_auth_log << req.request_line + if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" + raise WEBrick::HTTPStatus::ProxyAuthenticationRequired + end + }, + :BindAddress => '127.0.0.1', + :Port => 0}) + _, proxy_port, _, proxy_host = proxy.listeners[0].addr + proxy_url = "http://user:pass@#{proxy_host}:#{proxy_port}/" + begin + th = proxy.start + srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) + URI.open("#{url}/proxy", :proxy => proxy_url) {|f| + assert_equal("200", f.status[0]) + assert_equal("proxy", f.read) + } + assert_match(/#{Regexp.quote url}/, proxy_auth_log); proxy_auth_log.clear + assert_equal("", proxy_auth_log); proxy_auth_log.clear + ensure + proxy.shutdown + th.join + end + assert_equal("", proxy_log.string) + } + end +end From 324111eb419fdcbd129bb8c76748306e8e70d4a6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 4 Jul 2024 11:02:13 +0900 Subject: [PATCH 02/15] Extract FTP tests from test_open-uri.rb --- test/open-uri/test_ftp.rb | 215 +++++++++++++++++++++++++++++++++ test/open-uri/test_open-uri.rb | 210 -------------------------------- 2 files changed, 215 insertions(+), 210 deletions(-) create mode 100644 test/open-uri/test_ftp.rb diff --git a/test/open-uri/test_ftp.rb b/test/open-uri/test_ftp.rb new file mode 100644 index 0000000..3a6cd14 --- /dev/null +++ b/test/open-uri/test_ftp.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true +require 'test/unit' +require 'open-uri' + +class TestOpenURIFtp < Test::Unit::TestCase + def with_env(h) + begin + old = {} + h.each_key {|k| old[k] = ENV[k] } + h.each {|k, v| ENV[k] = v } + yield + ensure + h.each_key {|k| ENV[k] = old[k] } + end + end + + begin + require 'net/ftp' + + def test_ftp_invalid_request + assert_raise(ArgumentError) { URI("ftp://127.0.0.1/").read } + assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Db").read } + assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Ab").read } + assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Db/f").read } + assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Ab/f").read } + assert_nothing_raised(URI::InvalidComponentError) { URI("ftp://127.0.0.1/d/f;type=x") } + end + + def test_ftp + TCPServer.open("127.0.0.1", 0) {|serv| + _, port, _, host = serv.addr + th = Thread.new { + s = serv.accept + begin + s.print "220 Test FTP Server\r\n" + assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n" + assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n" + assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n" + assert_equal("CWD foo\r\n", s.gets); s.print "250 CWD successful\r\n" + assert_equal("PASV\r\n", s.gets) + TCPServer.open("127.0.0.1", 0) {|data_serv| + _, data_serv_port, _, _ = data_serv.addr + hi = data_serv_port >> 8 + lo = data_serv_port & 0xff + s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n" + assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n" + data_sock = data_serv.accept + begin + data_sock << "content" + ensure + data_sock.close + end + s.print "226 transfer complete\r\n" + assert_nil(s.gets) + } + ensure + s.close if s + end + } + begin + content = URI("ftp://#{host}:#{port}/foo/bar").read + assert_equal("content", content) + ensure + Thread.kill(th) + th.join + end + } + end + + def test_ftp_active + TCPServer.open("127.0.0.1", 0) {|serv| + _, port, _, host = serv.addr + th = Thread.new { + s = serv.accept + begin + content = "content" + s.print "220 Test FTP Server\r\n" + assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n" + assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n" + assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n" + assert_equal("CWD foo\r\n", s.gets); s.print "250 CWD successful\r\n" + assert(m = /\APORT 127,0,0,1,(\d+),(\d+)\r\n\z/.match(s.gets)) + active_port = m[1].to_i << 8 | m[2].to_i + TCPSocket.open("127.0.0.1", active_port) {|data_sock| + s.print "200 data connection opened\r\n" + assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n" + begin + data_sock << content + ensure + data_sock.close + end + s.print "226 transfer complete\r\n" + assert_nil(s.gets) + } + ensure + s.close if s + end + } + begin + content = URI("ftp://#{host}:#{port}/foo/bar").read(:ftp_active_mode=>true) + assert_equal("content", content) + ensure + Thread.kill(th) + th.join + end + } + end + + def test_ftp_ascii + TCPServer.open("127.0.0.1", 0) {|serv| + _, port, _, host = serv.addr + th = Thread.new { + s = serv.accept + begin + content = "content" + s.print "220 Test FTP Server\r\n" + assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n" + assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n" + assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n" + assert_equal("CWD /foo\r\n", s.gets); s.print "250 CWD successful\r\n" + assert_equal("TYPE A\r\n", s.gets); s.print "200 type set to A\r\n" + assert_equal("SIZE bar\r\n", s.gets); s.print "213 #{content.bytesize}\r\n" + assert_equal("PASV\r\n", s.gets) + TCPServer.open("127.0.0.1", 0) {|data_serv| + _, data_serv_port, _, _ = data_serv.addr + hi = data_serv_port >> 8 + lo = data_serv_port & 0xff + s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n" + assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n" + data_sock = data_serv.accept + begin + data_sock << content + ensure + data_sock.close + end + s.print "226 transfer complete\r\n" + assert_nil(s.gets) + } + ensure + s.close if s + end + } + begin + length = [] + progress = [] + content = URI("ftp://#{host}:#{port}/%2Ffoo/b%61r;type=a").read( + :content_length_proc => lambda {|n| length << n }, + :progress_proc => lambda {|n| progress << n }) + assert_equal("content", content) + assert_equal([7], length) + assert_equal(7, progress.inject(&:+)) + ensure + Thread.kill(th) + th.join + end + } + end + rescue LoadError + # net-ftp is the bundled gems at Ruby 3.1 + end + + def test_ftp_over_http_proxy + TCPServer.open("127.0.0.1", 0) {|proxy_serv| + proxy_port = proxy_serv.addr[1] + th = Thread.new { + proxy_sock = proxy_serv.accept + begin + req = proxy_sock.gets("\r\n\r\n") + assert_match(%r{\AGET ftp://192.0.2.1/foo/bar }, req) + proxy_sock.print "HTTP/1.0 200 OK\r\n" + proxy_sock.print "Content-Length: 4\r\n\r\n" + proxy_sock.print "ab\r\n" + ensure + proxy_sock.close + end + } + begin + with_env('ftp_proxy'=>"http://127.0.0.1:#{proxy_port}") { + content = URI("ftp://192.0.2.1/foo/bar").read + assert_equal("ab\r\n", content) + } + ensure + Thread.kill(th) + th.join + end + } + end + + def test_ftp_over_http_proxy_auth + TCPServer.open("127.0.0.1", 0) {|proxy_serv| + proxy_port = proxy_serv.addr[1] + th = Thread.new { + proxy_sock = proxy_serv.accept + begin + req = proxy_sock.gets("\r\n\r\n") + assert_match(%r{\AGET ftp://192.0.2.1/foo/bar }, req) + assert_match(%r{Proxy-Authorization: Basic #{['proxy-user:proxy-password'].pack('m').chomp}\r\n}, req) + proxy_sock.print "HTTP/1.0 200 OK\r\n" + proxy_sock.print "Content-Length: 4\r\n\r\n" + proxy_sock.print "ab\r\n" + ensure + proxy_sock.close + end + } + begin + content = URI("ftp://192.0.2.1/foo/bar").read( + :proxy_http_basic_authentication => ["http://127.0.0.1:#{proxy_port}", "proxy-user", "proxy-password"]) + assert_equal("ab\r\n", content) + ensure + Thread.kill(th) + th.join + end + } + end +end diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 4711bfa..434d6b2 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -47,17 +47,6 @@ def with_http(log_tester=lambda {|log| assert_equal([], log) }) WEBrick::Utils::TimeoutHandler.terminate end - def with_env(h) - begin - old = {} - h.each_key {|k| old[k] = ENV[k] } - h.each {|k, v| ENV[k] = v } - yield - ensure - h.each_key {|k| ENV[k] = old[k] } - end - end - def setup @proxies = %w[http_proxy HTTP_PROXY ftp_proxy FTP_PROXY no_proxy] @old_proxies = @proxies.map {|k| ENV[k] } @@ -571,205 +560,6 @@ def test_multiple_cookies # 192.0.2.0/24 is TEST-NET. [RFC3330] - begin - require 'net/ftp' - - def test_ftp_invalid_request - assert_raise(ArgumentError) { URI("ftp://127.0.0.1/").read } - assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Db").read } - assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Ab").read } - assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Db/f").read } - assert_raise(ArgumentError) { URI("ftp://127.0.0.1/a%0Ab/f").read } - assert_nothing_raised(URI::InvalidComponentError) { URI("ftp://127.0.0.1/d/f;type=x") } - end - - def test_ftp - TCPServer.open("127.0.0.1", 0) {|serv| - _, port, _, host = serv.addr - th = Thread.new { - s = serv.accept - begin - s.print "220 Test FTP Server\r\n" - assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n" - assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n" - assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n" - assert_equal("CWD foo\r\n", s.gets); s.print "250 CWD successful\r\n" - assert_equal("PASV\r\n", s.gets) - TCPServer.open("127.0.0.1", 0) {|data_serv| - _, data_serv_port, _, _ = data_serv.addr - hi = data_serv_port >> 8 - lo = data_serv_port & 0xff - s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n" - assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n" - data_sock = data_serv.accept - begin - data_sock << "content" - ensure - data_sock.close - end - s.print "226 transfer complete\r\n" - assert_nil(s.gets) - } - ensure - s.close if s - end - } - begin - content = URI("ftp://#{host}:#{port}/foo/bar").read - assert_equal("content", content) - ensure - Thread.kill(th) - th.join - end - } - end - - def test_ftp_active - TCPServer.open("127.0.0.1", 0) {|serv| - _, port, _, host = serv.addr - th = Thread.new { - s = serv.accept - begin - content = "content" - s.print "220 Test FTP Server\r\n" - assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n" - assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n" - assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n" - assert_equal("CWD foo\r\n", s.gets); s.print "250 CWD successful\r\n" - assert(m = /\APORT 127,0,0,1,(\d+),(\d+)\r\n\z/.match(s.gets)) - active_port = m[1].to_i << 8 | m[2].to_i - TCPSocket.open("127.0.0.1", active_port) {|data_sock| - s.print "200 data connection opened\r\n" - assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n" - begin - data_sock << content - ensure - data_sock.close - end - s.print "226 transfer complete\r\n" - assert_nil(s.gets) - } - ensure - s.close if s - end - } - begin - content = URI("ftp://#{host}:#{port}/foo/bar").read(:ftp_active_mode=>true) - assert_equal("content", content) - ensure - Thread.kill(th) - th.join - end - } - end - - def test_ftp_ascii - TCPServer.open("127.0.0.1", 0) {|serv| - _, port, _, host = serv.addr - th = Thread.new { - s = serv.accept - begin - content = "content" - s.print "220 Test FTP Server\r\n" - assert_equal("USER anonymous\r\n", s.gets); s.print "331 name ok\r\n" - assert_match(/\APASS .*\r\n\z/, s.gets); s.print "230 logged in\r\n" - assert_equal("TYPE I\r\n", s.gets); s.print "200 type set to I\r\n" - assert_equal("CWD /foo\r\n", s.gets); s.print "250 CWD successful\r\n" - assert_equal("TYPE A\r\n", s.gets); s.print "200 type set to A\r\n" - assert_equal("SIZE bar\r\n", s.gets); s.print "213 #{content.bytesize}\r\n" - assert_equal("PASV\r\n", s.gets) - TCPServer.open("127.0.0.1", 0) {|data_serv| - _, data_serv_port, _, _ = data_serv.addr - hi = data_serv_port >> 8 - lo = data_serv_port & 0xff - s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n" - assert_equal("RETR bar\r\n", s.gets); s.print "150 file okay\r\n" - data_sock = data_serv.accept - begin - data_sock << content - ensure - data_sock.close - end - s.print "226 transfer complete\r\n" - assert_nil(s.gets) - } - ensure - s.close if s - end - } - begin - length = [] - progress = [] - content = URI("ftp://#{host}:#{port}/%2Ffoo/b%61r;type=a").read( - :content_length_proc => lambda {|n| length << n }, - :progress_proc => lambda {|n| progress << n }) - assert_equal("content", content) - assert_equal([7], length) - assert_equal(7, progress.inject(&:+)) - ensure - Thread.kill(th) - th.join - end - } - end - rescue LoadError - # net-ftp is the bundled gems at Ruby 3.1 - end - - def test_ftp_over_http_proxy - TCPServer.open("127.0.0.1", 0) {|proxy_serv| - proxy_port = proxy_serv.addr[1] - th = Thread.new { - proxy_sock = proxy_serv.accept - begin - req = proxy_sock.gets("\r\n\r\n") - assert_match(%r{\AGET ftp://192.0.2.1/foo/bar }, req) - proxy_sock.print "HTTP/1.0 200 OK\r\n" - proxy_sock.print "Content-Length: 4\r\n\r\n" - proxy_sock.print "ab\r\n" - ensure - proxy_sock.close - end - } - begin - with_env('ftp_proxy'=>"http://127.0.0.1:#{proxy_port}") { - content = URI("ftp://192.0.2.1/foo/bar").read - assert_equal("ab\r\n", content) - } - ensure - Thread.kill(th) - th.join - end - } - end - - def test_ftp_over_http_proxy_auth - TCPServer.open("127.0.0.1", 0) {|proxy_serv| - proxy_port = proxy_serv.addr[1] - th = Thread.new { - proxy_sock = proxy_serv.accept - begin - req = proxy_sock.gets("\r\n\r\n") - assert_match(%r{\AGET ftp://192.0.2.1/foo/bar }, req) - assert_match(%r{Proxy-Authorization: Basic #{['proxy-user:proxy-password'].pack('m').chomp}\r\n}, req) - proxy_sock.print "HTTP/1.0 200 OK\r\n" - proxy_sock.print "Content-Length: 4\r\n\r\n" - proxy_sock.print "ab\r\n" - ensure - proxy_sock.close - end - } - begin - content = URI("ftp://192.0.2.1/foo/bar").read( - :proxy_http_basic_authentication => ["http://127.0.0.1:#{proxy_port}", "proxy-user", "proxy-password"]) - assert_equal("ab\r\n", content) - ensure - Thread.kill(th) - th.join - end - } - end - def test_meta_init_doesnt_bump_global_constant_state omit "RubyVM.stat not defined" unless defined? RubyVM.stat omit unless RubyVM.stat.has_key?(:global_constant_state) From b2d7fc4ff363b75e167a8237caf7f0aa6202ceee Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 4 Jul 2024 11:28:00 +0900 Subject: [PATCH 03/15] Removed needless setup and teardown, tmpdir variables --- test/open-uri/test_open-uri.rb | 64 ++++++++++++++-------------------- test/open-uri/test_proxy.rb | 10 +++--- 2 files changed, 32 insertions(+), 42 deletions(-) diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 434d6b2..1014d3a 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -36,7 +36,7 @@ def with_http(log_tester=lambda {|log| assert_equal([], log) }) } client_thread = Thread.new { begin - yield srv, dr, "http://#{host}:#{port}", server_thread, log + yield srv, "http://#{host}:#{port}", server_thread, log ensure srv.shutdown end @@ -47,18 +47,8 @@ def with_http(log_tester=lambda {|log| assert_equal([], log) }) WEBrick::Utils::TimeoutHandler.terminate end - def setup - @proxies = %w[http_proxy HTTP_PROXY ftp_proxy FTP_PROXY no_proxy] - @old_proxies = @proxies.map {|k| ENV[k] } - @proxies.each {|k| ENV[k] = nil } - end - - def teardown - @proxies.each_with_index {|k, i| ENV[k] = @old_proxies[i] } - end - def test_200_uri_open - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/urifoo200", lambda { |req, res| res.body = "urifoo200" } ) URI.open("#{url}/urifoo200") {|f| assert_equal("200", f.status[0]) @@ -68,7 +58,7 @@ def test_200_uri_open end def test_200 - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/foo200", lambda { |req, res| res.body = "foo200" } ) URI.open("#{url}/foo200") {|f| assert_equal("200", f.status[0]) @@ -78,7 +68,7 @@ def test_200 end def test_200big - with_http {|srv, dr, url| + with_http {|srv, url| content = "foo200big"*10240 srv.mount_proc("/foo200big", lambda { |req, res| res.body = content } ) URI.open("#{url}/foo200big") {|f| @@ -93,14 +83,14 @@ def test_404 assert_equal(1, server_log.length) assert_match(%r{ERROR `/not-exist' not found}, server_log[0]) } - with_http(log_tester) {|srv, dr, url, server_thread, server_log| + with_http(log_tester) {|srv, url, server_thread, server_log| exc = assert_raise(OpenURI::HTTPError) { URI.open("#{url}/not-exist") {} } assert_equal("404", exc.io.status[0]) } end def test_open_uri - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/foo_ou", lambda { |req, res| res.body = "foo_ou" } ) u = URI("#{url}/foo_ou") URI.open(u) {|f| @@ -144,7 +134,7 @@ def test_open_timeout URI("http://example.com/").read(open_timeout: 0.000001) end if false # avoid external resources in tests - with_http {|srv, dr, url| + with_http {|srv, url| url += '/' srv.mount_proc('/', lambda { |_, res| res.body = 'hi' }) begin @@ -175,7 +165,7 @@ def o.open(foo: ) foo end end def test_mode - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/mode", lambda { |req, res| res.body = "mode" } ) URI.open("#{url}/mode", "r") {|f| assert_equal("200", f.status[0]) @@ -197,7 +187,7 @@ def test_mode end def test_without_block - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/without_block", lambda { |req, res| res.body = "without_block" } ) begin f = URI.open("#{url}/without_block") @@ -210,7 +200,7 @@ def test_without_block end def test_close_in_block_small - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/close200", lambda { |req, res| res.body = "close200" } ) assert_nothing_raised { URI.open("#{url}/close200") {|f| @@ -221,7 +211,7 @@ def test_close_in_block_small end def test_close_in_block_big - with_http {|srv, dr, url| + with_http {|srv, url| content = "close200big"*10240 srv.mount_proc("/close200big", lambda { |req, res| res.body = content } ) assert_nothing_raised { @@ -235,7 +225,7 @@ def test_close_in_block_big def test_header myheader1 = 'barrrr' myheader2 = nil - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/h/") {|req, res| myheader2 = req['myheader']; res.body = "foo" } URI.open("#{url}/h/", 'MyHeader'=>myheader1) {|f| assert_equal("foo", f.read) @@ -257,7 +247,7 @@ def test_non_http_proxy end def test_redirect - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } srv.mount_proc("/r2/") {|req, res| res.body = "r2" } srv.mount_proc("/to-file/") {|req, res| res.status = 301; res["location"] = "file:///foo" } @@ -271,7 +261,7 @@ def test_redirect end def test_redirect_loop - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r1"; res.body = "r2" } assert_raise(RuntimeError) { URI.open("#{url}/r1/") {} } @@ -351,7 +341,7 @@ def setup_redirect_auth(srv, url) end def test_redirect_auth_success - with_http {|srv, dr, url| + with_http {|srv, url| setup_redirect_auth(srv, url) URI.open("#{url}/r2/", :http_basic_authentication=>['user', 'pass']) {|f| assert_equal("r2", f.read) @@ -364,7 +354,7 @@ def test_redirect_auth_failure_r2 assert_equal(1, server_log.length) assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, server_log[0]) } - with_http(log_tester) {|srv, dr, url, server_thread, server_log| + with_http(log_tester) {|srv, url, server_thread, server_log| setup_redirect_auth(srv, url) exc = assert_raise(OpenURI::HTTPError) { URI.open("#{url}/r2/") {} } assert_equal("401", exc.io.status[0]) @@ -376,7 +366,7 @@ def test_redirect_auth_failure_r1 assert_equal(1, server_log.length) assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, server_log[0]) } - with_http(log_tester) {|srv, dr, url, server_thread, server_log| + with_http(log_tester) {|srv, url, server_thread, server_log| setup_redirect_auth(srv, url) exc = assert_raise(OpenURI::HTTPError) { URI.open("#{url}/r1/", :http_basic_authentication=>['user', 'pass']) {} } assert_equal("401", exc.io.status[0]) @@ -384,7 +374,7 @@ def test_redirect_auth_failure_r1 end def test_max_redirects_success - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r3"; res.body = "r2" } srv.mount_proc("/r3/") {|req, res| res.body = "r3" } @@ -393,7 +383,7 @@ def test_max_redirects_success end def test_max_redirects_too_many - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r3"; res.body = "r2" } srv.mount_proc("/r3/") {|req, res| res.body = "r3" } @@ -407,7 +397,7 @@ def test_userinfo end def test_progress - with_http {|srv, dr, url| + with_http {|srv, url| content = "a" * 100000 srv.mount_proc("/data/") {|req, res| res.body = content } length = [] @@ -427,7 +417,7 @@ def test_progress end def test_progress_chunked - with_http {|srv, dr, url| + with_http {|srv, url| content = "a" * 100000 srv.mount_proc("/data/") {|req, res| res.body = content; res.chunked = true } length = [] @@ -447,7 +437,7 @@ def test_progress_chunked end def test_uri_read - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/uriread", lambda { |req, res| res.body = "uriread" } ) data = URI("#{url}/uriread").read assert_equal("200", data.status[0]) @@ -456,7 +446,7 @@ def test_uri_read end def test_encoding - with_http {|srv, dr, url| + with_http {|srv, url| content_u8 = "\u3042" content_ej = "\xa2\xa4".dup.force_encoding("euc-jp") srv.mount_proc("/u8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset=utf-8' } @@ -500,7 +490,7 @@ def test_encoding end def test_quoted_attvalue - with_http {|srv, dr, url| + with_http {|srv, url| content_u8 = "\u3042" srv.mount_proc("/qu8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset="utf\-8"' } URI.open("#{url}/qu8/") {|f| @@ -512,7 +502,7 @@ def test_quoted_attvalue end def test_last_modified - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/data/") {|req, res| res.body = "foo"; res['last-modified'] = 'Fri, 07 Aug 2009 06:05:04 GMT' } URI.open("#{url}/data/") {|f| assert_equal("foo", f.read) @@ -522,7 +512,7 @@ def test_last_modified end def test_content_encoding - with_http {|srv, dr, url| + with_http {|srv, url| content = "abc" * 10000 Zlib::GzipWriter.wrap(StringIO.new(content_gz="".b)) {|z| z.write content } srv.mount_proc("/data/") {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip' } @@ -544,7 +534,7 @@ def test_content_encoding end if defined?(Zlib::GzipWriter) def test_multiple_cookies - with_http {|srv, dr, url| + with_http {|srv, url| srv.mount_proc("/mcookie/") {|req, res| res.cookies << "name1=value1; blabla" res.cookies << "name2=value2; blabla" diff --git a/test/open-uri/test_proxy.rb b/test/open-uri/test_proxy.rb index 486603a..2b503d9 100644 --- a/test/open-uri/test_proxy.rb +++ b/test/open-uri/test_proxy.rb @@ -36,7 +36,7 @@ def with_http(log_tester=lambda {|log| assert_equal([], log) }) } client_thread = Thread.new { begin - yield srv, dr, "http://#{host}:#{port}", server_thread, log + yield srv, "http://#{host}:#{port}", server_thread, log ensure srv.shutdown end @@ -69,7 +69,7 @@ def teardown end def test_proxy - with_http {|srv, dr, url| + with_http {|srv, url| proxy_log = StringIO.new(''.dup) proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup @@ -123,7 +123,7 @@ def test_proxy end def test_proxy_http_basic_authentication_failure - with_http {|srv, dr, url| + with_http {|srv, url| proxy_log = StringIO.new(''.dup) proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup @@ -156,7 +156,7 @@ def test_proxy_http_basic_authentication_failure end def test_proxy_http_basic_authentication_success - with_http {|srv, dr, url| + with_http {|srv, url| proxy_log = StringIO.new(''.dup) proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup @@ -197,7 +197,7 @@ def test_proxy_http_basic_authentication_success end def test_authenticated_proxy_http_basic_authentication_success - with_http {|srv, dr, url| + with_http {|srv, url| proxy_log = StringIO.new(''.dup) proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup From a2b1ebe46553f7663dfa9747a247c4bc8a1a6ef9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 4 Jul 2024 13:33:44 +0900 Subject: [PATCH 04/15] Added SimpleHTTPSServer by TCPServer --- test/open-uri/test_open-uri.rb | 263 +++++++++++++++++++++++++-------- 1 file changed, 199 insertions(+), 64 deletions(-) diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 1014d3a..5f8454d 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -1,50 +1,185 @@ # frozen_string_literal: true require 'test/unit' require 'open-uri' -require 'webrick' -require 'webrick/httpproxy' +require 'socket' +require 'base64' begin require 'zlib' rescue LoadError end -class TestOpenURI < Test::Unit::TestCase +class SimpleHTTPServer + def initialize(bind_addr, port, log) + @server = TCPServer.new(bind_addr, port) + @log = log + @procs = {} + end - NullLog = Object.new - def NullLog.<<(arg) - #puts arg if / INFO / !~ arg + def mount_proc(path, proc) + @procs[path] = proc end + def start + @thread = Thread.new do + loop do + client = @server.accept + handle_request(client) + client.close + end + end + end + + def shutdown + @thread.kill + @server.close + end + + private + + def handle_request(client) + request_line = client.gets + return if request_line.nil? + + method, path, _ = request_line.split + headers = {} + while (line = client.gets) && line != "\r\n" + key, value = line.split(": ", 2) + headers[key.downcase] = value.strip + end + + if @procs.key?(path) || @procs.key?("#{path}/") + proc = @procs[path] || @procs["#{path}/"] + req = Request.new(method, path, headers) + res = Response.new(client) + proc.call(req, res) + res.finish + else + @log << "ERROR `#{path}' not found" + client.print "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n" + end + rescue ::TestOpenURI::Unauthorized => e + @log << "ERROR Unauthorized" + client.print "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n" + end + + class Request + attr_reader :method, :path, :headers + def initialize(method, path, headers) + @method = method + @path = path + @headers = headers + parse_basic_auth + end + + def [](key) + @headers[key.downcase] + end + + def []=(key, value) + @headers[key.downcase] = value + end + + private + + def parse_basic_auth + auth = @headers['Authorization'] + return unless auth && auth.start_with?('Basic ') + + encoded_credentials = auth.split(' ', 2).last + decoded_credentials = Base64.decode64(encoded_credentials) + @username, @password = decoded_credentials.split(':', 2) + end + end + + class Response + attr_accessor :body, :headers, :status, :chunked, :cookies + def initialize(client) + @client = client + @body = "" + @headers = {} + @status = 200 + @chunked = false + @cookies = [] + end + + def [](key) + @headers[key.downcase] + end + + def []=(key, value) + @headers[key.downcase] = value + end + + def write_chunk(chunk) + return unless @chunked + @client.write("#{chunk.bytesize.to_s(16)}\r\n") + @client.write("#{chunk}\r\n") + end + + def finish + @client.write build_response_headers + if @chunked + write_chunk(@body) + @client.write "0\r\n\r\n" + else + @client.write @body + end + end + + private + + def build_response_headers + response = "HTTP/1.1 #{@status} #{status_message(@status)}\r\n" + if @chunked + @headers['Transfer-Encoding'] = 'chunked' + else + @headers['Content-Length'] = @body.bytesize.to_s + end + @headers.each do |key, value| + response << "#{key}: #{value}\r\n" + end + @cookies.each do |cookie| + response << "Set-Cookie: #{cookie}\r\n" + end + response << "\r\n" + response + end + + def status_message(code) + case code + when 200 then 'OK' + when 301 then 'Moved Permanently' + else 'Unknown' + end + end + end +end + +class TestOpenURI < Test::Unit::TestCase + class Unauthorized < StandardError; end + def with_http(log_tester=lambda {|log| assert_equal([], log) }) log = [] - logger = WEBrick::Log.new(log, WEBrick::BasicLog::WARN) - Dir.mktmpdir {|dr| - srv = WEBrick::HTTPServer.new({ - :DocumentRoot => dr, - :ServerType => Thread, - :Logger => logger, - :AccessLog => [[NullLog, ""]], - :BindAddress => '127.0.0.1', - :Port => 0}) - _, port, _, host = srv.listeners[0].addr - server_thread = srv.start - server_thread2 = Thread.new { - server_thread.join - if log_tester - log_tester.call(log) - end - } - client_thread = Thread.new { - begin - yield srv, "http://#{host}:#{port}", server_thread, log - ensure - srv.shutdown - end - } - assert_join_threads([client_thread, server_thread2]) + srv = SimpleHTTPServer.new('localhost', 0, log) + + server_thread = srv.start + server_thread2 = Thread.new { + server_thread.join + if log_tester + log_tester.call(log) + end + } + + port = srv.instance_variable_get(:@server).addr[1] + + client_thread = Thread.new { + begin + yield srv, "http://localhost:#{port}", server_thread, log + ensure + srv.shutdown + end } - ensure - WEBrick::Utils::TimeoutHandler.terminate + assert_join_threads([client_thread, server_thread2]) end def test_200_uri_open @@ -226,7 +361,7 @@ def test_header myheader1 = 'barrrr' myheader2 = nil with_http {|srv, url| - srv.mount_proc("/h/") {|req, res| myheader2 = req['myheader']; res.body = "foo" } + srv.mount_proc("/h/", lambda {|req, res| myheader2 = req['myheader']; res.body = "foo" } ) URI.open("#{url}/h/", 'MyHeader'=>myheader1) {|f| assert_equal("foo", f.read) assert_equal(myheader1, myheader2) @@ -248,9 +383,9 @@ def test_non_http_proxy def test_redirect with_http {|srv, url| - srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } - srv.mount_proc("/r2/") {|req, res| res.body = "r2" } - srv.mount_proc("/to-file/") {|req, res| res.status = 301; res["location"] = "file:///foo" } + srv.mount_proc("/r1/", lambda {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } ) + srv.mount_proc("/r2/", lambda {|req, res| res.body = "r2" } ) + srv.mount_proc("/to-file/", lambda {|req, res| res.status = 301; res["location"] = "file:///foo" } ) URI.open("#{url}/r1/") {|f| assert_equal("#{url}/r2", f.base_uri.to_s) assert_equal("r2", f.read) @@ -262,8 +397,8 @@ def test_redirect def test_redirect_loop with_http {|srv, url| - srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } - srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r1"; res.body = "r2" } + srv.mount_proc("/r1/", lambda {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } ) + srv.mount_proc("/r2/", lambda {|req, res| res.status = 301; res["location"] = "#{url}/r1"; res.body = "r2" } ) assert_raise(RuntimeError) { URI.open("#{url}/r1/") {} } } end @@ -328,16 +463,16 @@ def test_redirect_invalid end def setup_redirect_auth(srv, url) - srv.mount_proc("/r1/") {|req, res| + srv.mount_proc("/r1/", lambda {|req, res| res.status = 301 res["location"] = "#{url}/r2" - } - srv.mount_proc("/r2/") {|req, res| + }) + srv.mount_proc("/r2/", lambda {|req, res| if req["Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" - raise WEBrick::HTTPStatus::Unauthorized + raise Unauthorized end res.body = "r2" - } + }) end def test_redirect_auth_success @@ -352,7 +487,7 @@ def test_redirect_auth_success def test_redirect_auth_failure_r2 log_tester = lambda {|server_log| assert_equal(1, server_log.length) - assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, server_log[0]) + assert_match(/ERROR Unauthorized/, server_log[0]) } with_http(log_tester) {|srv, url, server_thread, server_log| setup_redirect_auth(srv, url) @@ -364,7 +499,7 @@ def test_redirect_auth_failure_r2 def test_redirect_auth_failure_r1 log_tester = lambda {|server_log| assert_equal(1, server_log.length) - assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, server_log[0]) + assert_match(/ERROR Unauthorized/, server_log[0]) } with_http(log_tester) {|srv, url, server_thread, server_log| setup_redirect_auth(srv, url) @@ -375,18 +510,18 @@ def test_redirect_auth_failure_r1 def test_max_redirects_success with_http {|srv, url| - srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } - srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r3"; res.body = "r2" } - srv.mount_proc("/r3/") {|req, res| res.body = "r3" } + srv.mount_proc("/r1/", lambda {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } ) + srv.mount_proc("/r2/", lambda {|req, res| res.status = 301; res["location"] = "#{url}/r3"; res.body = "r2" } ) + srv.mount_proc("/r3/", lambda {|req, res| res.body = "r3" } ) URI.open("#{url}/r1/", max_redirects: 2) { |f| assert_equal("r3", f.read) } } end def test_max_redirects_too_many with_http {|srv, url| - srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } - srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r3"; res.body = "r2" } - srv.mount_proc("/r3/") {|req, res| res.body = "r3" } + srv.mount_proc("/r1/", lambda {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" } ) + srv.mount_proc("/r2/", lambda {|req, res| res.status = 301; res["location"] = "#{url}/r3"; res.body = "r2" } ) + srv.mount_proc("/r3/", lambda {|req, res| res.body = "r3" } ) exc = assert_raise(OpenURI::TooManyRedirects) { URI.open("#{url}/r1/", max_redirects: 1) {} } assert_equal("Too many redirects", exc.message) } @@ -399,7 +534,7 @@ def test_userinfo def test_progress with_http {|srv, url| content = "a" * 100000 - srv.mount_proc("/data/") {|req, res| res.body = content } + srv.mount_proc("/data/", lambda {|req, res| res.body = content }) length = [] progress = [] URI.open("#{url}/data/", @@ -419,7 +554,7 @@ def test_progress def test_progress_chunked with_http {|srv, url| content = "a" * 100000 - srv.mount_proc("/data/") {|req, res| res.body = content; res.chunked = true } + srv.mount_proc("/data/", lambda {|req, res| res.body = content; res.chunked = true } ) length = [] progress = [] URI.open("#{url}/data/", @@ -449,9 +584,9 @@ def test_encoding with_http {|srv, url| content_u8 = "\u3042" content_ej = "\xa2\xa4".dup.force_encoding("euc-jp") - srv.mount_proc("/u8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset=utf-8' } - srv.mount_proc("/ej/") {|req, res| res.body = content_ej; res['content-type'] = 'TEXT/PLAIN; charset=EUC-JP' } - srv.mount_proc("/nc/") {|req, res| res.body = "aa"; res['content-type'] = 'Text/Plain' } + srv.mount_proc("/u8/", lambda {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset=utf-8' } ) + srv.mount_proc("/ej/", lambda {|req, res| res.body = content_ej; res['content-type'] = 'TEXT/PLAIN; charset=EUC-JP' } ) + srv.mount_proc("/nc/", lambda {|req, res| res.body = "aa"; res['content-type'] = 'Text/Plain' } ) URI.open("#{url}/u8/") {|f| assert_equal(content_u8, f.read) assert_equal("text/plain", f.content_type) @@ -492,7 +627,7 @@ def test_encoding def test_quoted_attvalue with_http {|srv, url| content_u8 = "\u3042" - srv.mount_proc("/qu8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset="utf\-8"' } + srv.mount_proc("/qu8/", lambda {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset="utf\-8"' } ) URI.open("#{url}/qu8/") {|f| assert_equal(content_u8, f.read) assert_equal("text/plain", f.content_type) @@ -503,7 +638,7 @@ def test_quoted_attvalue def test_last_modified with_http {|srv, url| - srv.mount_proc("/data/") {|req, res| res.body = "foo"; res['last-modified'] = 'Fri, 07 Aug 2009 06:05:04 GMT' } + srv.mount_proc("/data/", lambda {|req, res| res.body = "foo"; res['last-modified'] = 'Fri, 07 Aug 2009 06:05:04 GMT' } ) URI.open("#{url}/data/") {|f| assert_equal("foo", f.read) assert_equal(Time.utc(2009,8,7,6,5,4), f.last_modified) @@ -515,9 +650,9 @@ def test_content_encoding with_http {|srv, url| content = "abc" * 10000 Zlib::GzipWriter.wrap(StringIO.new(content_gz="".b)) {|z| z.write content } - srv.mount_proc("/data/") {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip' } - srv.mount_proc("/data2/") {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip'; res.chunked = true } - srv.mount_proc("/noce/") {|req, res| res.body = content_gz } + srv.mount_proc("/data/", lambda {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip' } ) + srv.mount_proc("/data2/", lambda {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip'; res.chunked = true } ) + srv.mount_proc("/noce/", lambda {|req, res| res.body = content_gz } ) URI.open("#{url}/data/") {|f| assert_equal [], f.content_encoding assert_equal(content, f.read) @@ -535,11 +670,11 @@ def test_content_encoding def test_multiple_cookies with_http {|srv, url| - srv.mount_proc("/mcookie/") {|req, res| + srv.mount_proc("/mcookie/", lambda {|req, res| res.cookies << "name1=value1; blabla" res.cookies << "name2=value2; blabla" res.body = "foo" - } + }) URI.open("#{url}/mcookie/") {|f| assert_equal("foo", f.read) assert_equal(["name1=value1; blabla", "name2=value2; blabla"], From 489a1e9006f4de7783fdabe516886308f3724903 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 4 Jul 2024 17:03:35 +0900 Subject: [PATCH 05/15] Extract SimpleHTTPServer and with_http to TestOpenURIUtils module for other tests --- test/open-uri/test_open-uri.rb | 176 +------------------------------- test/open-uri/test_proxy.rb | 34 +------ test/open-uri/utils.rb | 177 +++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 206 deletions(-) create mode 100644 test/open-uri/utils.rb diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 5f8454d..ee39dbc 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -1,186 +1,14 @@ # frozen_string_literal: true require 'test/unit' require 'open-uri' -require 'socket' -require 'base64' +require_relative 'utils' begin require 'zlib' rescue LoadError end -class SimpleHTTPServer - def initialize(bind_addr, port, log) - @server = TCPServer.new(bind_addr, port) - @log = log - @procs = {} - end - - def mount_proc(path, proc) - @procs[path] = proc - end - - def start - @thread = Thread.new do - loop do - client = @server.accept - handle_request(client) - client.close - end - end - end - - def shutdown - @thread.kill - @server.close - end - - private - - def handle_request(client) - request_line = client.gets - return if request_line.nil? - - method, path, _ = request_line.split - headers = {} - while (line = client.gets) && line != "\r\n" - key, value = line.split(": ", 2) - headers[key.downcase] = value.strip - end - - if @procs.key?(path) || @procs.key?("#{path}/") - proc = @procs[path] || @procs["#{path}/"] - req = Request.new(method, path, headers) - res = Response.new(client) - proc.call(req, res) - res.finish - else - @log << "ERROR `#{path}' not found" - client.print "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n" - end - rescue ::TestOpenURI::Unauthorized => e - @log << "ERROR Unauthorized" - client.print "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n" - end - - class Request - attr_reader :method, :path, :headers - def initialize(method, path, headers) - @method = method - @path = path - @headers = headers - parse_basic_auth - end - - def [](key) - @headers[key.downcase] - end - - def []=(key, value) - @headers[key.downcase] = value - end - - private - - def parse_basic_auth - auth = @headers['Authorization'] - return unless auth && auth.start_with?('Basic ') - - encoded_credentials = auth.split(' ', 2).last - decoded_credentials = Base64.decode64(encoded_credentials) - @username, @password = decoded_credentials.split(':', 2) - end - end - - class Response - attr_accessor :body, :headers, :status, :chunked, :cookies - def initialize(client) - @client = client - @body = "" - @headers = {} - @status = 200 - @chunked = false - @cookies = [] - end - - def [](key) - @headers[key.downcase] - end - - def []=(key, value) - @headers[key.downcase] = value - end - - def write_chunk(chunk) - return unless @chunked - @client.write("#{chunk.bytesize.to_s(16)}\r\n") - @client.write("#{chunk}\r\n") - end - - def finish - @client.write build_response_headers - if @chunked - write_chunk(@body) - @client.write "0\r\n\r\n" - else - @client.write @body - end - end - - private - - def build_response_headers - response = "HTTP/1.1 #{@status} #{status_message(@status)}\r\n" - if @chunked - @headers['Transfer-Encoding'] = 'chunked' - else - @headers['Content-Length'] = @body.bytesize.to_s - end - @headers.each do |key, value| - response << "#{key}: #{value}\r\n" - end - @cookies.each do |cookie| - response << "Set-Cookie: #{cookie}\r\n" - end - response << "\r\n" - response - end - - def status_message(code) - case code - when 200 then 'OK' - when 301 then 'Moved Permanently' - else 'Unknown' - end - end - end -end - class TestOpenURI < Test::Unit::TestCase - class Unauthorized < StandardError; end - - def with_http(log_tester=lambda {|log| assert_equal([], log) }) - log = [] - srv = SimpleHTTPServer.new('localhost', 0, log) - - server_thread = srv.start - server_thread2 = Thread.new { - server_thread.join - if log_tester - log_tester.call(log) - end - } - - port = srv.instance_variable_get(:@server).addr[1] - - client_thread = Thread.new { - begin - yield srv, "http://localhost:#{port}", server_thread, log - ensure - srv.shutdown - end - } - assert_join_threads([client_thread, server_thread2]) - end + include TestOpenURIUtils def test_200_uri_open with_http {|srv, url| diff --git a/test/open-uri/test_proxy.rb b/test/open-uri/test_proxy.rb index 2b503d9..4c65158 100644 --- a/test/open-uri/test_proxy.rb +++ b/test/open-uri/test_proxy.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test/unit' require 'open-uri' +require_relative 'utils' require 'webrick' require 'webrick/httpproxy' begin @@ -9,44 +10,13 @@ end class TestOpenURIProxy < Test::Unit::TestCase + include TestOpenURIUtils NullLog = Object.new def NullLog.<<(arg) #puts arg if / INFO / !~ arg end - def with_http(log_tester=lambda {|log| assert_equal([], log) }) - log = [] - logger = WEBrick::Log.new(log, WEBrick::BasicLog::WARN) - Dir.mktmpdir {|dr| - srv = WEBrick::HTTPServer.new({ - :DocumentRoot => dr, - :ServerType => Thread, - :Logger => logger, - :AccessLog => [[NullLog, ""]], - :BindAddress => '127.0.0.1', - :Port => 0}) - _, port, _, host = srv.listeners[0].addr - server_thread = srv.start - server_thread2 = Thread.new { - server_thread.join - if log_tester - log_tester.call(log) - end - } - client_thread = Thread.new { - begin - yield srv, "http://#{host}:#{port}", server_thread, log - ensure - srv.shutdown - end - } - assert_join_threads([client_thread, server_thread2]) - } - ensure - WEBrick::Utils::TimeoutHandler.terminate - end - def with_env(h) begin old = {} diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb new file mode 100644 index 0000000..53bbc0a --- /dev/null +++ b/test/open-uri/utils.rb @@ -0,0 +1,177 @@ +require 'socket' +require 'base64' + +class SimpleHTTPServer + def initialize(bind_addr, port, log) + @server = TCPServer.new(bind_addr, port) + @log = log + @procs = {} + end + + def mount_proc(path, proc) + @procs[path] = proc + end + + def start + @thread = Thread.new do + loop do + client = @server.accept + handle_request(client) + client.close + end + end + end + + def shutdown + @thread.kill + @server.close + end + + private + + def handle_request(client) + request_line = client.gets + return if request_line.nil? + + method, path, _ = request_line.split + headers = {} + while (line = client.gets) && line != "\r\n" + key, value = line.split(": ", 2) + headers[key.downcase] = value.strip + end + + if @procs.key?(path) || @procs.key?("#{path}/") + proc = @procs[path] || @procs["#{path}/"] + req = Request.new(method, path, headers) + res = Response.new(client) + proc.call(req, res) + res.finish + else + @log << "ERROR `#{path}' not found" + client.print "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n" + end + rescue ::TestOpenURI::Unauthorized => e + @log << "ERROR Unauthorized" + client.print "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n" + end + + class Request + attr_reader :method, :path, :headers + def initialize(method, path, headers) + @method = method + @path = path + @headers = headers + parse_basic_auth + end + + def [](key) + @headers[key.downcase] + end + + def []=(key, value) + @headers[key.downcase] = value + end + + private + + def parse_basic_auth + auth = @headers['Authorization'] + return unless auth && auth.start_with?('Basic ') + + encoded_credentials = auth.split(' ', 2).last + decoded_credentials = Base64.decode64(encoded_credentials) + @username, @password = decoded_credentials.split(':', 2) + end + end + + class Response + attr_accessor :body, :headers, :status, :chunked, :cookies + def initialize(client) + @client = client + @body = "" + @headers = {} + @status = 200 + @chunked = false + @cookies = [] + end + + def [](key) + @headers[key.downcase] + end + + def []=(key, value) + @headers[key.downcase] = value + end + + def write_chunk(chunk) + return unless @chunked + @client.write("#{chunk.bytesize.to_s(16)}\r\n") + @client.write("#{chunk}\r\n") + end + + def finish + @client.write build_response_headers + if @chunked + write_chunk(@body) + @client.write "0\r\n\r\n" + else + @client.write @body + end + end + + private + + def build_response_headers + response = "HTTP/1.1 #{@status} #{status_message(@status)}\r\n" + if @chunked + @headers['Transfer-Encoding'] = 'chunked' + else + @headers['Content-Length'] = @body.bytesize.to_s + end + @headers.each do |key, value| + response << "#{key}: #{value}\r\n" + end + @cookies.each do |cookie| + response << "Set-Cookie: #{cookie}\r\n" + end + response << "\r\n" + response + end + + def status_message(code) + case code + when 200 then 'OK' + when 301 then 'Moved Permanently' + else 'Unknown' + end + end + end +end + +module TestOpenURIUtils + class Unauthorized < StandardError; end + + def with_http(log_tester=lambda {|log| assert_equal([], log) }) + log = [] + srv = SimpleHTTPServer.new('localhost', 0, log) + + server_thread = srv.start + server_thread2 = Thread.new { + server_thread.join + if log_tester + log_tester.call(log) + end + } + + port = srv.instance_variable_get(:@server).addr[1] + + client_thread = Thread.new { + begin + yield srv, "http://localhost:#{port}", server_thread, log + ensure + srv.shutdown + end + } + assert_join_threads([client_thread, server_thread2]) + end +end \ No newline at end of file From ad4752930657d79a35f440a31cc3a8f73786d280 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 4 Jul 2024 17:42:35 +0900 Subject: [PATCH 06/15] Added SimpleHTTPProxyServer by TCPServer --- test/open-uri/test_proxy.rb | 75 ++++++++++--------------------------- test/open-uri/utils.rb | 75 +++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 55 deletions(-) diff --git a/test/open-uri/test_proxy.rb b/test/open-uri/test_proxy.rb index 4c65158..7fd1fd8 100644 --- a/test/open-uri/test_proxy.rb +++ b/test/open-uri/test_proxy.rb @@ -2,21 +2,10 @@ require 'test/unit' require 'open-uri' require_relative 'utils' -require 'webrick' -require 'webrick/httpproxy' -begin - require 'zlib' -rescue LoadError -end class TestOpenURIProxy < Test::Unit::TestCase include TestOpenURIUtils - NullLog = Object.new - def NullLog.<<(arg) - #puts arg if / INFO / !~ arg - end - def with_env(h) begin old = {} @@ -41,18 +30,12 @@ def teardown def test_proxy with_http {|srv, url| proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| + proxy_host = '127.0.0.1' + proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| proxy_auth_log << req.request_line - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr + }, proxy_log) + proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://#{proxy_host}:#{proxy_port}/" begin proxy_thread = proxy.start @@ -95,21 +78,15 @@ def test_proxy def test_proxy_http_basic_authentication_failure with_http {|srv, url| proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| + proxy_host = '127.0.0.1' + proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| proxy_auth_log << req.request_line if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" - raise WEBrick::HTTPStatus::ProxyAuthenticationRequired + raise ProxyAuthenticationRequired end - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr + }, proxy_log) + proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://#{proxy_host}:#{proxy_port}/" begin th = proxy.start @@ -121,28 +98,22 @@ def test_proxy_http_basic_authentication_failure proxy.shutdown th.join end - assert_match(/ERROR WEBrick::HTTPStatus::ProxyAuthenticationRequired/, proxy_log.string) + assert_match(/ERROR ProxyAuthenticationRequired/, proxy_log.string) } end def test_proxy_http_basic_authentication_success with_http {|srv, url| proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| + proxy_host = '127.0.0.1' + proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| proxy_auth_log << req.request_line if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" - raise WEBrick::HTTPStatus::ProxyAuthenticationRequired + raise ProxyAuthenticationRequired end - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr + }, proxy_log) + proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://#{proxy_host}:#{proxy_port}/" begin th = proxy.start @@ -169,21 +140,15 @@ def test_proxy_http_basic_authentication_success def test_authenticated_proxy_http_basic_authentication_success with_http {|srv, url| proxy_log = StringIO.new(''.dup) - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) proxy_auth_log = ''.dup - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[NullLog, ""]], - :ProxyAuthProc => lambda {|req, res| + proxy_host = '127.0.0.1' + proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| proxy_auth_log << req.request_line if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" - raise WEBrick::HTTPStatus::ProxyAuthenticationRequired + raise ProxyAuthenticationRequired end - }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr + }, proxy_log) + proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://user:pass@#{proxy_host}:#{proxy_port}/" begin th = proxy.start diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index 53bbc0a..3ae1959 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -148,8 +148,83 @@ def status_message(code) end end +class SimpleHTTPProxyServer + def initialize(host, port, auth_proc = nil, log) + @server = TCPServer.new(host, port) + @auth_proc = auth_proc + @log = log + end + + def start + @thread = Thread.new do + loop do + client = @server.accept + request_line = client.gets + headers = {} + while (line = client.gets) && (line != "\r\n") + key, value = line.chomp.split(/:\s*/, 2) + headers[key] = value + end + next unless request_line + + method, path, _ = request_line.split(' ') + handle_request(client, method, path, request_line, headers) + end + end + end + + def shutdown + @thread.kill + @server.close + end + + private + + def handle_request(client, method, path, request_line, headers) + if @auth_proc + req = Request.new(method, path, request_line, headers) + res = Struct.new(:body, :status).new("", 200) + @auth_proc.call(req, res) + if res.status != 200 + client.print "HTTP/1.1 #{res.status}\r\nContent-Type: text/plain\r\n\r\n#{res.body}" + return + end + end + + uri = URI(path) + proxy_request(uri, client) + rescue TestOpenURIProxy::ProxyAuthenticationRequired + @log << "ERROR ProxyAuthenticationRequired" + client.print "HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 0\r\n\r\n" + ensure + client.close + end + + def proxy_request(uri, client) + Net::HTTP.start(uri.host, uri.port) do |http| + response = http.get(uri.path) + client.print "HTTP/1.1 #{response.code}\r\nContent-Type: #{response.content_type}\r\n\r\n#{response.body}" + end + end + + class Request + attr_reader :method, :path, :request_line, :headers + def initialize(method, path, request_line, headers) + @method = method + @path = path + @request_line = request_line + @headers = headers + end + + def [](key) + @headers[key] + end + end +end + module TestOpenURIUtils class Unauthorized < StandardError; end + class ProxyAuthenticationRequired < StandardError; end def with_http(log_tester=lambda {|log| assert_equal([], log) }) log = [] From 57c80e157656e9758e86c0e81165bd5461ce37df Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 11:10:52 +0900 Subject: [PATCH 07/15] Added SimpleHTTPSServer by TCPServer and OpenSSL --- test/open-uri/test_ssl.rb | 350 +------------------------------ test/open-uri/utils.rb | 426 +++++++++++++++++++++++++++++++++++++- 2 files changed, 433 insertions(+), 343 deletions(-) diff --git a/test/open-uri/test_ssl.rb b/test/open-uri/test_ssl.rb index 3f94cab..5cdd0c7 100644 --- a/test/open-uri/test_ssl.rb +++ b/test/open-uri/test_ssl.rb @@ -2,6 +2,7 @@ require 'test/unit' require 'open-uri' require 'stringio' +require_relative 'utils' require 'webrick' begin require 'openssl' @@ -14,11 +15,13 @@ class TestOpenURISSL < Test::Unit::TestCase end class TestOpenURISSL + include TestOpenURIUtils + NullLog = Object.new def NullLog.<<(arg) end - def with_https(log_tester=lambda {|log| assert_equal([], log) }) + def with_https_webrick(log_tester=lambda {|log| assert_equal([], log) }) log = [] logger = WEBrick::Log.new(log, WEBrick::BasicLog::WARN) Dir.mktmpdir {|dr| @@ -68,7 +71,9 @@ def teardown def setup_validation(srv, dr) cacert_filename = "#{dr}/cacert.pem" URI.open(cacert_filename, "w") {|f| f << CA_CERT } - srv.mount_proc("/data", lambda { |req, res| res.body = "ddd" } ) + if srv.respond_to?(:mount_proc) + srv.mount_proc("/data", lambda { |req, res| res.body = "ddd" } ) + end cacert_filename end @@ -101,7 +106,7 @@ def test_validation_failure assert_match(/ERROR OpenSSL::SSL::SSLError:/, server_log[0]) } end - with_https(log_tester) {|srv, dr, url, server_thread, server_log| + with_https_webrick(log_tester) {|srv, dr, url, server_thread, server_log| setup_validation(srv, dr) assert_raise(OpenSSL::SSL::SSLError) { URI.open("#{url}/data") {} } } @@ -130,7 +135,6 @@ def with_https_proxy(proxy_log_tester=lambda {|proxy_log, proxy_access_log| asse proxy_log = [] proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) with_https {|srv, dr, url, server_thread, server_log, threads| - srv.mount_proc("/proxy", lambda { |req, res| res.body = "proxy" } ) cacert_filename = "#{dr}/cacert.pem" open(cacert_filename, "w") {|f| f << CA_CERT } cacert_directory = "#{dr}/certs" @@ -192,341 +196,3 @@ def test_proxy_cacert_dir end end if defined?(OpenSSL::SSL) - -if defined?(OpenSSL::SSL) -# cp /etc/ssl/openssl.cnf . # I copied from OpenSSL 1.1.1b source - -# mkdir demoCA demoCA/private demoCA/newcerts -# touch demoCA/index.txt -# echo 00 > demoCA/serial -# openssl genrsa -des3 -out demoCA/private/cakey.pem 2048 -# openssl req -new -key demoCA/private/cakey.pem -out demoCA/careq.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=Ruby Test CA" -# # basicConstraints=CA:TRUE is required; the default openssl.cnf has it in [v3_ca] -# openssl ca -config openssl.cnf -extensions v3_ca -out demoCA/cacert.pem -startdate 090101000000Z -enddate 491231235959Z -batch -keyfile demoCA/private/cakey.pem -selfsign -infiles demoCA/careq.pem - -# mkdir server -# openssl genrsa -des3 -out server/server.key 2048 -# openssl req -new -key server/server.key -out server/csr.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=127.0.0.1" -# openssl ca -config openssl.cnf -startdate 090101000000Z -enddate 491231235959Z -in server/csr.pem -keyfile demoCA/private/cakey.pem -cert demoCA/cacert.pem -out server/cert.pem - -# demoCA/cacert.pem => TestOpenURISSL::CA_CERT -# server/cert.pem => TestOpenURISSL::SERVER_CERT -# `openssl rsa -in server/server.key -text` => TestOpenURISSL::SERVER_KEY - -TestOpenURISSL::CA_CERT = <<'End' -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 0 (0x0) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA - Validity - Not Before: Jan 1 00:00:00 2009 GMT - Not After : Dec 31 23:59:59 2049 GMT - Subject: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:ad:f3:4d:5b:0b:01:54:cc:86:36:d1:93:6b:33: - 56:25:90:61:d6:9a:a0:f4:24:20:ee:c8:14:ab:0f: - 4b:89:d8:7c:bb:c0:f8:7f:fb:e9:a2:d5:1c:6b:6f: - dc:5c:23:b1:49:aa:2c:e8:ca:43:48:64:69:4b:8a: - bd:44:57:9b:14:d9:7a:b2:49:00:d6:c2:74:67:62: - 52:1d:a9:32:df:fe:7a:22:20:49:83:e1:cb:3d:dc: - 1a:2a:f0:36:20:c1:e8:c8:89:d4:51:1a:68:91:20: - e0:ba:67:0a:b2:6b:f8:e3:8c:f5:ee:a1:36:b1:89: - ec:23:b6:f2:39:a9:b9:2e:ea:de:d9:86:e5:42:11: - 46:ed:10:9a:90:76:44:4e:4d:49:2d:49:e8:e3:cb: - ff:7a:7d:80:cb:bf:c4:c3:69:ba:9c:60:4a:de:af: - bf:26:78:b8:fb:46:d1:37:d0:89:ba:78:93:6a:37: - a5:e9:58:e7:e2:e3:7d:7c:95:20:79:41:56:15:cd: - b2:c6:3b:e1:b7:e7:ba:47:60:9a:05:b1:07:f3:26: - 72:9d:3b:1b:02:18:3d:d5:de:e6:e9:30:a9:b5:8f: - 15:1b:40:f9:64:61:54:d3:53:e8:c4:29:4a:89:f3: - e5:0d:fd:16:61:ee:f2:6d:8a:45:a8:34:7e:53:46: - 8e:87 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Subject Key Identifier: - A0:7E:0B:AD:A3:AD:37:D7:21:0B:75:6F:8A:90:5F:8C:C9:69:DF:98 - X509v3 Authority Key Identifier: - keyid:A0:7E:0B:AD:A3:AD:37:D7:21:0B:75:6F:8A:90:5F:8C:C9:69:DF:98 - - X509v3 Basic Constraints: critical - CA:TRUE - Signature Algorithm: sha256WithRSAEncryption - 06:ea:06:02:19:9a:cb:94:a2:7e:c0:86:71:66:e7:a5:71:46: - a2:25:55:f5:e5:58:df:d1:91:58:e6:8a:0e:91:b3:22:4c:88: - 4d:5f:02:af:0f:73:65:0d:af:9a:f2:e4:36:f3:1f:e8:28:1d: - 9c:74:72:5b:f7:12:e8:fa:45:d6:df:e5:f1:d3:91:f4:0e:db: - e2:56:63:ee:82:57:6f:12:ad:d7:0d:de:5a:8c:3d:76:d2:87: - c9:48:1c:c4:f3:89:63:3c:c2:25:e0:dd:63:a6:4c:6c:5a:07: - 7b:86:78:62:86:02:a1:ef:0e:41:75:c5:d4:61:ab:c3:3b:9b: - 51:0b:e6:34:6d:0b:14:5a:2d:aa:d3:58:26:43:8f:4c:d7:45: - 73:1e:67:66:5e:f3:0c:69:70:27:a1:d5:70:f3:5a:10:98:c8: - 4f:8a:3b:9f:ad:8e:8d:49:8f:fb:f6:36:5d:4f:70:f9:4f:54: - 33:cf:a2:a6:1d:8c:61:b9:30:42:f2:49:d1:3d:a1:f1:eb:1e: - 78:a6:30:f8:8a:48:89:c7:3e:bd:0d:d8:72:04:a6:00:e5:62: - a4:13:3f:9e:b6:86:25:dc:d1:ff:3a:fc:f5:0e:e4:0e:f7:b8: - 66:90:fe:4f:c2:54:2a:7f:61:6e:e7:4b:bf:40:7e:75:30:02: - 5b:bb:91:1b ------BEGIN CERTIFICATE----- -MIIDXDCCAkSgAwIBAgIBADANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJKUDEO -MAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRUwEwYDVQQDDAxSdWJ5 -IFRlc3QgQ0EwHhcNMDkwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBHMQswCQYD -VQQGEwJKUDEOMAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRUwEwYD -VQQDDAxSdWJ5IFRlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCt801bCwFUzIY20ZNrM1YlkGHWmqD0JCDuyBSrD0uJ2Hy7wPh/++mi1Rxrb9xc -I7FJqizoykNIZGlLir1EV5sU2XqySQDWwnRnYlIdqTLf/noiIEmD4cs93Boq8DYg -wejIidRRGmiRIOC6Zwqya/jjjPXuoTaxiewjtvI5qbku6t7ZhuVCEUbtEJqQdkRO -TUktSejjy/96fYDLv8TDabqcYErer78meLj7RtE30Im6eJNqN6XpWOfi4318lSB5 -QVYVzbLGO+G357pHYJoFsQfzJnKdOxsCGD3V3ubpMKm1jxUbQPlkYVTTU+jEKUqJ -8+UN/RZh7vJtikWoNH5TRo6HAgMBAAGjUzBRMB0GA1UdDgQWBBSgfguto6031yEL -dW+KkF+MyWnfmDAfBgNVHSMEGDAWgBSgfguto6031yELdW+KkF+MyWnfmDAPBgNV -HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAG6gYCGZrLlKJ+wIZxZuel -cUaiJVX15Vjf0ZFY5ooOkbMiTIhNXwKvD3NlDa+a8uQ28x/oKB2cdHJb9xLo+kXW -3+Xx05H0DtviVmPugldvEq3XDd5ajD120ofJSBzE84ljPMIl4N1jpkxsWgd7hnhi -hgKh7w5BdcXUYavDO5tRC+Y0bQsUWi2q01gmQ49M10VzHmdmXvMMaXAnodVw81oQ -mMhPijufrY6NSY/79jZdT3D5T1Qzz6KmHYxhuTBC8knRPaHx6x54pjD4ikiJxz69 -DdhyBKYA5WKkEz+etoYl3NH/Ovz1DuQO97hmkP5PwlQqf2Fu50u/QH51MAJbu5Eb ------END CERTIFICATE----- -End - -TestOpenURISSL::SERVER_CERT = <<'End' -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA - Validity - Not Before: Jan 1 00:00:00 2009 GMT - Not After : Dec 31 23:59:59 2049 GMT - Subject: C=JP, ST=Tokyo, O=RubyTest, CN=127.0.0.1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:cb:b3:71:95:12:70:fc:db:d4:a9:a7:66:d6:d3: - 09:dd:06:80:19:e1:f2:d6:1e:31:b6:6b:20:75:51: - dc:a7:37:a9:ac:5b:57:5d:69:36:b6:de:1d:2c:f6: - 44:64:f8:e8:d6:f0:da:38:6a:ba:c2:b1:9e:dc:bb: - 79:94:e0:25:0c:ce:76:87:17:5d:79:9e:14:9e:bd: - 4c:0d:aa:74:10:3a:96:ef:76:82:d5:72:16:b5:c1: - ac:17:2d:90:83:73:5c:d7:a6:f5:36:0f:4c:55:f3: - 30:5d:19:dc:01:0e:f8:e6:fe:a5:ad:52:88:59:dc: - 4a:07:ed:a2:eb:a1:01:63:c4:8a:92:ba:06:80:9b: - 0d:85:f2:9f:f9:70:ac:d7:ad:f0:7a:3f:b8:92:2a: - 33:ca:69:d0:01:65:5d:31:38:1d:f6:1f:b2:17:07: - 7e:ac:88:67:a6:c4:5f:3e:93:94:61:e6:e4:49:9d: - ba:d4:d2:e8:e3:93:d1:66:79:c5:e3:1d:f8:5a:50: - 54:58:3d:04:b0:fd:65:d1:b3:8a:b5:8a:30:5f:b2: - dc:34:1a:14:f7:74:4c:03:29:97:63:5a:d7:de:bb: - eb:7f:4a:2a:90:59:c0:2b:47:09:82:8f:75:de:14: - 3f:bc:78:9a:69:25:80:5b:6c:a0:65:12:0d:29:61: - ac:f9 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - OpenSSL Generated Certificate - X509v3 Subject Key Identifier: - EC:6B:7C:79:B8:3B:11:1D:42:F3:9A:2A:CF:9A:15:59:D7:F9:D8:C6 - X509v3 Authority Key Identifier: - keyid:A0:7E:0B:AD:A3:AD:37:D7:21:0B:75:6F:8A:90:5F:8C:C9:69:DF:98 - - Signature Algorithm: sha256WithRSAEncryption - 29:14:db:71:e9:a0:86:f8:cc:4d:e4:8a:76:78:a7:ff:4e:94: - b4:4d:92:dc:57:9a:52:64:46:27:15:8b:4f:2a:18:a7:0d:fc: - d2:75:ce:4e:49:97:0b:46:71:57:23:e3:a5:c0:c5:71:94:fc: - f2:1d:3b:06:93:82:03:59:56:d4:fb:09:06:08:b4:97:50:33: - cf:58:89:dd:91:31:07:26:9a:7e:7f:8d:71:de:09:dc:4f:e5: - 6b:a3:10:71:d4:50:24:43:a0:1c:f5:2a:d9:1a:fb:e3:d6:f1: - bc:6b:42:67:16:b4:3b:31:f4:ec:03:7d:78:e2:64:16:57:6d: - ba:7c:0c:e1:14:b2:7c:75:4e:2b:09:3e:86:e4:aa:cc:7e:5c: - 2b:bd:8d:26:4d:49:36:74:86:fe:c5:a6:15:4a:af:e8:b4:4e: - d5:f2:e1:59:c2:fb:7e:c3:c4:f1:63:d8:c2:b0:9a:ae:31:96: - 90:c3:09:d0:ce:2e:31:90:d7:83:dd:ac:31:cc:f7:87:41:08: - 92:33:28:52:fa:2d:9e:ad:ae:6a:9f:c3:be:ce:c1:a6:e4:16: - 2f:69:34:40:86:b6:10:21:0e:31:69:81:9e:fc:fd:c3:06:25: - 65:37:d3:d9:4a:20:84:aa:e7:0e:60:7c:bf:3f:88:67:ac:e5: - 8c:e0:61:d6 ------BEGIN CERTIFICATE----- -MIIDgTCCAmmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJKUDEO -MAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRUwEwYDVQQDDAxSdWJ5 -IFRlc3QgQ0EwHhcNMDkwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBEMQswCQYD -VQQGEwJKUDEOMAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRIwEAYD -VQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL -s3GVEnD829Spp2bW0wndBoAZ4fLWHjG2ayB1UdynN6msW1ddaTa23h0s9kRk+OjW -8No4arrCsZ7cu3mU4CUMznaHF115nhSevUwNqnQQOpbvdoLVcha1wawXLZCDc1zX -pvU2D0xV8zBdGdwBDvjm/qWtUohZ3EoH7aLroQFjxIqSugaAmw2F8p/5cKzXrfB6 -P7iSKjPKadABZV0xOB32H7IXB36siGemxF8+k5Rh5uRJnbrU0ujjk9FmecXjHfha -UFRYPQSw/WXRs4q1ijBfstw0GhT3dEwDKZdjWtfeu+t/SiqQWcArRwmCj3XeFD+8 -eJppJYBbbKBlEg0pYaz5AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgEN -BB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTsa3x5 -uDsRHULzmirPmhVZ1/nYxjAfBgNVHSMEGDAWgBSgfguto6031yELdW+KkF+MyWnf -mDANBgkqhkiG9w0BAQsFAAOCAQEAKRTbcemghvjMTeSKdnin/06UtE2S3FeaUmRG -JxWLTyoYpw380nXOTkmXC0ZxVyPjpcDFcZT88h07BpOCA1lW1PsJBgi0l1Azz1iJ -3ZExByaafn+Ncd4J3E/la6MQcdRQJEOgHPUq2Rr749bxvGtCZxa0OzH07AN9eOJk -FldtunwM4RSyfHVOKwk+huSqzH5cK72NJk1JNnSG/sWmFUqv6LRO1fLhWcL7fsPE -8WPYwrCarjGWkMMJ0M4uMZDXg92sMcz3h0EIkjMoUvotnq2uap/Dvs7BpuQWL2k0 -QIa2ECEOMWmBnvz9wwYlZTfT2UoghKrnDmB8vz+IZ6zljOBh1g== ------END CERTIFICATE----- -End - -TestOpenURISSL::SERVER_KEY = <<'End' -RSA Private-Key: (2048 bit, 2 primes) -modulus: - 00:cb:b3:71:95:12:70:fc:db:d4:a9:a7:66:d6:d3: - 09:dd:06:80:19:e1:f2:d6:1e:31:b6:6b:20:75:51: - dc:a7:37:a9:ac:5b:57:5d:69:36:b6:de:1d:2c:f6: - 44:64:f8:e8:d6:f0:da:38:6a:ba:c2:b1:9e:dc:bb: - 79:94:e0:25:0c:ce:76:87:17:5d:79:9e:14:9e:bd: - 4c:0d:aa:74:10:3a:96:ef:76:82:d5:72:16:b5:c1: - ac:17:2d:90:83:73:5c:d7:a6:f5:36:0f:4c:55:f3: - 30:5d:19:dc:01:0e:f8:e6:fe:a5:ad:52:88:59:dc: - 4a:07:ed:a2:eb:a1:01:63:c4:8a:92:ba:06:80:9b: - 0d:85:f2:9f:f9:70:ac:d7:ad:f0:7a:3f:b8:92:2a: - 33:ca:69:d0:01:65:5d:31:38:1d:f6:1f:b2:17:07: - 7e:ac:88:67:a6:c4:5f:3e:93:94:61:e6:e4:49:9d: - ba:d4:d2:e8:e3:93:d1:66:79:c5:e3:1d:f8:5a:50: - 54:58:3d:04:b0:fd:65:d1:b3:8a:b5:8a:30:5f:b2: - dc:34:1a:14:f7:74:4c:03:29:97:63:5a:d7:de:bb: - eb:7f:4a:2a:90:59:c0:2b:47:09:82:8f:75:de:14: - 3f:bc:78:9a:69:25:80:5b:6c:a0:65:12:0d:29:61: - ac:f9 -publicExponent: 65537 (0x10001) -privateExponent: - 12:be:d5:b2:01:3b:72:99:8c:4d:7c:81:43:3d:b2: - 87:ab:84:78:5d:49:aa:98:a6:bc:81:c9:3f:e2:a3: - aa:a3:bd:b2:85:c9:59:68:48:47:b5:d2:fb:83:42: - 32:04:91:f0:cd:c3:57:33:c3:32:0d:84:70:0d:b4: - 97:95:b4:f3:23:c0:d6:97:b8:db:6b:47:bc:7f:f1: - 12:c4:df:df:6a:74:df:5e:89:95:b8:e5:0c:1e:e1: - 86:54:84:1b:04:af:c3:8c:b2:be:21:d4:45:88:96: - a7:ca:ac:6b:50:84:69:45:7f:db:9e:5f:bb:dd:40: - d6:cf:f0:91:3c:84:d3:38:65:c9:15:f7:9e:37:aa: - 1a:2e:bc:16:b6:95:be:bc:af:45:76:ba:ad:99:f6: - ef:6a:e8:fd:f0:31:89:19:c4:04:67:a1:ec:c4:79: - 59:08:77:ab:0b:65:88:88:02:b1:38:5c:80:4e:27: - 78:b2:a5:bd:b5:ad:d5:9c:4c:ea:ad:db:05:56:25: - 70:28:da:22:fb:d8:de:8c:3b:78:fe:3e:cf:ed:1b: - f9:97:c6:b6:4a:bf:60:08:8f:dc:85:5e:b1:49:ab: - 87:8b:68:72:f4:6a:3f:bc:db:a3:6c:f7:e8:b0:15: - bb:4b:ba:37:49:a2:d1:7c:f8:4f:1b:05:11:22:d9: - 81 -prime1: - 00:fb:d2:cb:14:61:00:c1:7a:83:ba:fe:79:97:a2: - 4d:5a:ea:40:78:96:6e:d2:be:71:5b:c6:2c:1f:c9: - 18:48:6b:ae:20:86:87:b5:08:0b:17:69:ca:93:cd: - 00:36:22:51:7b:d5:2d:8c:0c:0e:de:bc:86:a8:07: - 0e:c5:57:e4:df:be:ed:7d:cc:b1:a4:d6:a8:2b:00: - 65:2a:69:30:5e:dc:6d:6d:c4:c8:7e:20:34:eb:6f: - 5e:cf:b3:b8:2e:8d:56:31:44:a8:17:ea:be:65:19: - ff:da:14:e0:0c:73:56:14:08:47:4c:5b:79:51:74: - 5d:bc:e7:fe:01:2f:55:27:69 -prime2: - 00:cf:14:54:47:bb:5f:5d:d6:2b:2d:ed:a6:8a:6f: - 36:fc:47:5e:9f:84:ae:aa:1f:f8:44:50:91:15:f5: - ed:9d:29:d9:2b:2a:19:66:56:2e:96:15:b5:8e:a9: - 7f:89:27:21:b5:57:55:7e:2a:c5:8c:93:fe:f6:0a: - a5:17:15:91:91:b3:7d:35:1a:d5:9a:2e:b8:0d:ad: - e6:97:6d:83:a3:27:29:ee:00:74:ef:57:34:f3:07: - ad:12:43:37:0c:5c:b7:26:34:bc:4e:3a:43:65:6b: - 0c:b8:23:ac:77:fd:b2:23:eb:7b:65:70:f6:96:c4: - 17:2c:aa:24:b8:a5:5e:b7:11 -exponent1: - 00:92:32:ae:f4:05:dd:0a:76:b6:43:b9:b9:9d:ee: - fc:39:ec:05:c1:fc:94:1a:85:b6:0a:31:e3:2c:10: - f3:a8:17:db:df:c6:3a:c3:3f:08:31:6f:99:cc:75: - 17:ca:55:e2:38:a2:6a:ef:03:91:1e:7f:15:2e:37: - ea:bb:67:6b:d8:fa:5f:a6:c9:4f:d9:03:46:5e:b0: - bc:0b:03:46:b1:cc:07:3b:d3:23:13:16:5f:a2:cf: - e5:9b:70:1b:5d:eb:70:3e:ea:3d:2c:a5:7c:23:f6: - 14:33:e8:2a:ab:0f:ca:c9:96:84:ce:2f:cd:1f:1d: - 0f:ce:bc:61:1b:0e:ff:c1:01 -exponent2: - 00:9e:0b:f3:03:48:73:d1:e7:9a:cf:13:f9:ae:e0: - 91:03:dc:e8:d0:30:f1:2a:30:fa:48:11:81:9a:54: - 37:c5:62:e2:37:fa:8a:a6:3b:92:94:c3:fe:ec:e2: - 5a:cf:70:09:5f:21:47:c3:e2:9b:21:de:f6:92:0c: - af:d1:bd:89:7b:bd:95:0b:49:ee:cb:1d:6b:26:2d: - 9a:b7:ea:42:b4:ec:38:29:49:39:f6:4e:05:c0:93: - 14:39:c3:09:29:ab:3d:b1:b0:40:24:28:7d:b5:d3: - 0d:43:21:1f:09:f9:9b:d3:a4:6f:6a:8d:db:f6:57: - b5:24:46:bb:7e:1d:e0:fb:31 -coefficient: - 10:93:1d:c8:33:a5:c1:d3:84:6a:22:68:e5:60:cc: - 9c:27:0a:52:0b:58:a3:0c:83:f4:f4:46:09:0c:a1: - 41:a6:ea:bf:80:9d:0e:5d:d8:3d:25:00:c5:a1:35: - 7a:8c:ea:95:16:94:c3:7c:8f:2b:e0:53:ea:66:ae: - 19:be:55:04:3d:ee:e2:4b:a8:69:1b:7e:d8:09:7f: - ed:7c:ee:95:88:10:dc:4b:5b:bf:81:a4:e8:dc:7e: - 4f:e5:c3:90:c4:e5:5a:90:10:32:d6:08:b5:1f:5d: - 09:18:d8:44:28:e4:c4:c7:07:75:9b:9b:b3:80:86: - 68:9d:fe:68:f3:4d:db:66 -writing RSA key ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAy7NxlRJw/NvUqadm1tMJ3QaAGeHy1h4xtmsgdVHcpzeprFtX -XWk2tt4dLPZEZPjo1vDaOGq6wrGe3Lt5lOAlDM52hxddeZ4Unr1MDap0EDqW73aC -1XIWtcGsFy2Qg3Nc16b1Ng9MVfMwXRncAQ745v6lrVKIWdxKB+2i66EBY8SKkroG -gJsNhfKf+XCs163wej+4kiozymnQAWVdMTgd9h+yFwd+rIhnpsRfPpOUYebkSZ26 -1NLo45PRZnnF4x34WlBUWD0EsP1l0bOKtYowX7LcNBoU93RMAymXY1rX3rvrf0oq -kFnAK0cJgo913hQ/vHiaaSWAW2ygZRINKWGs+QIDAQABAoIBABK+1bIBO3KZjE18 -gUM9soerhHhdSaqYpryByT/io6qjvbKFyVloSEe10vuDQjIEkfDNw1czwzINhHAN -tJeVtPMjwNaXuNtrR7x/8RLE399qdN9eiZW45Qwe4YZUhBsEr8OMsr4h1EWIlqfK -rGtQhGlFf9ueX7vdQNbP8JE8hNM4ZckV9543qhouvBa2lb68r0V2uq2Z9u9q6P3w -MYkZxARnoezEeVkId6sLZYiIArE4XIBOJ3iypb21rdWcTOqt2wVWJXAo2iL72N6M -O3j+Ps/tG/mXxrZKv2AIj9yFXrFJq4eLaHL0aj+826Ns9+iwFbtLujdJotF8+E8b -BREi2YECgYEA+9LLFGEAwXqDuv55l6JNWupAeJZu0r5xW8YsH8kYSGuuIIaHtQgL -F2nKk80ANiJRe9UtjAwO3ryGqAcOxVfk377tfcyxpNaoKwBlKmkwXtxtbcTIfiA0 -629ez7O4Lo1WMUSoF+q+ZRn/2hTgDHNWFAhHTFt5UXRdvOf+AS9VJ2kCgYEAzxRU -R7tfXdYrLe2mim82/Eden4Suqh/4RFCRFfXtnSnZKyoZZlYulhW1jql/iSchtVdV -firFjJP+9gqlFxWRkbN9NRrVmi64Da3ml22Doycp7gB071c08wetEkM3DFy3JjS8 -TjpDZWsMuCOsd/2yI+t7ZXD2lsQXLKokuKVetxECgYEAkjKu9AXdCna2Q7m5ne78 -OewFwfyUGoW2CjHjLBDzqBfb38Y6wz8IMW+ZzHUXylXiOKJq7wORHn8VLjfqu2dr -2PpfpslP2QNGXrC8CwNGscwHO9MjExZfos/lm3AbXetwPuo9LKV8I/YUM+gqqw/K -yZaEzi/NHx0PzrxhGw7/wQECgYEAngvzA0hz0eeazxP5ruCRA9zo0DDxKjD6SBGB -mlQ3xWLiN/qKpjuSlMP+7OJaz3AJXyFHw+KbId72kgyv0b2Je72VC0nuyx1rJi2a -t+pCtOw4KUk59k4FwJMUOcMJKas9sbBAJCh9tdMNQyEfCfmb06Rvao3b9le1JEa7 -fh3g+zECgYAQkx3IM6XB04RqImjlYMycJwpSC1ijDIP09EYJDKFBpuq/gJ0OXdg9 -JQDFoTV6jOqVFpTDfI8r4FPqZq4ZvlUEPe7iS6hpG37YCX/tfO6ViBDcS1u/gaTo -3H5P5cOQxOVakBAy1gi1H10JGNhEKOTExwd1m5uzgIZonf5o803bZg== ------END RSA PRIVATE KEY----- -End - -TestOpenURISSL::DHPARAMS = <<'End' - DH Parameters: (2048 bit) - prime: - 00:ec:4e:a4:06:b6:22:ca:f9:8a:00:cc:d0:ee:2f: - 16:bf:05:64:f5:8f:fe:7f:c4:bb:b0:24:cd:ef:5d: - 8a:90:ad:dc:a9:dd:63:84:90:d8:25:ba:d8:78:d5: - 77:91:42:0a:84:fc:56:1e:13:9b:1c:aa:43:d5:1f: - 38:52:92:fe:b3:66:f9:e7:e8:8c:77:a1:a6:2f:b3: - 98:98:d2:13:fc:57:1c:2a:14:dc:bd:e6:9b:54:19: - 99:4f:ce:81:64:a6:32:7f:8e:61:50:5f:45:3a:e5: - 0c:f7:13:f3:b8:ad:d5:77:ca:09:42:f7:d8:30:27: - 7b:2c:f0:b4:b5:a0:04:96:34:0b:47:81:1d:7f:c1: - 3a:62:86:8e:7d:f8:13:7f:9a:b1:8b:09:23:9e:55: - 59:41:cd:f0:86:09:c4:b7:d1:69:54:cb:d0:f5:e9: - 27:c9:e1:81:e4:a1:df:6b:20:1c:df:e8:54:02:f2: - 37:fc:2a:f7:d5:b3:6f:79:7e:70:22:78:79:18:3c: - 75:14:68:4a:05:9f:ac:d4:7f:9a:79:db:9d:0a:6e: - ec:0a:04:70:bf:c9:4a:59:81:a2:1f:33:9b:4a:66: - bc:03:ce:8a:1b:e3:03:ec:ba:39:26:ab:90:dc:39: - 41:a1:d8:f7:20:3c:8f:af:12:2f:f7:a9:6f:44:f1: - 6d:03 - generator: 2 (0x2) ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY -JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab -VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 -YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 -1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD -7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== ------END DH PARAMETERS----- -End - -end diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index 3ae1959..2481bf8 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -222,6 +222,65 @@ def [](key) end end +class SimpleHTTPSServer + def initialize(cert, key, dh, bind_addr, port, log) + @cert = cert + @key = key + @dh = dh + @bind_addr = bind_addr + @port = port + @log = log + @server = TCPServer.new(@bind_addr, @port) + context = OpenSSL::SSL::SSLContext.new + context.cert = @cert + context.key = @key + context.tmp_dh_callback = proc { @dh } + @ssl_server = OpenSSL::SSL::SSLServer.new(@server, context) + end + + def start + @thread = Thread.new do + loop do + ssl_socket = @ssl_server.accept + handle_request(ssl_socket) + ssl_socket.close + rescue OpenSSL::SSL::SSLError => e + @log << "ERROR OpenSSL::SSL::SSLError" + raise e + end + end + end + + def shutdown + @thread.kill + @server.close + end + + def handle_request(socket) + request_line = socket.gets + return if request_line.nil? || request_line.strip.empty? + + method, path, version = request_line.split + headers = {} + while (line = socket.gets) + break if line.strip.empty? + key, value = line.split(': ', 2) + headers[key] = value.strip + end + + response = case path + when '/data' + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 3\r\n\r\nddd" + when "/proxy" + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nproxy" + else + "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n" + end + + socket.print(response) + end +end + module TestOpenURIUtils class Unauthorized < StandardError; end class ProxyAuthenticationRequired < StandardError; end @@ -249,4 +308,369 @@ def with_http(log_tester=lambda {|log| assert_equal([], log) }) } assert_join_threads([client_thread, server_thread2]) end -end \ No newline at end of file + + def with_https(log_tester=lambda {|log| assert_equal([], log) }) + log = [] + Dir.mktmpdir {|dr| + cert = OpenSSL::X509::Certificate.new(SERVER_CERT) + key = OpenSSL::PKey::RSA.new(SERVER_KEY) + dh = OpenSSL::PKey::DH.new(DHPARAMS) + srv = SimpleHTTPSServer.new(cert, key, dh, '127.0.0.1', 0, log) + _, port, _, host = srv.instance_variable_get(:@server).addr + threads = [] + server_thread = srv.start + threads << Thread.new { + server_thread.join + if log_tester + log_tester.call(log) + end + } + threads << Thread.new { + begin + yield srv, dr, "https://#{host}:#{port}", server_thread, log, threads + ensure + srv.shutdown + end + } + assert_join_threads(threads) + } + end if defined?(OpenSSL::SSL) +end + +if defined?(OpenSSL::SSL) +# cp /etc/ssl/openssl.cnf . # I copied from OpenSSL 1.1.1b source + +# mkdir demoCA demoCA/private demoCA/newcerts +# touch demoCA/index.txt +# echo 00 > demoCA/serial +# openssl genrsa -des3 -out demoCA/private/cakey.pem 2048 +# openssl req -new -key demoCA/private/cakey.pem -out demoCA/careq.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=Ruby Test CA" +# # basicConstraints=CA:TRUE is required; the default openssl.cnf has it in [v3_ca] +# openssl ca -config openssl.cnf -extensions v3_ca -out demoCA/cacert.pem -startdate 090101000000Z -enddate 491231235959Z -batch -keyfile demoCA/private/cakey.pem -selfsign -infiles demoCA/careq.pem + +# mkdir server +# openssl genrsa -des3 -out server/server.key 2048 +# openssl req -new -key server/server.key -out server/csr.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=127.0.0.1" +# openssl ca -config openssl.cnf -startdate 090101000000Z -enddate 491231235959Z -in server/csr.pem -keyfile demoCA/private/cakey.pem -cert demoCA/cacert.pem -out server/cert.pem + +# demoCA/cacert.pem => TestOpenURISSL::CA_CERT +# server/cert.pem => TestOpenURISSL::SERVER_CERT +# `openssl rsa -in server/server.key -text` => TestOpenURISSL::SERVER_KEY + +TestOpenURIUtils::CA_CERT = <<'End' +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA + Validity + Not Before: Jan 1 00:00:00 2009 GMT + Not After : Dec 31 23:59:59 2049 GMT + Subject: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:ad:f3:4d:5b:0b:01:54:cc:86:36:d1:93:6b:33: + 56:25:90:61:d6:9a:a0:f4:24:20:ee:c8:14:ab:0f: + 4b:89:d8:7c:bb:c0:f8:7f:fb:e9:a2:d5:1c:6b:6f: + dc:5c:23:b1:49:aa:2c:e8:ca:43:48:64:69:4b:8a: + bd:44:57:9b:14:d9:7a:b2:49:00:d6:c2:74:67:62: + 52:1d:a9:32:df:fe:7a:22:20:49:83:e1:cb:3d:dc: + 1a:2a:f0:36:20:c1:e8:c8:89:d4:51:1a:68:91:20: + e0:ba:67:0a:b2:6b:f8:e3:8c:f5:ee:a1:36:b1:89: + ec:23:b6:f2:39:a9:b9:2e:ea:de:d9:86:e5:42:11: + 46:ed:10:9a:90:76:44:4e:4d:49:2d:49:e8:e3:cb: + ff:7a:7d:80:cb:bf:c4:c3:69:ba:9c:60:4a:de:af: + bf:26:78:b8:fb:46:d1:37:d0:89:ba:78:93:6a:37: + a5:e9:58:e7:e2:e3:7d:7c:95:20:79:41:56:15:cd: + b2:c6:3b:e1:b7:e7:ba:47:60:9a:05:b1:07:f3:26: + 72:9d:3b:1b:02:18:3d:d5:de:e6:e9:30:a9:b5:8f: + 15:1b:40:f9:64:61:54:d3:53:e8:c4:29:4a:89:f3: + e5:0d:fd:16:61:ee:f2:6d:8a:45:a8:34:7e:53:46: + 8e:87 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + A0:7E:0B:AD:A3:AD:37:D7:21:0B:75:6F:8A:90:5F:8C:C9:69:DF:98 + X509v3 Authority Key Identifier: + keyid:A0:7E:0B:AD:A3:AD:37:D7:21:0B:75:6F:8A:90:5F:8C:C9:69:DF:98 + + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + 06:ea:06:02:19:9a:cb:94:a2:7e:c0:86:71:66:e7:a5:71:46: + a2:25:55:f5:e5:58:df:d1:91:58:e6:8a:0e:91:b3:22:4c:88: + 4d:5f:02:af:0f:73:65:0d:af:9a:f2:e4:36:f3:1f:e8:28:1d: + 9c:74:72:5b:f7:12:e8:fa:45:d6:df:e5:f1:d3:91:f4:0e:db: + e2:56:63:ee:82:57:6f:12:ad:d7:0d:de:5a:8c:3d:76:d2:87: + c9:48:1c:c4:f3:89:63:3c:c2:25:e0:dd:63:a6:4c:6c:5a:07: + 7b:86:78:62:86:02:a1:ef:0e:41:75:c5:d4:61:ab:c3:3b:9b: + 51:0b:e6:34:6d:0b:14:5a:2d:aa:d3:58:26:43:8f:4c:d7:45: + 73:1e:67:66:5e:f3:0c:69:70:27:a1:d5:70:f3:5a:10:98:c8: + 4f:8a:3b:9f:ad:8e:8d:49:8f:fb:f6:36:5d:4f:70:f9:4f:54: + 33:cf:a2:a6:1d:8c:61:b9:30:42:f2:49:d1:3d:a1:f1:eb:1e: + 78:a6:30:f8:8a:48:89:c7:3e:bd:0d:d8:72:04:a6:00:e5:62: + a4:13:3f:9e:b6:86:25:dc:d1:ff:3a:fc:f5:0e:e4:0e:f7:b8: + 66:90:fe:4f:c2:54:2a:7f:61:6e:e7:4b:bf:40:7e:75:30:02: + 5b:bb:91:1b +-----BEGIN CERTIFICATE----- +MIIDXDCCAkSgAwIBAgIBADANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJKUDEO +MAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRUwEwYDVQQDDAxSdWJ5 +IFRlc3QgQ0EwHhcNMDkwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBHMQswCQYD +VQQGEwJKUDEOMAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRUwEwYD +VQQDDAxSdWJ5IFRlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCt801bCwFUzIY20ZNrM1YlkGHWmqD0JCDuyBSrD0uJ2Hy7wPh/++mi1Rxrb9xc +I7FJqizoykNIZGlLir1EV5sU2XqySQDWwnRnYlIdqTLf/noiIEmD4cs93Boq8DYg +wejIidRRGmiRIOC6Zwqya/jjjPXuoTaxiewjtvI5qbku6t7ZhuVCEUbtEJqQdkRO +TUktSejjy/96fYDLv8TDabqcYErer78meLj7RtE30Im6eJNqN6XpWOfi4318lSB5 +QVYVzbLGO+G357pHYJoFsQfzJnKdOxsCGD3V3ubpMKm1jxUbQPlkYVTTU+jEKUqJ +8+UN/RZh7vJtikWoNH5TRo6HAgMBAAGjUzBRMB0GA1UdDgQWBBSgfguto6031yEL +dW+KkF+MyWnfmDAfBgNVHSMEGDAWgBSgfguto6031yELdW+KkF+MyWnfmDAPBgNV +HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAG6gYCGZrLlKJ+wIZxZuel +cUaiJVX15Vjf0ZFY5ooOkbMiTIhNXwKvD3NlDa+a8uQ28x/oKB2cdHJb9xLo+kXW +3+Xx05H0DtviVmPugldvEq3XDd5ajD120ofJSBzE84ljPMIl4N1jpkxsWgd7hnhi +hgKh7w5BdcXUYavDO5tRC+Y0bQsUWi2q01gmQ49M10VzHmdmXvMMaXAnodVw81oQ +mMhPijufrY6NSY/79jZdT3D5T1Qzz6KmHYxhuTBC8knRPaHx6x54pjD4ikiJxz69 +DdhyBKYA5WKkEz+etoYl3NH/Ovz1DuQO97hmkP5PwlQqf2Fu50u/QH51MAJbu5Eb +-----END CERTIFICATE----- +End + +TestOpenURIUtils::SERVER_CERT = <<'End' +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA + Validity + Not Before: Jan 1 00:00:00 2009 GMT + Not After : Dec 31 23:59:59 2049 GMT + Subject: C=JP, ST=Tokyo, O=RubyTest, CN=127.0.0.1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:cb:b3:71:95:12:70:fc:db:d4:a9:a7:66:d6:d3: + 09:dd:06:80:19:e1:f2:d6:1e:31:b6:6b:20:75:51: + dc:a7:37:a9:ac:5b:57:5d:69:36:b6:de:1d:2c:f6: + 44:64:f8:e8:d6:f0:da:38:6a:ba:c2:b1:9e:dc:bb: + 79:94:e0:25:0c:ce:76:87:17:5d:79:9e:14:9e:bd: + 4c:0d:aa:74:10:3a:96:ef:76:82:d5:72:16:b5:c1: + ac:17:2d:90:83:73:5c:d7:a6:f5:36:0f:4c:55:f3: + 30:5d:19:dc:01:0e:f8:e6:fe:a5:ad:52:88:59:dc: + 4a:07:ed:a2:eb:a1:01:63:c4:8a:92:ba:06:80:9b: + 0d:85:f2:9f:f9:70:ac:d7:ad:f0:7a:3f:b8:92:2a: + 33:ca:69:d0:01:65:5d:31:38:1d:f6:1f:b2:17:07: + 7e:ac:88:67:a6:c4:5f:3e:93:94:61:e6:e4:49:9d: + ba:d4:d2:e8:e3:93:d1:66:79:c5:e3:1d:f8:5a:50: + 54:58:3d:04:b0:fd:65:d1:b3:8a:b5:8a:30:5f:b2: + dc:34:1a:14:f7:74:4c:03:29:97:63:5a:d7:de:bb: + eb:7f:4a:2a:90:59:c0:2b:47:09:82:8f:75:de:14: + 3f:bc:78:9a:69:25:80:5b:6c:a0:65:12:0d:29:61: + ac:f9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + EC:6B:7C:79:B8:3B:11:1D:42:F3:9A:2A:CF:9A:15:59:D7:F9:D8:C6 + X509v3 Authority Key Identifier: + keyid:A0:7E:0B:AD:A3:AD:37:D7:21:0B:75:6F:8A:90:5F:8C:C9:69:DF:98 + + Signature Algorithm: sha256WithRSAEncryption + 29:14:db:71:e9:a0:86:f8:cc:4d:e4:8a:76:78:a7:ff:4e:94: + b4:4d:92:dc:57:9a:52:64:46:27:15:8b:4f:2a:18:a7:0d:fc: + d2:75:ce:4e:49:97:0b:46:71:57:23:e3:a5:c0:c5:71:94:fc: + f2:1d:3b:06:93:82:03:59:56:d4:fb:09:06:08:b4:97:50:33: + cf:58:89:dd:91:31:07:26:9a:7e:7f:8d:71:de:09:dc:4f:e5: + 6b:a3:10:71:d4:50:24:43:a0:1c:f5:2a:d9:1a:fb:e3:d6:f1: + bc:6b:42:67:16:b4:3b:31:f4:ec:03:7d:78:e2:64:16:57:6d: + ba:7c:0c:e1:14:b2:7c:75:4e:2b:09:3e:86:e4:aa:cc:7e:5c: + 2b:bd:8d:26:4d:49:36:74:86:fe:c5:a6:15:4a:af:e8:b4:4e: + d5:f2:e1:59:c2:fb:7e:c3:c4:f1:63:d8:c2:b0:9a:ae:31:96: + 90:c3:09:d0:ce:2e:31:90:d7:83:dd:ac:31:cc:f7:87:41:08: + 92:33:28:52:fa:2d:9e:ad:ae:6a:9f:c3:be:ce:c1:a6:e4:16: + 2f:69:34:40:86:b6:10:21:0e:31:69:81:9e:fc:fd:c3:06:25: + 65:37:d3:d9:4a:20:84:aa:e7:0e:60:7c:bf:3f:88:67:ac:e5: + 8c:e0:61:d6 +-----BEGIN CERTIFICATE----- +MIIDgTCCAmmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJKUDEO +MAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRUwEwYDVQQDDAxSdWJ5 +IFRlc3QgQ0EwHhcNMDkwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBEMQswCQYD +VQQGEwJKUDEOMAwGA1UECAwFVG9reW8xETAPBgNVBAoMCFJ1YnlUZXN0MRIwEAYD +VQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL +s3GVEnD829Spp2bW0wndBoAZ4fLWHjG2ayB1UdynN6msW1ddaTa23h0s9kRk+OjW +8No4arrCsZ7cu3mU4CUMznaHF115nhSevUwNqnQQOpbvdoLVcha1wawXLZCDc1zX +pvU2D0xV8zBdGdwBDvjm/qWtUohZ3EoH7aLroQFjxIqSugaAmw2F8p/5cKzXrfB6 +P7iSKjPKadABZV0xOB32H7IXB36siGemxF8+k5Rh5uRJnbrU0ujjk9FmecXjHfha +UFRYPQSw/WXRs4q1ijBfstw0GhT3dEwDKZdjWtfeu+t/SiqQWcArRwmCj3XeFD+8 +eJppJYBbbKBlEg0pYaz5AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgEN +BB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTsa3x5 +uDsRHULzmirPmhVZ1/nYxjAfBgNVHSMEGDAWgBSgfguto6031yELdW+KkF+MyWnf +mDANBgkqhkiG9w0BAQsFAAOCAQEAKRTbcemghvjMTeSKdnin/06UtE2S3FeaUmRG +JxWLTyoYpw380nXOTkmXC0ZxVyPjpcDFcZT88h07BpOCA1lW1PsJBgi0l1Azz1iJ +3ZExByaafn+Ncd4J3E/la6MQcdRQJEOgHPUq2Rr749bxvGtCZxa0OzH07AN9eOJk +FldtunwM4RSyfHVOKwk+huSqzH5cK72NJk1JNnSG/sWmFUqv6LRO1fLhWcL7fsPE +8WPYwrCarjGWkMMJ0M4uMZDXg92sMcz3h0EIkjMoUvotnq2uap/Dvs7BpuQWL2k0 +QIa2ECEOMWmBnvz9wwYlZTfT2UoghKrnDmB8vz+IZ6zljOBh1g== +-----END CERTIFICATE----- +End + +TestOpenURIUtils::SERVER_KEY = <<'End' +RSA Private-Key: (2048 bit, 2 primes) +modulus: + 00:cb:b3:71:95:12:70:fc:db:d4:a9:a7:66:d6:d3: + 09:dd:06:80:19:e1:f2:d6:1e:31:b6:6b:20:75:51: + dc:a7:37:a9:ac:5b:57:5d:69:36:b6:de:1d:2c:f6: + 44:64:f8:e8:d6:f0:da:38:6a:ba:c2:b1:9e:dc:bb: + 79:94:e0:25:0c:ce:76:87:17:5d:79:9e:14:9e:bd: + 4c:0d:aa:74:10:3a:96:ef:76:82:d5:72:16:b5:c1: + ac:17:2d:90:83:73:5c:d7:a6:f5:36:0f:4c:55:f3: + 30:5d:19:dc:01:0e:f8:e6:fe:a5:ad:52:88:59:dc: + 4a:07:ed:a2:eb:a1:01:63:c4:8a:92:ba:06:80:9b: + 0d:85:f2:9f:f9:70:ac:d7:ad:f0:7a:3f:b8:92:2a: + 33:ca:69:d0:01:65:5d:31:38:1d:f6:1f:b2:17:07: + 7e:ac:88:67:a6:c4:5f:3e:93:94:61:e6:e4:49:9d: + ba:d4:d2:e8:e3:93:d1:66:79:c5:e3:1d:f8:5a:50: + 54:58:3d:04:b0:fd:65:d1:b3:8a:b5:8a:30:5f:b2: + dc:34:1a:14:f7:74:4c:03:29:97:63:5a:d7:de:bb: + eb:7f:4a:2a:90:59:c0:2b:47:09:82:8f:75:de:14: + 3f:bc:78:9a:69:25:80:5b:6c:a0:65:12:0d:29:61: + ac:f9 +publicExponent: 65537 (0x10001) +privateExponent: + 12:be:d5:b2:01:3b:72:99:8c:4d:7c:81:43:3d:b2: + 87:ab:84:78:5d:49:aa:98:a6:bc:81:c9:3f:e2:a3: + aa:a3:bd:b2:85:c9:59:68:48:47:b5:d2:fb:83:42: + 32:04:91:f0:cd:c3:57:33:c3:32:0d:84:70:0d:b4: + 97:95:b4:f3:23:c0:d6:97:b8:db:6b:47:bc:7f:f1: + 12:c4:df:df:6a:74:df:5e:89:95:b8:e5:0c:1e:e1: + 86:54:84:1b:04:af:c3:8c:b2:be:21:d4:45:88:96: + a7:ca:ac:6b:50:84:69:45:7f:db:9e:5f:bb:dd:40: + d6:cf:f0:91:3c:84:d3:38:65:c9:15:f7:9e:37:aa: + 1a:2e:bc:16:b6:95:be:bc:af:45:76:ba:ad:99:f6: + ef:6a:e8:fd:f0:31:89:19:c4:04:67:a1:ec:c4:79: + 59:08:77:ab:0b:65:88:88:02:b1:38:5c:80:4e:27: + 78:b2:a5:bd:b5:ad:d5:9c:4c:ea:ad:db:05:56:25: + 70:28:da:22:fb:d8:de:8c:3b:78:fe:3e:cf:ed:1b: + f9:97:c6:b6:4a:bf:60:08:8f:dc:85:5e:b1:49:ab: + 87:8b:68:72:f4:6a:3f:bc:db:a3:6c:f7:e8:b0:15: + bb:4b:ba:37:49:a2:d1:7c:f8:4f:1b:05:11:22:d9: + 81 +prime1: + 00:fb:d2:cb:14:61:00:c1:7a:83:ba:fe:79:97:a2: + 4d:5a:ea:40:78:96:6e:d2:be:71:5b:c6:2c:1f:c9: + 18:48:6b:ae:20:86:87:b5:08:0b:17:69:ca:93:cd: + 00:36:22:51:7b:d5:2d:8c:0c:0e:de:bc:86:a8:07: + 0e:c5:57:e4:df:be:ed:7d:cc:b1:a4:d6:a8:2b:00: + 65:2a:69:30:5e:dc:6d:6d:c4:c8:7e:20:34:eb:6f: + 5e:cf:b3:b8:2e:8d:56:31:44:a8:17:ea:be:65:19: + ff:da:14:e0:0c:73:56:14:08:47:4c:5b:79:51:74: + 5d:bc:e7:fe:01:2f:55:27:69 +prime2: + 00:cf:14:54:47:bb:5f:5d:d6:2b:2d:ed:a6:8a:6f: + 36:fc:47:5e:9f:84:ae:aa:1f:f8:44:50:91:15:f5: + ed:9d:29:d9:2b:2a:19:66:56:2e:96:15:b5:8e:a9: + 7f:89:27:21:b5:57:55:7e:2a:c5:8c:93:fe:f6:0a: + a5:17:15:91:91:b3:7d:35:1a:d5:9a:2e:b8:0d:ad: + e6:97:6d:83:a3:27:29:ee:00:74:ef:57:34:f3:07: + ad:12:43:37:0c:5c:b7:26:34:bc:4e:3a:43:65:6b: + 0c:b8:23:ac:77:fd:b2:23:eb:7b:65:70:f6:96:c4: + 17:2c:aa:24:b8:a5:5e:b7:11 +exponent1: + 00:92:32:ae:f4:05:dd:0a:76:b6:43:b9:b9:9d:ee: + fc:39:ec:05:c1:fc:94:1a:85:b6:0a:31:e3:2c:10: + f3:a8:17:db:df:c6:3a:c3:3f:08:31:6f:99:cc:75: + 17:ca:55:e2:38:a2:6a:ef:03:91:1e:7f:15:2e:37: + ea:bb:67:6b:d8:fa:5f:a6:c9:4f:d9:03:46:5e:b0: + bc:0b:03:46:b1:cc:07:3b:d3:23:13:16:5f:a2:cf: + e5:9b:70:1b:5d:eb:70:3e:ea:3d:2c:a5:7c:23:f6: + 14:33:e8:2a:ab:0f:ca:c9:96:84:ce:2f:cd:1f:1d: + 0f:ce:bc:61:1b:0e:ff:c1:01 +exponent2: + 00:9e:0b:f3:03:48:73:d1:e7:9a:cf:13:f9:ae:e0: + 91:03:dc:e8:d0:30:f1:2a:30:fa:48:11:81:9a:54: + 37:c5:62:e2:37:fa:8a:a6:3b:92:94:c3:fe:ec:e2: + 5a:cf:70:09:5f:21:47:c3:e2:9b:21:de:f6:92:0c: + af:d1:bd:89:7b:bd:95:0b:49:ee:cb:1d:6b:26:2d: + 9a:b7:ea:42:b4:ec:38:29:49:39:f6:4e:05:c0:93: + 14:39:c3:09:29:ab:3d:b1:b0:40:24:28:7d:b5:d3: + 0d:43:21:1f:09:f9:9b:d3:a4:6f:6a:8d:db:f6:57: + b5:24:46:bb:7e:1d:e0:fb:31 +coefficient: + 10:93:1d:c8:33:a5:c1:d3:84:6a:22:68:e5:60:cc: + 9c:27:0a:52:0b:58:a3:0c:83:f4:f4:46:09:0c:a1: + 41:a6:ea:bf:80:9d:0e:5d:d8:3d:25:00:c5:a1:35: + 7a:8c:ea:95:16:94:c3:7c:8f:2b:e0:53:ea:66:ae: + 19:be:55:04:3d:ee:e2:4b:a8:69:1b:7e:d8:09:7f: + ed:7c:ee:95:88:10:dc:4b:5b:bf:81:a4:e8:dc:7e: + 4f:e5:c3:90:c4:e5:5a:90:10:32:d6:08:b5:1f:5d: + 09:18:d8:44:28:e4:c4:c7:07:75:9b:9b:b3:80:86: + 68:9d:fe:68:f3:4d:db:66 +writing RSA key +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAy7NxlRJw/NvUqadm1tMJ3QaAGeHy1h4xtmsgdVHcpzeprFtX +XWk2tt4dLPZEZPjo1vDaOGq6wrGe3Lt5lOAlDM52hxddeZ4Unr1MDap0EDqW73aC +1XIWtcGsFy2Qg3Nc16b1Ng9MVfMwXRncAQ745v6lrVKIWdxKB+2i66EBY8SKkroG +gJsNhfKf+XCs163wej+4kiozymnQAWVdMTgd9h+yFwd+rIhnpsRfPpOUYebkSZ26 +1NLo45PRZnnF4x34WlBUWD0EsP1l0bOKtYowX7LcNBoU93RMAymXY1rX3rvrf0oq +kFnAK0cJgo913hQ/vHiaaSWAW2ygZRINKWGs+QIDAQABAoIBABK+1bIBO3KZjE18 +gUM9soerhHhdSaqYpryByT/io6qjvbKFyVloSEe10vuDQjIEkfDNw1czwzINhHAN +tJeVtPMjwNaXuNtrR7x/8RLE399qdN9eiZW45Qwe4YZUhBsEr8OMsr4h1EWIlqfK +rGtQhGlFf9ueX7vdQNbP8JE8hNM4ZckV9543qhouvBa2lb68r0V2uq2Z9u9q6P3w +MYkZxARnoezEeVkId6sLZYiIArE4XIBOJ3iypb21rdWcTOqt2wVWJXAo2iL72N6M +O3j+Ps/tG/mXxrZKv2AIj9yFXrFJq4eLaHL0aj+826Ns9+iwFbtLujdJotF8+E8b +BREi2YECgYEA+9LLFGEAwXqDuv55l6JNWupAeJZu0r5xW8YsH8kYSGuuIIaHtQgL +F2nKk80ANiJRe9UtjAwO3ryGqAcOxVfk377tfcyxpNaoKwBlKmkwXtxtbcTIfiA0 +629ez7O4Lo1WMUSoF+q+ZRn/2hTgDHNWFAhHTFt5UXRdvOf+AS9VJ2kCgYEAzxRU +R7tfXdYrLe2mim82/Eden4Suqh/4RFCRFfXtnSnZKyoZZlYulhW1jql/iSchtVdV +firFjJP+9gqlFxWRkbN9NRrVmi64Da3ml22Doycp7gB071c08wetEkM3DFy3JjS8 +TjpDZWsMuCOsd/2yI+t7ZXD2lsQXLKokuKVetxECgYEAkjKu9AXdCna2Q7m5ne78 +OewFwfyUGoW2CjHjLBDzqBfb38Y6wz8IMW+ZzHUXylXiOKJq7wORHn8VLjfqu2dr +2PpfpslP2QNGXrC8CwNGscwHO9MjExZfos/lm3AbXetwPuo9LKV8I/YUM+gqqw/K +yZaEzi/NHx0PzrxhGw7/wQECgYEAngvzA0hz0eeazxP5ruCRA9zo0DDxKjD6SBGB +mlQ3xWLiN/qKpjuSlMP+7OJaz3AJXyFHw+KbId72kgyv0b2Je72VC0nuyx1rJi2a +t+pCtOw4KUk59k4FwJMUOcMJKas9sbBAJCh9tdMNQyEfCfmb06Rvao3b9le1JEa7 +fh3g+zECgYAQkx3IM6XB04RqImjlYMycJwpSC1ijDIP09EYJDKFBpuq/gJ0OXdg9 +JQDFoTV6jOqVFpTDfI8r4FPqZq4ZvlUEPe7iS6hpG37YCX/tfO6ViBDcS1u/gaTo +3H5P5cOQxOVakBAy1gi1H10JGNhEKOTExwd1m5uzgIZonf5o803bZg== +-----END RSA PRIVATE KEY----- +End + +TestOpenURIUtils::DHPARAMS = <<'End' + DH Parameters: (2048 bit) + prime: + 00:ec:4e:a4:06:b6:22:ca:f9:8a:00:cc:d0:ee:2f: + 16:bf:05:64:f5:8f:fe:7f:c4:bb:b0:24:cd:ef:5d: + 8a:90:ad:dc:a9:dd:63:84:90:d8:25:ba:d8:78:d5: + 77:91:42:0a:84:fc:56:1e:13:9b:1c:aa:43:d5:1f: + 38:52:92:fe:b3:66:f9:e7:e8:8c:77:a1:a6:2f:b3: + 98:98:d2:13:fc:57:1c:2a:14:dc:bd:e6:9b:54:19: + 99:4f:ce:81:64:a6:32:7f:8e:61:50:5f:45:3a:e5: + 0c:f7:13:f3:b8:ad:d5:77:ca:09:42:f7:d8:30:27: + 7b:2c:f0:b4:b5:a0:04:96:34:0b:47:81:1d:7f:c1: + 3a:62:86:8e:7d:f8:13:7f:9a:b1:8b:09:23:9e:55: + 59:41:cd:f0:86:09:c4:b7:d1:69:54:cb:d0:f5:e9: + 27:c9:e1:81:e4:a1:df:6b:20:1c:df:e8:54:02:f2: + 37:fc:2a:f7:d5:b3:6f:79:7e:70:22:78:79:18:3c: + 75:14:68:4a:05:9f:ac:d4:7f:9a:79:db:9d:0a:6e: + ec:0a:04:70:bf:c9:4a:59:81:a2:1f:33:9b:4a:66: + bc:03:ce:8a:1b:e3:03:ec:ba:39:26:ab:90:dc:39: + 41:a1:d8:f7:20:3c:8f:af:12:2f:f7:a9:6f:44:f1: + 6d:03 + generator: 2 (0x2) +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY +JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab +VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 +YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 +1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD +7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== +-----END DH PARAMETERS----- +End + +end From 2e36793bd59dcf774f031cd3ff267f5a52df5ae0 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 16:01:13 +0900 Subject: [PATCH 08/15] support CONNECT method for SimpleHTTPProxy --- test/open-uri/test_proxy.rb | 12 ++++-- test/open-uri/test_ssl.rb | 86 +------------------------------------ test/open-uri/utils.rb | 65 +++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 95 deletions(-) diff --git a/test/open-uri/test_proxy.rb b/test/open-uri/test_proxy.rb index 7fd1fd8..85f23d9 100644 --- a/test/open-uri/test_proxy.rb +++ b/test/open-uri/test_proxy.rb @@ -30,11 +30,12 @@ def teardown def test_proxy with_http {|srv, url| proxy_log = StringIO.new(''.dup) + proxy_access_log = StringIO.new(''.dup) proxy_auth_log = ''.dup proxy_host = '127.0.0.1' proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| proxy_auth_log << req.request_line - }, proxy_log) + }, proxy_log, proxy_access_log) proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://#{proxy_host}:#{proxy_port}/" begin @@ -78,6 +79,7 @@ def test_proxy def test_proxy_http_basic_authentication_failure with_http {|srv, url| proxy_log = StringIO.new(''.dup) + proxy_access_log = StringIO.new(''.dup) proxy_auth_log = ''.dup proxy_host = '127.0.0.1' proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| @@ -85,7 +87,7 @@ def test_proxy_http_basic_authentication_failure if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" raise ProxyAuthenticationRequired end - }, proxy_log) + }, proxy_log, proxy_access_log) proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://#{proxy_host}:#{proxy_port}/" begin @@ -105,6 +107,7 @@ def test_proxy_http_basic_authentication_failure def test_proxy_http_basic_authentication_success with_http {|srv, url| proxy_log = StringIO.new(''.dup) + proxy_access_log = StringIO.new(''.dup) proxy_auth_log = ''.dup proxy_host = '127.0.0.1' proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| @@ -112,7 +115,7 @@ def test_proxy_http_basic_authentication_success if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" raise ProxyAuthenticationRequired end - }, proxy_log) + }, proxy_log, proxy_access_log) proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://#{proxy_host}:#{proxy_port}/" begin @@ -140,6 +143,7 @@ def test_proxy_http_basic_authentication_success def test_authenticated_proxy_http_basic_authentication_success with_http {|srv, url| proxy_log = StringIO.new(''.dup) + proxy_access_log = StringIO.new(''.dup) proxy_auth_log = ''.dup proxy_host = '127.0.0.1' proxy = SimpleHTTPProxyServer.new(proxy_host, 0, lambda {|req, res| @@ -147,7 +151,7 @@ def test_authenticated_proxy_http_basic_authentication_success if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}" raise ProxyAuthenticationRequired end - }, proxy_log) + }, proxy_log, proxy_access_log) proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_url = "http://user:pass@#{proxy_host}:#{proxy_port}/" begin diff --git a/test/open-uri/test_ssl.rb b/test/open-uri/test_ssl.rb index 5cdd0c7..019186f 100644 --- a/test/open-uri/test_ssl.rb +++ b/test/open-uri/test_ssl.rb @@ -3,13 +3,10 @@ require 'open-uri' require 'stringio' require_relative 'utils' -require 'webrick' begin require 'openssl' - require 'webrick/https' rescue LoadError end -require 'webrick/httpproxy' class TestOpenURISSL < Test::Unit::TestCase end @@ -17,47 +14,6 @@ class TestOpenURISSL < Test::Unit::TestCase class TestOpenURISSL include TestOpenURIUtils - NullLog = Object.new - def NullLog.<<(arg) - end - - def with_https_webrick(log_tester=lambda {|log| assert_equal([], log) }) - log = [] - logger = WEBrick::Log.new(log, WEBrick::BasicLog::WARN) - Dir.mktmpdir {|dr| - srv = WEBrick::HTTPServer.new({ - :DocumentRoot => dr, - :ServerType => Thread, - :Logger => logger, - :AccessLog => [[NullLog, ""]], - :SSLEnable => true, - :SSLCertificate => OpenSSL::X509::Certificate.new(SERVER_CERT), - :SSLPrivateKey => OpenSSL::PKey::RSA.new(SERVER_KEY), - :SSLTmpDhCallback => proc { OpenSSL::PKey::DH.new(DHPARAMS) }, - :BindAddress => '127.0.0.1', - :Port => 0}) - _, port, _, host = srv.listeners[0].addr - threads = [] - server_thread = srv.start - threads << Thread.new { - server_thread.join - if log_tester - log_tester.call(log) - end - } - threads << Thread.new { - begin - yield srv, dr, "https://#{host}:#{port}", server_thread, log, threads - ensure - srv.shutdown - end - } - assert_join_threads(threads) - } - ensure - WEBrick::Utils::TimeoutHandler.terminate - end - def setup @proxies = %w[http_proxy HTTP_PROXY https_proxy HTTPS_PROXY ftp_proxy FTP_PROXY no_proxy] @old_proxies = @proxies.map {|k| ENV[k] } @@ -98,15 +54,7 @@ def test_validation_noverify end def test_validation_failure - unless /mswin|mingw/ =~ RUBY_PLATFORM - # on Windows, Errno::ECONNRESET will be raised, and it'll be eaten by - # WEBrick - log_tester = lambda {|server_log| - assert_equal(1, server_log.length) - assert_match(/ERROR OpenSSL::SSL::SSLError:/, server_log[0]) - } - end - with_https_webrick(log_tester) {|srv, dr, url, server_thread, server_log| + with_https(nil) {|srv, dr, url, server_thread, server_log| setup_validation(srv, dr) assert_raise(OpenSSL::SSL::SSLError) { URI.open("#{url}/data") {} } } @@ -131,38 +79,6 @@ def test_bad_ssl_version } end - def with_https_proxy(proxy_log_tester=lambda {|proxy_log, proxy_access_log| assert_equal([], proxy_log) }) - proxy_log = [] - proxy_logger = WEBrick::Log.new(proxy_log, WEBrick::BasicLog::WARN) - with_https {|srv, dr, url, server_thread, server_log, threads| - cacert_filename = "#{dr}/cacert.pem" - open(cacert_filename, "w") {|f| f << CA_CERT } - cacert_directory = "#{dr}/certs" - Dir.mkdir cacert_directory - hashed_name = "%08x.0" % OpenSSL::X509::Certificate.new(CA_CERT).subject.hash - open("#{cacert_directory}/#{hashed_name}", "w") {|f| f << CA_CERT } - proxy = WEBrick::HTTPProxyServer.new({ - :ServerType => Thread, - :Logger => proxy_logger, - :AccessLog => [[proxy_access_log=[], WEBrick::AccessLog::COMMON_LOG_FORMAT]], - :BindAddress => '127.0.0.1', - :Port => 0}) - _, proxy_port, _, proxy_host = proxy.listeners[0].addr - proxy_thread = proxy.start - threads << Thread.new { - proxy_thread.join - if proxy_log_tester - proxy_log_tester.call(proxy_log, proxy_access_log) - end - } - begin - yield srv, dr, url, cacert_filename, cacert_directory, proxy_host, proxy_port - ensure - proxy.shutdown - end - } - end - def test_proxy_cacert_file url = nil proxy_log_tester = lambda {|proxy_log, proxy_access_log| diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index 2481bf8..3334079 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -149,10 +149,11 @@ def status_message(code) end class SimpleHTTPProxyServer - def initialize(host, port, auth_proc = nil, log) + def initialize(host, port, auth_proc = nil, log, access_log) @server = TCPServer.new(host, port) @auth_proc = auth_proc @log = log + @access_log = access_log end def start @@ -169,6 +170,7 @@ def start method, path, _ = request_line.split(' ') handle_request(client, method, path, request_line, headers) + rescue IOError end end end @@ -191,8 +193,12 @@ def handle_request(client, method, path, request_line, headers) end end - uri = URI(path) - proxy_request(uri, client) + if method == 'CONNECT' + proxy_connect(path, client) + else + uri = URI(path) + proxy_request(uri, client) + end rescue TestOpenURIProxy::ProxyAuthenticationRequired @log << "ERROR ProxyAuthenticationRequired" client.print "HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 0\r\n\r\n" @@ -200,6 +206,26 @@ def handle_request(client, method, path, request_line, headers) client.close end + def proxy_connect(path, client) + host, port = path.split(':') + backend = TCPSocket.new(host, port.to_i) + client.puts "HTTP/1.1 200 Connection Established\r\n\r\n" + @access_log << "CONNECT #{path} \n" + begin + while fds = IO.select([client, backend]) + if fds[0].include?(client) + data = client.readpartial(1024) + backend.write(data) + elsif fds[0].include?(backend) + data = backend.readpartial(1024) + client.write(data) + end + end + rescue + backend.close + end + end + def proxy_request(uri, client) Net::HTTP.start(uri.host, uri.port) do |http| response = http.get(uri.path) @@ -244,10 +270,8 @@ def start ssl_socket = @ssl_server.accept handle_request(ssl_socket) ssl_socket.close - rescue OpenSSL::SSL::SSLError => e - @log << "ERROR OpenSSL::SSL::SSLError" - raise e end + rescue OpenSSL::SSL::SSLError end end @@ -309,6 +333,35 @@ def with_http(log_tester=lambda {|log| assert_equal([], log) }) assert_join_threads([client_thread, server_thread2]) end + def with_https_proxy(proxy_log_tester=lambda {|proxy_log, proxy_access_log| assert_equal([], proxy_log) }) + proxy_log = [] + proxy_access_log = [] + with_https {|srv, dr, url, server_thread, server_log, threads| + srv.instance_variable_get(:@server).setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) + cacert_filename = "#{dr}/cacert.pem" + open(cacert_filename, "w") {|f| f << CA_CERT } + cacert_directory = "#{dr}/certs" + Dir.mkdir cacert_directory + hashed_name = "%08x.0" % OpenSSL::X509::Certificate.new(CA_CERT).subject.hash + open("#{cacert_directory}/#{hashed_name}", "w") {|f| f << CA_CERT } + proxy = SimpleHTTPProxyServer.new('127.0.0.1', 0, proxy_log, proxy_access_log) + proxy.start + _, proxy_port, _, proxy_host = proxy.instance_variable_get(:@server).addr + proxy_thread = proxy.start + threads << Thread.new { + proxy_thread.join + if proxy_log_tester + proxy_log_tester.call(proxy_log, proxy_access_log) + end + } + begin + yield srv, dr, url, cacert_filename, cacert_directory, proxy_host, proxy_port + ensure + proxy.shutdown + end + } + end + def with_https(log_tester=lambda {|log| assert_equal([], log) }) log = [] Dir.mktmpdir {|dr| From 15989970b63dd9c5890a0fe9075f3762dae7ed07 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 17:24:41 +0900 Subject: [PATCH 09/15] Don't use URI library --- test/open-uri/utils.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index 3334079..88b0797 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -196,8 +196,7 @@ def handle_request(client, method, path, request_line, headers) if method == 'CONNECT' proxy_connect(path, client) else - uri = URI(path) - proxy_request(uri, client) + proxy_request(path, client) end rescue TestOpenURIProxy::ProxyAuthenticationRequired @log << "ERROR ProxyAuthenticationRequired" @@ -226,9 +225,12 @@ def proxy_connect(path, client) end end - def proxy_request(uri, client) - Net::HTTP.start(uri.host, uri.port) do |http| - response = http.get(uri.path) + def proxy_request(path, client) + path.gsub!(/\Ahttps?:\/\//, '') + host, path = path.split('/') + host, port = host.split(':') + Net::HTTP.start(host, port) do |http| + response = http.get("/#{path}") client.print "HTTP/1.1 #{response.code}\r\nContent-Type: #{response.content_type}\r\n\r\n#{response.body}" end end From ab0e91699718f595362af1aa7d91ed8aa582e3c2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 17:25:42 +0900 Subject: [PATCH 10/15] Removed needless class definition and variables --- test/open-uri/test_ssl.rb | 3 --- test/open-uri/utils.rb | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/test/open-uri/test_ssl.rb b/test/open-uri/test_ssl.rb index 019186f..cc0a751 100644 --- a/test/open-uri/test_ssl.rb +++ b/test/open-uri/test_ssl.rb @@ -9,9 +9,6 @@ end class TestOpenURISSL < Test::Unit::TestCase -end - -class TestOpenURISSL include TestOpenURIUtils def setup diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index 88b0797..464de5e 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -50,7 +50,7 @@ def handle_request(client) @log << "ERROR `#{path}' not found" client.print "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n" end - rescue ::TestOpenURI::Unauthorized => e + rescue ::TestOpenURI::Unauthorized @log << "ERROR Unauthorized" client.print "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n" end @@ -286,7 +286,7 @@ def handle_request(socket) request_line = socket.gets return if request_line.nil? || request_line.strip.empty? - method, path, version = request_line.split + _, path, _ = request_line.split headers = {} while (line = socket.gets) break if line.strip.empty? From cb17a907a2f67d3e09e6941b7d8fd53ed33d23fb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 17:30:03 +0900 Subject: [PATCH 11/15] Load stringio with the correct files --- test/open-uri/test_open-uri.rb | 1 + test/open-uri/test_proxy.rb | 1 + test/open-uri/test_ssl.rb | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index ee39dbc..9db6a8b 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test/unit' require 'open-uri' +require 'stringio' require_relative 'utils' begin require 'zlib' diff --git a/test/open-uri/test_proxy.rb b/test/open-uri/test_proxy.rb index 85f23d9..a36a63f 100644 --- a/test/open-uri/test_proxy.rb +++ b/test/open-uri/test_proxy.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test/unit' require 'open-uri' +require 'stringio' require_relative 'utils' class TestOpenURIProxy < Test::Unit::TestCase diff --git a/test/open-uri/test_ssl.rb b/test/open-uri/test_ssl.rb index cc0a751..31b93e0 100644 --- a/test/open-uri/test_ssl.rb +++ b/test/open-uri/test_ssl.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'test/unit' require 'open-uri' -require 'stringio' require_relative 'utils' begin require 'openssl' From a28c2da5d27bc58817f78df383700ad4f4a9335a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 17:30:35 +0900 Subject: [PATCH 12/15] Move certificates under the TestOpenURIUtils --- test/open-uri/utils.rb | 45 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index 464de5e..ee0c0e4 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -364,6 +364,7 @@ def with_https_proxy(proxy_log_tester=lambda {|proxy_log, proxy_access_log| asse } end + if defined?(OpenSSL::SSL) def with_https(log_tester=lambda {|log| assert_equal([], log) }) log = [] Dir.mktmpdir {|dr| @@ -389,30 +390,28 @@ def with_https(log_tester=lambda {|log| assert_equal([], log) }) } assert_join_threads(threads) } - end if defined?(OpenSSL::SSL) -end + end -if defined?(OpenSSL::SSL) -# cp /etc/ssl/openssl.cnf . # I copied from OpenSSL 1.1.1b source + # cp /etc/ssl/openssl.cnf . # I copied from OpenSSL 1.1.1b source -# mkdir demoCA demoCA/private demoCA/newcerts -# touch demoCA/index.txt -# echo 00 > demoCA/serial -# openssl genrsa -des3 -out demoCA/private/cakey.pem 2048 -# openssl req -new -key demoCA/private/cakey.pem -out demoCA/careq.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=Ruby Test CA" -# # basicConstraints=CA:TRUE is required; the default openssl.cnf has it in [v3_ca] -# openssl ca -config openssl.cnf -extensions v3_ca -out demoCA/cacert.pem -startdate 090101000000Z -enddate 491231235959Z -batch -keyfile demoCA/private/cakey.pem -selfsign -infiles demoCA/careq.pem + # mkdir demoCA demoCA/private demoCA/newcerts + # touch demoCA/index.txt + # echo 00 > demoCA/serial + # openssl genrsa -des3 -out demoCA/private/cakey.pem 2048 + # openssl req -new -key demoCA/private/cakey.pem -out demoCA/careq.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=Ruby Test CA" + # # basicConstraints=CA:TRUE is required; the default openssl.cnf has it in [v3_ca] + # openssl ca -config openssl.cnf -extensions v3_ca -out demoCA/cacert.pem -startdate 090101000000Z -enddate 491231235959Z -batch -keyfile demoCA/private/cakey.pem -selfsign -infiles demoCA/careq.pem -# mkdir server -# openssl genrsa -des3 -out server/server.key 2048 -# openssl req -new -key server/server.key -out server/csr.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=127.0.0.1" -# openssl ca -config openssl.cnf -startdate 090101000000Z -enddate 491231235959Z -in server/csr.pem -keyfile demoCA/private/cakey.pem -cert demoCA/cacert.pem -out server/cert.pem + # mkdir server + # openssl genrsa -des3 -out server/server.key 2048 + # openssl req -new -key server/server.key -out server/csr.pem -subj "/C=JP/ST=Tokyo/O=RubyTest/CN=127.0.0.1" + # openssl ca -config openssl.cnf -startdate 090101000000Z -enddate 491231235959Z -in server/csr.pem -keyfile demoCA/private/cakey.pem -cert demoCA/cacert.pem -out server/cert.pem -# demoCA/cacert.pem => TestOpenURISSL::CA_CERT -# server/cert.pem => TestOpenURISSL::SERVER_CERT -# `openssl rsa -in server/server.key -text` => TestOpenURISSL::SERVER_KEY + # demoCA/cacert.pem => TestOpenURISSL::CA_CERT + # server/cert.pem => TestOpenURISSL::SERVER_CERT + # `openssl rsa -in server/server.key -text` => TestOpenURISSL::SERVER_KEY -TestOpenURIUtils::CA_CERT = <<'End' + CA_CERT = <<'End' Certificate: Data: Version: 3 (0x2) @@ -492,7 +491,7 @@ def with_https(log_tester=lambda {|log| assert_equal([], log) }) -----END CERTIFICATE----- End -TestOpenURIUtils::SERVER_CERT = <<'End' + SERVER_CERT = <<'End' Certificate: Data: Version: 3 (0x2) @@ -575,7 +574,7 @@ def with_https(log_tester=lambda {|log| assert_equal([], log) }) -----END CERTIFICATE----- End -TestOpenURIUtils::SERVER_KEY = <<'End' + SERVER_KEY = <<'End' RSA Private-Key: (2048 bit, 2 primes) modulus: 00:cb:b3:71:95:12:70:fc:db:d4:a9:a7:66:d6:d3: @@ -696,7 +695,7 @@ def with_https(log_tester=lambda {|log| assert_equal([], log) }) -----END RSA PRIVATE KEY----- End -TestOpenURIUtils::DHPARAMS = <<'End' + DHPARAMS = <<'End' DH Parameters: (2048 bit) prime: 00:ec:4e:a4:06:b6:22:ca:f9:8a:00:cc:d0:ee:2f: @@ -727,5 +726,5 @@ def with_https(log_tester=lambda {|log| assert_equal([], log) }) 7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== -----END DH PARAMETERS----- End - + end end From 2606892a4347bf8a71d2bb0879339bcda6fce198 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 17:39:06 +0900 Subject: [PATCH 13/15] Test with Windows --- .github/workflows/test.yml | 2 +- test/open-uri/test_ssl.rb | 4 ++++ test/open-uri/utils.rb | 15 +++++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6617a3..e76f00d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} - os: [ ubuntu-latest, macos-latest ] + os: [ ubuntu-latest, macos-latest, windows-latest ] exclude: - ruby: 2.5 os: macos-latest diff --git a/test/open-uri/test_ssl.rb b/test/open-uri/test_ssl.rb index 31b93e0..067d115 100644 --- a/test/open-uri/test_ssl.rb +++ b/test/open-uri/test_ssl.rb @@ -76,6 +76,8 @@ def test_bad_ssl_version end def test_proxy_cacert_file + pend if RUBY_PLATFORM =~ /mswin|mingw/ + url = nil proxy_log_tester = lambda {|proxy_log, proxy_access_log| assert_equal(1, proxy_access_log.length) @@ -92,6 +94,8 @@ def test_proxy_cacert_file end def test_proxy_cacert_dir + pend if RUBY_PLATFORM =~ /mswin|mingw/ + url = nil proxy_log_tester = lambda {|proxy_log, proxy_access_log| assert_equal(1, proxy_access_log.length) diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index ee0c0e4..b529ba8 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -313,7 +313,8 @@ class ProxyAuthenticationRequired < StandardError; end def with_http(log_tester=lambda {|log| assert_equal([], log) }) log = [] - srv = SimpleHTTPServer.new('localhost', 0, log) + host = "127.0.0.1" + srv = SimpleHTTPServer.new(host, 0, log) server_thread = srv.start server_thread2 = Thread.new { @@ -327,7 +328,7 @@ def with_http(log_tester=lambda {|log| assert_equal([], log) }) client_thread = Thread.new { begin - yield srv, "http://localhost:#{port}", server_thread, log + yield srv, "http://#{host}:#{port}", server_thread, log ensure srv.shutdown end @@ -346,9 +347,10 @@ def with_https_proxy(proxy_log_tester=lambda {|proxy_log, proxy_access_log| asse Dir.mkdir cacert_directory hashed_name = "%08x.0" % OpenSSL::X509::Certificate.new(CA_CERT).subject.hash open("#{cacert_directory}/#{hashed_name}", "w") {|f| f << CA_CERT } - proxy = SimpleHTTPProxyServer.new('127.0.0.1', 0, proxy_log, proxy_access_log) + proxy_host = '127.0.0.1' + proxy = SimpleHTTPProxyServer.new(proxy_host, 0, proxy_log, proxy_access_log) proxy.start - _, proxy_port, _, proxy_host = proxy.instance_variable_get(:@server).addr + proxy_port = proxy.instance_variable_get(:@server).addr[1] proxy_thread = proxy.start threads << Thread.new { proxy_thread.join @@ -371,8 +373,9 @@ def with_https(log_tester=lambda {|log| assert_equal([], log) }) cert = OpenSSL::X509::Certificate.new(SERVER_CERT) key = OpenSSL::PKey::RSA.new(SERVER_KEY) dh = OpenSSL::PKey::DH.new(DHPARAMS) - srv = SimpleHTTPSServer.new(cert, key, dh, '127.0.0.1', 0, log) - _, port, _, host = srv.instance_variable_get(:@server).addr + host = '127.0.0.1' + srv = SimpleHTTPSServer.new(cert, key, dh, host, 0, log) + port = srv.instance_variable_get(:@server).addr[1] threads = [] server_thread = srv.start threads << Thread.new { From 4c98e3e4a286783d01f39dbfa6b092dc770cb298 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 5 Jul 2024 17:18:21 +0900 Subject: [PATCH 14/15] Removed webrick from dependency --- Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile b/Gemfile index ebc40c8..017ffb2 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,3 @@ gem "net-ftp" gem "rake" gem "test-unit" gem "test-unit-ruby-core" -gem "webrick" From c8c0452d53cdd8c570cddc401a55a4a33d83a253 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Jul 2024 13:39:54 +0900 Subject: [PATCH 15/15] Don't use Base64 library --- test/open-uri/utils.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/open-uri/utils.rb b/test/open-uri/utils.rb index b529ba8..3a79a32 100644 --- a/test/open-uri/utils.rb +++ b/test/open-uri/utils.rb @@ -1,5 +1,4 @@ require 'socket' -require 'base64' class SimpleHTTPServer def initialize(bind_addr, port, log) @@ -79,7 +78,7 @@ def parse_basic_auth return unless auth && auth.start_with?('Basic ') encoded_credentials = auth.split(' ', 2).last - decoded_credentials = Base64.decode64(encoded_credentials) + decoded_credentials = [encoded_credentials].pack("m") @username, @password = decoded_credentials.split(':', 2) end end