From 73085c8d8ee5456b6c157042b78d812f3d1ab054 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 13 Aug 2021 18:09:14 +0200 Subject: [PATCH] Update to ruby/spec@330c641 --- spec/ruby/core/gc/stat_spec.rb | 6 +- spec/ruby/language/numbers_spec.rb | 4 + .../net/http/http/fixtures/http_server.rb | 162 ++++++++++-------- spec/ruby/library/net/http/http/post_spec.rb | 2 +- .../net/http/http/send_request_spec.rb | 2 +- spec/ruby/security/cve_2017_17742_spec.rb | 57 +++--- 6 files changed, 129 insertions(+), 104 deletions(-) diff --git a/spec/ruby/core/gc/stat_spec.rb b/spec/ruby/core/gc/stat_spec.rb index 34656c401c..eb71fd9691 100644 --- a/spec/ruby/core/gc/stat_spec.rb +++ b/spec/ruby/core/gc/stat_spec.rb @@ -4,7 +4,11 @@ describe "GC.stat" do it "returns hash of values" do stat = GC.stat stat.should be_kind_of(Hash) - stat.keys.should include(:count) + stat.keys.should.include?(:count) + end + + it "the values are all Integer since rb_gc_stat() returns size_t" do + GC.stat.values.each { |value| value.should be_kind_of(Integer) } end it "can return a single value" do diff --git a/spec/ruby/language/numbers_spec.rb b/spec/ruby/language/numbers_spec.rb index 418bc60fa6..2d8e19c40a 100644 --- a/spec/ruby/language/numbers_spec.rb +++ b/spec/ruby/language/numbers_spec.rb @@ -45,6 +45,10 @@ describe "A number literal" do eval('-3r').should == Rational(-3, 1) end + it "can be an float literal with trailing 'r' to represent a Rational in a canonical form" do + eval('1.0r').should == Rational(1, 1) + end + it "can be a float literal with trailing 'r' to represent a Rational" do eval('0.0174532925199432957r').should == Rational(174532925199432957, 10000000000000000000) end diff --git a/spec/ruby/library/net/http/http/fixtures/http_server.rb b/spec/ruby/library/net/http/http/fixtures/http_server.rb index c06012cc86..63543b46a9 100644 --- a/spec/ruby/library/net/http/http/fixtures/http_server.rb +++ b/spec/ruby/library/net/http/http/fixtures/http_server.rb @@ -1,5 +1,4 @@ -require 'webrick' -require 'webrick/httpservlet/abstract' +require 'socket' module NetHTTPSpecs class NullWriter @@ -9,102 +8,117 @@ module NetHTTPSpecs def printf(*args) end end - class SpecServlet < WEBrick::HTTPServlet::AbstractServlet - def handle(req, res) - reply(req, res) + class SmallHTTPServer + def initialize(bind_address) + @server = TCPServer.new(bind_address, 0) + @running = Mutex.new + @thread = Thread.new { + Thread.current.abort_on_exception = true + listen + } end - %w{ do_GET do_HEAD do_POST do_PUT do_PROPPATCH do_LOCK do_UNLOCK - do_OPTIONS do_PROPFIND do_DELETE do_MOVE do_COPY - do_MKCOL do_TRACE }.each do |method| - alias_method method.to_sym, :handle + def port + @server.addr[1] end - end - class RequestServlet < SpecServlet - def reply(req, res) - res.content_type = "text/plain" - res.body = "Request type: #{req.request_method}" - end - end + def listen + loop do + begin + client = @server.accept + rescue IOError => e + if @running.locked? # close + break + else + raise e + end + end - class RequestBodyServlet < SpecServlet - def reply(req, res) - res.content_type = "text/plain" - res.body = req.body - end - end - - class RequestHeaderServlet < SpecServlet - def reply(req, res) - res.content_type = "text/plain" - res.body = req.header.inspect - end - end - - class RequestBasicAuthServlet < SpecServlet - def reply(req, res) - res.content_type = "text/plain" - - WEBrick::HTTPAuth.basic_auth(req, res, "realm") do |user, pass| - res.body = "username: #{user}\npassword: #{pass}" - true + handle_client(client) end end + + def handle_client(client) + begin + until client.closed? + request = client.gets("\r\n\r\n") + break unless request + handle_request(client, request) + end + ensure + client.close + end + end + + def parse_request(request) + request, *headers = request.chomp.lines.map { |line| line.chomp } + request_method, request_uri, _http_version = request.split + headers = headers.map { |line| line.split(': ', 2) }.to_h + [request_method, request_uri, headers] + end + + def handle_request(client, request) + request_method, request_uri, headers = parse_request(request) + + if headers.include? 'Content-Length' + request_body_size = Integer(headers['Content-Length']) + request_body = client.read(request_body_size) + end + + case request_uri + when '/' + raise request_method unless request_method == 'GET' + reply(client, "This is the index page.", request_method) + when '/request' + reply(client, "Request type: #{request_method}", request_method) + when '/request/body' + reply(client, request_body, request_method) + when '/request/header' + reply(client, headers.inspect, request_method) + when '/request/basic_auth' + reply(client, "username: \npassword: ", request_method) + else + raise request_uri + end + end + + def reply(client, body, request_method) + client.print "HTTP/1.1 200 OK\r\n" + if request_method == 'HEAD' + client.close + else + client.print "Content-Type: text/plain\r\n" + client.print "Content-Length: #{body.bytesize}\r\n" + client.print "\r\n" + client.print body + end + end + + def close + @running.lock + @server.close + @thread.join + end end @server = nil - @server_thread = nil class << self def port raise "server not started" unless @server - @server.config[:Port] + @server.port end def start_server bind_address = platform_is(:windows) ? "localhost" : "127.0.0.1" - server_config = { - BindAddress: bind_address, - Port: 0, - Logger: WEBrick::Log.new(NullWriter.new), - AccessLog: [], - ServerType: Thread - } - - @server = WEBrick::HTTPServer.new(server_config) - - @server.mount_proc('/') do |req, res| - res.content_type = "text/plain" - res.body = "This is the index page." - end - @server.mount('/request', RequestServlet) - @server.mount("/request/body", RequestBodyServlet) - @server.mount("/request/header", RequestHeaderServlet) - @server.mount("/request/basic_auth", RequestBasicAuthServlet) - - @server_thread = @server.start + @server = SmallHTTPServer.new(bind_address) end def stop_server if @server - begin - @server.shutdown - rescue Errno::EPIPE, Errno::EBADF - # Because WEBrick is not thread-safe and only catches IOError - - # EBADF can happen because WEBrick @server_thread concurrently closes the shutdown pipe - # once @status = :Shutdown, while the current thread does write_nonblock("\0"). - # On MRI this EBADF is replaced by IOError due to the GIL around both #close and #write_nonblock. - end + @server.close @server = nil end - if @server_thread - @server_thread.join - @server_thread = nil - end - timeout = WEBrick::Utils::TimeoutHandler - timeout.terminate if timeout.respond_to?(:terminate) end end end diff --git a/spec/ruby/library/net/http/http/post_spec.rb b/spec/ruby/library/net/http/http/post_spec.rb index 9f20a03c85..891e20aaca 100644 --- a/spec/ruby/library/net/http/http/post_spec.rb +++ b/spec/ruby/library/net/http/http/post_spec.rb @@ -27,7 +27,7 @@ describe "Net::HTTP.post" do it "sends Content-Type: application/x-www-form-urlencoded by default" do response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test") - response.body.should include('"content-type"=>["application/x-www-form-urlencoded"]') + response.body.should include('"Content-Type"=>"application/x-www-form-urlencoded"') end it "does not support HTTP Basic Auth" do diff --git a/spec/ruby/library/net/http/http/send_request_spec.rb b/spec/ruby/library/net/http/http/send_request_spec.rb index 47b3eef5b9..83e9448b8b 100644 --- a/spec/ruby/library/net/http/http/send_request_spec.rb +++ b/spec/ruby/library/net/http/http/send_request_spec.rb @@ -54,7 +54,7 @@ describe "Net::HTTP#send_request" do @methods.each do |method| response = @http.send_request(method, "/request/header", "test=test", "referer" => referer) - response.body.should include('"referer"=>["' + referer + '"]') + response.body.should include('"Referer"=>"' + referer + '"') end end end diff --git a/spec/ruby/security/cve_2017_17742_spec.rb b/spec/ruby/security/cve_2017_17742_spec.rb index 72776cb497..b0d93e42b8 100644 --- a/spec/ruby/security/cve_2017_17742_spec.rb +++ b/spec/ruby/security/cve_2017_17742_spec.rb @@ -1,34 +1,37 @@ require_relative '../spec_helper' -require "webrick" -require "stringio" -require "net/http" +# webrick is no longer in stdlib in Ruby 3+ +ruby_version_is ""..."3.0" do + require "webrick" + require "stringio" + require "net/http" -describe "WEBrick" do - describe "resists CVE-2017-17742" do - it "for a response splitting headers" do - config = WEBrick::Config::HTTP - res = WEBrick::HTTPResponse.new config - res['X-header'] = "malicious\r\nCookie: hack" - io = StringIO.new - res.send_response io - io.rewind - res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) - res.code.should == '500' - io.string.should_not =~ /hack/ - end + describe "WEBrick" do + describe "resists CVE-2017-17742" do + it "for a response splitting headers" do + config = WEBrick::Config::HTTP + res = WEBrick::HTTPResponse.new config + res['X-header'] = "malicious\r\nCookie: hack" + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + res.code.should == '500' + io.string.should_not =~ /hack/ + end - it "for a response splitting cookie headers" do - user_input = "malicious\r\nCookie: hack" - config = WEBrick::Config::HTTP - res = WEBrick::HTTPResponse.new config - res.cookies << WEBrick::Cookie.new('author', user_input) - io = StringIO.new - res.send_response io - io.rewind - res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) - res.code.should == '500' - io.string.should_not =~ /hack/ + it "for a response splitting cookie headers" do + user_input = "malicious\r\nCookie: hack" + config = WEBrick::Config::HTTP + res = WEBrick::HTTPResponse.new config + res.cookies << WEBrick::Cookie.new('author', user_input) + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + res.code.should == '500' + io.string.should_not =~ /hack/ + end end end end