ruby/lib/webrick/httpservlet/cgihandler.rb
nagachika 16b426e966 merge revision(s) 62960,62961,62962,62963,62964,62965:
webrick/httprequest: limit request headers size

	We use the same 112 KB limit started (AFAIK) by Mongrel, Thin,
	and Puma to prevent malicious users from using up all the memory
	with a single request.  This also limits the damage done by
	excessive ranges in multipart Range: requests.

	Due to the way we rely on IO#gets and the desire to keep
	the code simple, the actual maximum header may be 4093 bytes
	larger than 112 KB, but we're splitting hairs at that point.

	* lib/webrick/httprequest.rb: define MAX_HEADER_LENGTH
	  (read_header): raise when headers exceed max length

	webrick/httpservlet/cgihandler: reduce memory use

	WEBrick::HTTPRequest#body can be passed a block to process the
	body in chunks.  Use this feature to avoid building a giant
	string in memory.

	* lib/webrick/httpservlet/cgihandler.rb (do_GET):
	  avoid reading entire request body into memory
	  (do_POST is aliased to do_GET, so it handles bodies)

	webrick/httprequest: raise correct exception

	"BadRequest" alone does not resolve correctly, it is in the
	HTTPStatus namespace.

	* lib/webrick/httprequest.rb (read_chunked): use correct exception
	* test/webrick/test_httpserver.rb (test_eof_in_chunk): new test

	webrick/httprequest: use InputBufferSize for chunked requests

	While WEBrick::HTTPRequest#body provides a Proc interface
	for streaming large request bodies, clients must not force
	the server to use an excessively large chunk size.

	* lib/webrick/httprequest.rb (read_chunk_size): limit each
	  read and block.call to :InputBufferSize in config.
	* test/webrick/test_httpserver.rb (test_big_chunks): new test

	webrick: add test for Digest auth-int

	No changes to the actual code, this is a new test for
	a feature for which no tests existed.  I don't understand
	the Digest authentication code well at all, but this is
	necessary for the subsequent change.

	* test/webrick/test_httpauth.rb (test_digest_auth_int): new test
	  (credentials_for_request): support bodies with POST

	webrick/httpauth/digestauth: stream req.body

	WARNING! WARNING! WARNING!  LIKELY BROKEN CHANGE

	Pass a proc to WEBrick::HTTPRequest#body to avoid reading a
	potentially large request body into memory during
	authentication.

	WARNING! this will break apps completely which want to do
	something with the body besides calculating the MD5 digest
	of it.

	Also, keep in mind that probably nobody uses "auth-int".
	Servers such as Apache, lighttpd, nginx don't seem to
	support it; nor does curl when using POST/PUT bodies;
	and we didn't have tests for it until now...

	* lib/webrick/httpauth/digestauth.rb (_authenticate): stream req.body

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_2_4@63004 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-03-28 12:23:29 +00:00

120 lines
3.6 KiB
Ruby

# frozen_string_literal: false
#
# cgihandler.rb -- CGIHandler Class
#
# Author: IPR -- Internet Programming with Ruby -- writers
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
# reserved.
#
# $IPR: cgihandler.rb,v 1.27 2003/03/21 19:56:01 gotoyuzo Exp $
require 'rbconfig'
require 'tempfile'
require 'webrick/config'
require 'webrick/httpservlet/abstract'
module WEBrick
module HTTPServlet
##
# Servlet for handling CGI scripts
#
# Example:
#
# server.mount('/cgi/my_script', WEBrick::HTTPServlet::CGIHandler,
# '/path/to/my_script')
class CGIHandler < AbstractServlet
Ruby = RbConfig.ruby # :nodoc:
CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\"" # :nodoc:
##
# Creates a new CGI script servlet for the script at +name+
def initialize(server, name)
super(server, name)
@script_filename = name
@tempdir = server[:TempDir]
@cgicmd = "#{CGIRunner} #{server[:CGIInterpreter]}"
end
# :stopdoc:
def do_GET(req, res)
cgi_in = IO::popen(@cgicmd, "wb")
cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
cgi_out.set_encoding("ASCII-8BIT")
cgi_err = Tempfile.new("webrick.cgierr.", @tempdir, mode: IO::BINARY)
cgi_err.set_encoding("ASCII-8BIT")
begin
cgi_in.sync = true
meta = req.meta_vars
meta["SCRIPT_FILENAME"] = @script_filename
meta["PATH"] = @config[:CGIPathEnv]
meta.delete("HTTP_PROXY")
if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
meta["SystemRoot"] = ENV["SystemRoot"]
end
dump = Marshal.dump(meta)
cgi_in.write("%8d" % cgi_out.path.bytesize)
cgi_in.write(cgi_out.path)
cgi_in.write("%8d" % cgi_err.path.bytesize)
cgi_in.write(cgi_err.path)
cgi_in.write("%8d" % dump.bytesize)
cgi_in.write(dump)
req.body { |chunk| cgi_in.write(chunk) }
ensure
cgi_in.close
status = $?.exitstatus
sleep 0.1 if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
data = cgi_out.read
cgi_out.close(true)
if errmsg = cgi_err.read
if errmsg.bytesize > 0
@logger.error("CGIHandler: #{@script_filename}:\n" + errmsg)
end
end
cgi_err.close(true)
end
if status != 0
@logger.error("CGIHandler: #{@script_filename} exit with #{status}")
end
data = "" unless data
raw_header, body = data.split(/^[\xd\xa]+/, 2)
raise HTTPStatus::InternalServerError,
"Premature end of script headers: #{@script_filename}" if body.nil?
begin
header = HTTPUtils::parse_header(raw_header)
if /^(\d+)/ =~ header['status'][0]
res.status = $1.to_i
header.delete('status')
end
if header.has_key?('location')
# RFC 3875 6.2.3, 6.2.4
res.status = 302 unless (300...400) === res.status
end
if header.has_key?('set-cookie')
header['set-cookie'].each{|k|
res.cookies << Cookie.parse_set_cookie(k)
}
header.delete('set-cookie')
end
header.each{|key, val| res[key] = val.join(", ") }
rescue => ex
raise HTTPStatus::InternalServerError, ex.message
end
res.body = body
end
alias do_POST do_GET
# :startdoc:
end
end
end