Boot out CGI Processor.
* Add ActionController::CGIHandler as a backwards compatible CGI wrapper around Rack. * Also pull failsafe responder into ActionController::Failsafe middleware.
This commit is contained in:
parent
27ebfd795f
commit
9c9da6c892
|
@ -46,10 +46,9 @@ module ActionController
|
|||
autoload :Base, 'action_controller/base'
|
||||
autoload :Benchmarking, 'action_controller/benchmarking'
|
||||
autoload :Caching, 'action_controller/caching'
|
||||
autoload :CgiRequest, 'action_controller/cgi_process'
|
||||
autoload :CgiResponse, 'action_controller/cgi_process'
|
||||
autoload :Cookies, 'action_controller/cookies'
|
||||
autoload :Dispatcher, 'action_controller/dispatcher'
|
||||
autoload :Failsafe, 'action_controller/failsafe'
|
||||
autoload :Filters, 'action_controller/filters'
|
||||
autoload :Flash, 'action_controller/flash'
|
||||
autoload :Helpers, 'action_controller/helpers'
|
||||
|
@ -89,6 +88,11 @@ module ActionController
|
|||
module Http
|
||||
autoload :Headers, 'action_controller/headers'
|
||||
end
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
autoload :CgiRequest, 'action_controller/cgi_process'
|
||||
autoload :CgiResponse, 'action_controller/cgi_process'
|
||||
autoload :CGIHandler, 'action_controller/cgi_process'
|
||||
end
|
||||
|
||||
class CGI
|
||||
|
|
|
@ -1,184 +1,72 @@
|
|||
require 'action_controller/cgi_ext'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
# Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
|
||||
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
|
||||
#
|
||||
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# lib/action_controller/session.
|
||||
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
|
||||
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
|
||||
# automatically generated for a new session.
|
||||
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# an ArgumentError is raised.
|
||||
# * <tt>:session_expires</tt> - the time the current session expires, as a Time object. If not set, the session will continue
|
||||
# indefinitely.
|
||||
# * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
|
||||
# server.
|
||||
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
|
||||
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
|
||||
# * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
|
||||
# the query string or POST parameters. This protects against session fixation attacks.
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
new.process_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
def process_cgi(cgi, session_options = {}) #:nodoc:
|
||||
process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cgi, :session_options
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
||||
:prefix => "ruby_sess.", # prefix session file names
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true,
|
||||
:session_http_only=> true
|
||||
}
|
||||
|
||||
def initialize(cgi, session_options = {})
|
||||
@cgi = cgi
|
||||
@session_options = session_options
|
||||
@env = @cgi.__send__(:env_table)
|
||||
super()
|
||||
end
|
||||
|
||||
def query_string
|
||||
qs = @cgi.query_string if @cgi.respond_to?(:query_string)
|
||||
if !qs.blank?
|
||||
qs
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def body_stream #:nodoc:
|
||||
@cgi.stdinput
|
||||
end
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
|
||||
def session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
|
||||
raise SessionFixationAttempt
|
||||
end
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
when true
|
||||
@session = new_session
|
||||
when false
|
||||
begin
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
||||
# and no session cookie or query param is present.
|
||||
rescue ArgumentError
|
||||
@session = Hash.new
|
||||
end
|
||||
when nil
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
else
|
||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
||||
end
|
||||
@session['__valid_session']
|
||||
end
|
||||
end
|
||||
end
|
||||
@session
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
||||
@session = new_session
|
||||
end
|
||||
|
||||
def method_missing(method_id, *arguments)
|
||||
@cgi.__send__(method_id, *arguments) rescue super
|
||||
end
|
||||
|
||||
private
|
||||
# Delete an old session if it exists then create a new one.
|
||||
def new_session
|
||||
if @session_options == false
|
||||
Hash.new
|
||||
else
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
|
||||
class CGIHandler
|
||||
module ProperStream
|
||||
def each
|
||||
while line = gets
|
||||
yield line
|
||||
end
|
||||
end
|
||||
|
||||
def cookie_only?
|
||||
session_options_with_string_keys['cookie_only']
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, <<-end_msg
|
||||
Session contains objects whose class definition isn\'t available.
|
||||
Remember to require the classes for all objects kept in the session.
|
||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
||||
end_msg
|
||||
end
|
||||
|
||||
retry
|
||||
def read(*args)
|
||||
if args.empty?
|
||||
super || ""
|
||||
else
|
||||
raise
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def session_options_with_string_keys
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
||||
end
|
||||
end
|
||||
|
||||
class CgiResponse < AbstractResponse #:nodoc:
|
||||
def initialize(cgi)
|
||||
@cgi = cgi
|
||||
super()
|
||||
end
|
||||
|
||||
def out(output = $stdout)
|
||||
output.binmode if output.respond_to?(:binmode)
|
||||
output.sync = false if output.respond_to?(:sync=)
|
||||
def self.dispatch_cgi(app, cgi, out = $stdout)
|
||||
env = cgi.__send__(:env_table)
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
|
||||
cgi.stdinput.extend ProperStream
|
||||
|
||||
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
||||
|
||||
env.update({
|
||||
"rack.version" => [0,1],
|
||||
"rack.input" => cgi.stdinput,
|
||||
"rack.errors" => $stderr,
|
||||
"rack.multithread" => false,
|
||||
"rack.multiprocess" => true,
|
||||
"rack.run_once" => false,
|
||||
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
||||
})
|
||||
|
||||
env["QUERY_STRING"] ||= ""
|
||||
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
||||
env["REQUEST_PATH"] ||= "/"
|
||||
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
|
||||
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
output.write(@cgi.header(@headers))
|
||||
out.binmode if out.respond_to?(:binmode)
|
||||
out.sync = false if out.respond_to?(:sync=)
|
||||
|
||||
if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
||||
return
|
||||
elsif @body.respond_to?(:call)
|
||||
# Flush the output now in case the @body Proc uses
|
||||
# #syswrite.
|
||||
output.flush if output.respond_to?(:flush)
|
||||
@body.call(self, output)
|
||||
else
|
||||
output.write(@body)
|
||||
end
|
||||
headers['Status'] = status.to_s
|
||||
out.write(cgi.header(headers))
|
||||
|
||||
output.flush if output.respond_to?(:flush)
|
||||
rescue Errno::EPIPE, Errno::ECONNRESET
|
||||
# lost connection to parent process, ignore output
|
||||
body.each { |part|
|
||||
out.write part
|
||||
out.flush if out.respond_to?(:flush)
|
||||
}
|
||||
ensure
|
||||
body.close if body.respond_to?(:close)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequest #:nodoc:
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::CookieStore,
|
||||
:prefix => "ruby_sess.",
|
||||
:session_path => "/",
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true,
|
||||
:session_http_only => true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,8 +24,7 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
||||
# in favor of Dispatcher.new(output, request, response).dispatch.
|
||||
# DEPRECATE: Remove CGI support
|
||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
@ -43,57 +42,16 @@ module ActionController
|
|||
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
|
||||
@prepare_dispatch_callbacks.replace_or_append!(callback)
|
||||
end
|
||||
|
||||
# If the block raises, send status code as a last-ditch response.
|
||||
def failsafe_response(fallback_output, status, originating_exception = nil)
|
||||
yield
|
||||
rescue Exception => exception
|
||||
begin
|
||||
log_failsafe_exception(status, originating_exception || exception)
|
||||
body = failsafe_response_body(status)
|
||||
fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
|
||||
nil
|
||||
rescue Exception => failsafe_error # Logger or IO errors
|
||||
$stderr.puts "Error during failsafe response: #{failsafe_error}"
|
||||
$stderr.puts "(originally #{originating_exception})" if originating_exception
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def failsafe_response_body(status)
|
||||
error_path = "#{error_file_path}/#{status.to_s[0..2]}.html"
|
||||
|
||||
if File.exist?(error_path)
|
||||
File.read(error_path)
|
||||
else
|
||||
"<html><body><h1>#{status}</h1></body></html>"
|
||||
end
|
||||
end
|
||||
|
||||
def log_failsafe_exception(status, exception)
|
||||
message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: #{status}\n"
|
||||
message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
|
||||
failsafe_logger.fatal message
|
||||
end
|
||||
|
||||
def failsafe_logger
|
||||
if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
|
||||
::RAILS_DEFAULT_LOGGER
|
||||
else
|
||||
Logger.new($stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cattr_accessor :middleware
|
||||
self.middleware = MiddlewareStack.new
|
||||
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
||||
self.middleware.use "ActionController::Failsafe"
|
||||
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
# DEPRECATE: Remove arguments
|
||||
def initialize(output = $stdout, request = nil, response = nil)
|
||||
@output, @request, @response = output, request, response
|
||||
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
|
||||
|
@ -120,14 +78,9 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
def dispatch_cgi(cgi, session_options)
|
||||
if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
|
||||
@request = CgiRequest.new(cgi, session_options)
|
||||
@response = CgiResponse.new(cgi)
|
||||
dispatch
|
||||
end
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
CGIHandler.dispatch_cgi(self, cgi, @output)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
|
@ -160,40 +113,22 @@ module ActionController
|
|||
Base.logger.flush
|
||||
end
|
||||
|
||||
def mark_as_test_request!
|
||||
@test_request = true
|
||||
self
|
||||
end
|
||||
|
||||
def test_request?
|
||||
@test_request
|
||||
end
|
||||
|
||||
def checkin_connections
|
||||
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
|
||||
return if test_request?
|
||||
# TODO: This callback should have direct access to env
|
||||
return if @request.key?("action_controller.test")
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
|
||||
protected
|
||||
def handle_request
|
||||
@controller = Routing::Routes.recognize(@request)
|
||||
@controller.process(@request, @response).out(@output)
|
||||
@controller.process(@request, @response).out
|
||||
end
|
||||
|
||||
def failsafe_rescue(exception)
|
||||
if @test_request
|
||||
process_with_exception(exception)
|
||||
else
|
||||
self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
|
||||
process_with_exception(exception)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_with_exception(exception)
|
||||
if @controller ||= (::ApplicationController rescue Base)
|
||||
@controller.process_with_exception(@request, @response, exception).out(@output)
|
||||
@controller.process_with_exception(@request, @response, exception).out
|
||||
else
|
||||
raise exception
|
||||
end
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
module ActionController
|
||||
class Failsafe
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@app.call(env)
|
||||
rescue Exception => exception
|
||||
# Reraise exception in test environment
|
||||
if env["action_controller.test"]
|
||||
raise exception
|
||||
else
|
||||
failsafe_response(exception)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def failsafe_response(exception)
|
||||
log_failsafe_exception(exception)
|
||||
[500, {'Content-Type' => 'text/html'}, failsafe_response_body]
|
||||
rescue Exception => failsafe_error # Logger or IO errors
|
||||
$stderr.puts "Error during failsafe response: #{failsafe_error}"
|
||||
end
|
||||
|
||||
def failsafe_response_body
|
||||
error_path = "#{self.class.error_file_path}/500.html"
|
||||
if File.exist?(error_path)
|
||||
File.read(error_path)
|
||||
else
|
||||
"<html><body><h1>500 Internal Server Error</h1></body></html>"
|
||||
end
|
||||
end
|
||||
|
||||
def log_failsafe_exception(exception)
|
||||
message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: 500 Internal Server Error\n"
|
||||
message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
|
||||
failsafe_logger.fatal(message)
|
||||
end
|
||||
|
||||
def failsafe_logger
|
||||
if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
|
||||
::RAILS_DEFAULT_LOGGER
|
||||
else
|
||||
Logger.new($stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -257,7 +257,8 @@ module ActionController
|
|||
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
|
||||
"HTTP_COOKIE" => encode_cookies,
|
||||
"HTTPS" => https? ? "on" : "off",
|
||||
"HTTP_ACCEPT" => accept
|
||||
"HTTP_ACCEPT" => accept,
|
||||
"action_controller.test" => true
|
||||
)
|
||||
|
||||
(headers || {}).each do |key, value|
|
||||
|
@ -273,7 +274,7 @@ module ActionController
|
|||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
|
||||
@status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
|
||||
@status, @headers, result_body = ActionController::Dispatcher.new.call(env)
|
||||
@request_count += 1
|
||||
|
||||
@controller = ActionController::Base.last_instantiation
|
||||
|
|
|
@ -164,7 +164,7 @@ end_msg
|
|||
@status || super
|
||||
end
|
||||
|
||||
def out(output = $stdout, &block)
|
||||
def out(&block)
|
||||
# Nasty hack because CGI sessions are closed after the normal
|
||||
# prepare! statement
|
||||
set_cookies!
|
||||
|
|
|
@ -1,262 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class BaseCgiTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@request_hash = {
|
||||
"HTTP_MAX_FORWARDS" => "10",
|
||||
"SERVER_NAME" => "glu.ttono.us:8007",
|
||||
"FCGI_ROLE" => "RESPONDER",
|
||||
"AUTH_TYPE" => "Basic",
|
||||
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
|
||||
"HTTP_ACCEPT_CHARSET" => "UTF-8",
|
||||
"HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
||||
"HTTP_CACHE_CONTROL" => "no-cache, max-age=0",
|
||||
"HTTP_PRAGMA" => "no-cache",
|
||||
"HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)",
|
||||
"PATH_INFO" => "/homepage/",
|
||||
"HTTP_ACCEPT_LANGUAGE" => "en",
|
||||
"HTTP_NEGOTIATE" => "trans",
|
||||
"HTTP_HOST" => "glu.ttono.us:8007",
|
||||
"HTTP_REFERER" => "http://www.google.com/search?q=glu.ttono.us",
|
||||
"HTTP_FROM" => "googlebot",
|
||||
"SERVER_PROTOCOL" => "HTTP/1.1",
|
||||
"REDIRECT_URI" => "/dispatch.fcgi",
|
||||
"SCRIPT_NAME" => "/dispatch.fcgi",
|
||||
"SERVER_ADDR" => "207.7.108.53",
|
||||
"REMOTE_ADDR" => "207.7.108.53",
|
||||
"REMOTE_HOST" => "google.com",
|
||||
"REMOTE_IDENT" => "kevin",
|
||||
"REMOTE_USER" => "kevin",
|
||||
"SERVER_SOFTWARE" => "lighttpd/1.4.5",
|
||||
"HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes",
|
||||
"HTTP_X_FORWARDED_SERVER" => "glu.ttono.us",
|
||||
"REQUEST_URI" => "/admin",
|
||||
"DOCUMENT_ROOT" => "/home/kevinc/sites/typo/public",
|
||||
"PATH_TRANSLATED" => "/home/kevinc/sites/typo/public/homepage/",
|
||||
"SERVER_PORT" => "8007",
|
||||
"QUERY_STRING" => "",
|
||||
"REMOTE_PORT" => "63137",
|
||||
"GATEWAY_INTERFACE" => "CGI/1.1",
|
||||
"HTTP_X_FORWARDED_FOR" => "65.88.180.234",
|
||||
"HTTP_ACCEPT" => "*/*",
|
||||
"SCRIPT_FILENAME" => "/home/kevinc/sites/typo/public/dispatch.fcgi",
|
||||
"REDIRECT_STATUS" => "200",
|
||||
"REQUEST_METHOD" => "GET"
|
||||
}
|
||||
# some Nokia phone browsers omit the space after the semicolon separator.
|
||||
# some developers have grown accustomed to using comma in cookie values.
|
||||
@alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}
|
||||
@cgi = CGI.new
|
||||
class << @cgi; attr_accessor :env_table end
|
||||
@cgi.env_table = @request_hash
|
||||
@request = ActionController::CgiRequest.new(@cgi)
|
||||
end
|
||||
|
||||
def default_test; end
|
||||
|
||||
private
|
||||
|
||||
def set_content_data(data)
|
||||
@request.env['REQUEST_METHOD'] = 'POST'
|
||||
@request.env['CONTENT_LENGTH'] = data.length
|
||||
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
@request.env['RAW_POST_DATA'] = data
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestTest < BaseCgiTest
|
||||
def test_proxy_request
|
||||
assert_equal 'glu.ttono.us', @request.host_with_port
|
||||
end
|
||||
|
||||
def test_http_host
|
||||
@request_hash.delete "HTTP_X_FORWARDED_HOST"
|
||||
@request_hash['HTTP_HOST'] = "rubyonrails.org:8080"
|
||||
assert_equal "rubyonrails.org:8080", @request.host_with_port
|
||||
|
||||
@request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
|
||||
assert_equal "www.secondhost.org", @request.host(true)
|
||||
end
|
||||
|
||||
def test_http_host_with_default_port_overrides_server_port
|
||||
@request_hash.delete "HTTP_X_FORWARDED_HOST"
|
||||
@request_hash['HTTP_HOST'] = "rubyonrails.org"
|
||||
assert_equal "rubyonrails.org", @request.host_with_port
|
||||
end
|
||||
|
||||
def test_host_with_port_defaults_to_server_name_if_no_host_headers
|
||||
@request_hash.delete "HTTP_X_FORWARDED_HOST"
|
||||
@request_hash.delete "HTTP_HOST"
|
||||
assert_equal "glu.ttono.us:8007", @request.host_with_port
|
||||
end
|
||||
|
||||
def test_host_with_port_falls_back_to_server_addr_if_necessary
|
||||
@request_hash.delete "HTTP_X_FORWARDED_HOST"
|
||||
@request_hash.delete "HTTP_HOST"
|
||||
@request_hash.delete "SERVER_NAME"
|
||||
assert_equal "207.7.108.53:8007", @request.host_with_port
|
||||
end
|
||||
|
||||
def test_host_with_port_if_http_standard_port_is_specified
|
||||
@request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
|
||||
assert_equal "glu.ttono.us", @request.host_with_port
|
||||
end
|
||||
|
||||
def test_host_with_port_if_https_standard_port_is_specified
|
||||
@request_hash['HTTP_X_FORWARDED_PROTO'] = "https"
|
||||
@request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
|
||||
assert_equal "glu.ttono.us", @request.host_with_port
|
||||
end
|
||||
|
||||
def test_host_if_ipv6_reference
|
||||
@request_hash.delete "HTTP_X_FORWARDED_HOST"
|
||||
@request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
|
||||
assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
|
||||
end
|
||||
|
||||
def test_host_if_ipv6_reference_with_port
|
||||
@request_hash.delete "HTTP_X_FORWARDED_HOST"
|
||||
@request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
|
||||
assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
|
||||
end
|
||||
|
||||
def test_cgi_environment_variables
|
||||
assert_equal "Basic", @request.auth_type
|
||||
assert_equal 0, @request.content_length
|
||||
assert_equal nil, @request.content_type
|
||||
assert_equal "CGI/1.1", @request.gateway_interface
|
||||
assert_equal "*/*", @request.accept
|
||||
assert_equal "UTF-8", @request.accept_charset
|
||||
assert_equal "gzip, deflate", @request.accept_encoding
|
||||
assert_equal "en", @request.accept_language
|
||||
assert_equal "no-cache, max-age=0", @request.cache_control
|
||||
assert_equal "googlebot", @request.from
|
||||
assert_equal "glu.ttono.us", @request.host
|
||||
assert_equal "trans", @request.negotiate
|
||||
assert_equal "no-cache", @request.pragma
|
||||
assert_equal "http://www.google.com/search?q=glu.ttono.us", @request.referer
|
||||
assert_equal "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", @request.user_agent
|
||||
assert_equal "/homepage/", @request.path_info
|
||||
assert_equal "/home/kevinc/sites/typo/public/homepage/", @request.path_translated
|
||||
assert_equal "", @request.query_string
|
||||
assert_equal "207.7.108.53", @request.remote_addr
|
||||
assert_equal "google.com", @request.remote_host
|
||||
assert_equal "kevin", @request.remote_ident
|
||||
assert_equal "kevin", @request.remote_user
|
||||
assert_equal :get, @request.request_method
|
||||
assert_equal "/dispatch.fcgi", @request.script_name
|
||||
assert_equal "glu.ttono.us:8007", @request.server_name
|
||||
assert_equal 8007, @request.server_port
|
||||
assert_equal "HTTP/1.1", @request.server_protocol
|
||||
assert_equal "lighttpd", @request.server_software
|
||||
end
|
||||
|
||||
def test_cookie_syntax_resilience
|
||||
cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
|
||||
assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
|
||||
assert_equal ["yes"], cookies["is_admin"], cookies.inspect
|
||||
|
||||
alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
|
||||
assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
|
||||
assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestParamsParsingTest < BaseCgiTest
|
||||
def test_doesnt_break_when_content_type_has_charset
|
||||
set_content_data 'flamenco=love'
|
||||
|
||||
assert_equal({"flamenco"=> "love"}, @request.request_parameters)
|
||||
end
|
||||
|
||||
def test_doesnt_interpret_request_uri_as_query_string_when_missing
|
||||
@request.env['REQUEST_URI'] = 'foo'
|
||||
assert_equal({}, @request.query_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestContentTypeTest < BaseCgiTest
|
||||
def test_html_content_type_verification
|
||||
@request.env['CONTENT_TYPE'] = Mime::HTML.to_s
|
||||
assert @request.content_type.verify_request?
|
||||
end
|
||||
|
||||
def test_xml_content_type_verification
|
||||
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
|
||||
assert !@request.content_type.verify_request?
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestMethodTest < BaseCgiTest
|
||||
def test_get
|
||||
assert_equal :get, @request.request_method
|
||||
end
|
||||
|
||||
def test_post
|
||||
@request.env['REQUEST_METHOD'] = 'POST'
|
||||
assert_equal :post, @request.request_method
|
||||
end
|
||||
|
||||
def test_put
|
||||
set_content_data '_method=put'
|
||||
|
||||
assert_equal :put, @request.request_method
|
||||
end
|
||||
|
||||
def test_delete
|
||||
set_content_data '_method=delete'
|
||||
|
||||
assert_equal :delete, @request.request_method
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestNeedsRewoundTest < BaseCgiTest
|
||||
def test_body_should_be_rewound
|
||||
data = 'foo'
|
||||
fake_cgi = Struct.new(:env_table, :query_string, :stdinput).new(@request_hash, '', StringIO.new(data))
|
||||
fake_cgi.env_table['CONTENT_LENGTH'] = data.length
|
||||
fake_cgi.env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
|
||||
# Read the request body by parsing params.
|
||||
request = ActionController::CgiRequest.new(fake_cgi)
|
||||
request.request_parameters
|
||||
|
||||
# Should have rewound the body.
|
||||
assert_equal 0, request.body.pos
|
||||
end
|
||||
end
|
||||
|
||||
uses_mocha 'CGI Response' do
|
||||
class CgiResponseTest < BaseCgiTest
|
||||
def setup
|
||||
super
|
||||
@cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n")
|
||||
@response = ActionController::CgiResponse.new(@cgi)
|
||||
@output = StringIO.new('')
|
||||
end
|
||||
|
||||
def test_simple_output
|
||||
@response.body = "Hello, World!"
|
||||
|
||||
@response.out(@output)
|
||||
assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\nHello, World!", @output.string
|
||||
end
|
||||
|
||||
def test_head_request
|
||||
@cgi.env_table['REQUEST_METHOD'] = 'HEAD'
|
||||
@response.body = "Hello, World!"
|
||||
|
||||
@response.out(@output)
|
||||
assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n", @output.string
|
||||
end
|
||||
|
||||
def test_streaming_block
|
||||
@response.body = Proc.new do |response, output|
|
||||
5.times { |n| output.write(n) }
|
||||
end
|
||||
|
||||
@response.out(@output)
|
||||
assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n01234", @output.string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,7 +6,6 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
Dispatcher = ActionController::Dispatcher
|
||||
|
||||
def setup
|
||||
@output = StringIO.new
|
||||
ENV['REQUEST_METHOD'] = 'GET'
|
||||
|
||||
# Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
|
||||
|
@ -16,7 +15,7 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
|
||||
Dispatcher.stubs(:require_dependency)
|
||||
|
||||
@dispatcher = Dispatcher.new(@output)
|
||||
@dispatcher = Dispatcher.new
|
||||
end
|
||||
|
||||
def teardown
|
||||
|
@ -25,17 +24,17 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
|
||||
def test_clears_dependencies_after_dispatch_if_in_loading_mode
|
||||
ActiveSupport::Dependencies.expects(:clear).once
|
||||
dispatch(@output, false)
|
||||
dispatch(false)
|
||||
end
|
||||
|
||||
def test_reloads_routes_before_dispatch_if_in_loading_mode
|
||||
ActionController::Routing::Routes.expects(:reload).once
|
||||
dispatch(@output, false)
|
||||
dispatch(false)
|
||||
end
|
||||
|
||||
def test_clears_asset_tag_cache_before_dispatch_if_in_loading_mode
|
||||
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.expects(:clear).once
|
||||
dispatch(@output, false)
|
||||
dispatch(false)
|
||||
end
|
||||
|
||||
def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode
|
||||
|
@ -51,12 +50,16 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_failsafe_response
|
||||
CGI.expects(:new).raises('some multipart parsing failure')
|
||||
Dispatcher.expects(:log_failsafe_exception)
|
||||
Dispatcher.any_instance.expects(:dispatch_unlocked).raises('b00m')
|
||||
ActionController::Failsafe.any_instance.expects(:log_failsafe_exception)
|
||||
|
||||
assert_nothing_raised { dispatch }
|
||||
|
||||
assert_equal "Status: 400 Bad Request\r\nContent-Type: text/html\r\n\r\n<html><body><h1>400 Bad Request</h1></body></html>", @output.string
|
||||
assert_nothing_raised do
|
||||
assert_equal [
|
||||
500,
|
||||
{"Content-Type" => "text/html"},
|
||||
"<html><body><h1>500 Internal Server Error</h1></body></html>"
|
||||
], dispatch
|
||||
end
|
||||
end
|
||||
|
||||
def test_prepare_callbacks
|
||||
|
@ -77,7 +80,7 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
|
||||
# Make sure they are only run once
|
||||
a = b = c = nil
|
||||
@dispatcher.send :dispatch
|
||||
dispatch
|
||||
assert_nil a || b || c
|
||||
end
|
||||
|
||||
|
@ -92,15 +95,10 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
private
|
||||
def dispatch(output = @output, cache_classes = true)
|
||||
controller = mock
|
||||
controller.stubs(:process).returns(controller)
|
||||
controller.stubs(:out).with(output).returns('response')
|
||||
|
||||
ActionController::Routing::Routes.stubs(:recognize).returns(controller)
|
||||
|
||||
def dispatch(cache_classes = true)
|
||||
Dispatcher.any_instance.stubs(:handle_request).returns([200, {}, 'response'])
|
||||
Dispatcher.define_dispatcher_callbacks(cache_classes)
|
||||
Dispatcher.dispatch(nil, {}, output)
|
||||
@dispatcher.call({})
|
||||
end
|
||||
|
||||
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
|
||||
|
|
|
@ -230,14 +230,13 @@ class RackResponseTest < BaseRackTest
|
|||
def setup
|
||||
super
|
||||
@response = ActionController::RackResponse.new(@request)
|
||||
@output = StringIO.new('')
|
||||
end
|
||||
|
||||
def test_simple_output
|
||||
@response.body = "Hello, World!"
|
||||
@response.prepare!
|
||||
|
||||
status, headers, body = @response.out(@output)
|
||||
status, headers, body = @response.out
|
||||
assert_equal "200 OK", status
|
||||
assert_equal({
|
||||
"Content-Type" => "text/html; charset=utf-8",
|
||||
|
@ -258,7 +257,7 @@ class RackResponseTest < BaseRackTest
|
|||
end
|
||||
@response.prepare!
|
||||
|
||||
status, headers, body = @response.out(@output)
|
||||
status, headers, body = @response.out
|
||||
assert_equal "200 OK", status
|
||||
assert_equal({"Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers)
|
||||
|
||||
|
@ -274,7 +273,7 @@ class RackResponseTest < BaseRackTest
|
|||
@response.body = "Hello, World!"
|
||||
@response.prepare!
|
||||
|
||||
status, headers, body = @response.out(@output)
|
||||
status, headers, body = @response.out
|
||||
assert_equal "200 OK", status
|
||||
assert_equal({
|
||||
"Content-Type" => "text/html; charset=utf-8",
|
||||
|
@ -294,7 +293,6 @@ class RackResponseHeadersTest < BaseRackTest
|
|||
def setup
|
||||
super
|
||||
@response = ActionController::RackResponse.new(@request)
|
||||
@output = StringIO.new('')
|
||||
@response.headers['Status'] = "200 OK"
|
||||
end
|
||||
|
||||
|
@ -317,6 +315,6 @@ class RackResponseHeadersTest < BaseRackTest
|
|||
private
|
||||
def response_headers
|
||||
@response.prepare!
|
||||
@response.out(@output)[1]
|
||||
@response.out[1]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -637,7 +637,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
|
|||
input = {
|
||||
"customers[boston][first][name]" => [ "David" ],
|
||||
"something_else" => [ "blah" ],
|
||||
"logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ]
|
||||
"logo" => [ File.new(File.dirname(__FILE__) + "/rack_test.rb").path ]
|
||||
}
|
||||
|
||||
expected_output = {
|
||||
|
@ -649,7 +649,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
|
|||
}
|
||||
},
|
||||
"something_else" => "blah",
|
||||
"logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path,
|
||||
"logo" => File.new(File.dirname(__FILE__) + "/rack_test.rb").path,
|
||||
}
|
||||
|
||||
assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
|
||||
|
|
Loading…
Reference in New Issue