diff options
author | Michael Koziarski <michael@koziarski.com> | 2008-06-04 11:05:46 +1200 |
---|---|---|
committer | Michael Koziarski <michael@koziarski.com> | 2008-06-04 11:05:46 +1200 |
commit | e3c26e9926948587efcc8d31c729395093407df6 (patch) | |
tree | 358f418791a56620c32a48776739b4fdf39d3cb0 /actionpack | |
parent | b9a9b91a3e3b892ab72ff5c618181747d6b4be04 (diff) | |
parent | 8afa725f4b98a6e0ceee4792e8ebaebb6189e5f6 (diff) | |
download | rails-e3c26e9926948587efcc8d31c729395093407df6.tar.gz rails-e3c26e9926948587efcc8d31c729395093407df6.tar.bz2 rails-e3c26e9926948587efcc8d31c729395093407df6.zip |
Merge branch 'master' into patches
Diffstat (limited to 'actionpack')
-rw-r--r-- | actionpack/CHANGELOG | 9 | ||||
-rw-r--r-- | actionpack/Rakefile | 2 | ||||
-rwxr-xr-x | actionpack/lib/action_controller.rb | 1 | ||||
-rw-r--r-- | actionpack/lib/action_controller/caching/actions.rb | 20 | ||||
-rw-r--r-- | actionpack/lib/action_controller/dispatcher.rb | 10 | ||||
-rw-r--r-- | actionpack/lib/action_controller/rack_process.rb | 325 | ||||
-rw-r--r-- | actionpack/lib/action_controller/routing.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_pack/version.rb | 4 | ||||
-rw-r--r-- | actionpack/test/controller/caching_test.rb | 15 | ||||
-rwxr-xr-x | actionpack/test/controller/cgi_test.rb | 33 | ||||
-rw-r--r-- | actionpack/test/controller/dispatcher_test.rb | 4 | ||||
-rw-r--r-- | actionpack/test/controller/integration_upload_test.rb | 2 | ||||
-rw-r--r-- | actionpack/test/controller/rack_test.rb | 198 | ||||
-rw-r--r-- | actionpack/test/controller/routing_test.rb | 4 |
14 files changed, 613 insertions, 16 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 5c4bfbf3c9..ba2b16849c 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,6 +1,11 @@ -* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing] +* Allow caches_action to accept a layout option [José Valim] + +* Added Rack processor [Ezra Zygmuntowicz, Josh Peek] + -*2.1.0 RC1 (May 11th, 2008)* +*2.1.0 (May 31st, 2008)* + +* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing] * Fixed that forgery protection can be used without session tracking (Peter Jones) [#139] diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 0147a5c1e8..b37f756c1e 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -76,7 +76,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 810a5fb9b5..3c4a339d50 100755 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -53,6 +53,7 @@ require 'action_controller/streaming' require 'action_controller/session_management' require 'action_controller/http_authentication' require 'action_controller/components' +require 'action_controller/rack_process' require 'action_controller/record_identifier' require 'action_controller/request_forgery_protection' require 'action_controller/headers' diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 1ef9e60a21..c4b0a97a33 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -40,6 +40,8 @@ module ActionController #:nodoc: # controller.send(:list_url, c.params[:id]) } # end # + # If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information. + # module Actions def self.included(base) #:nodoc: base.extend(ClassMethods) @@ -54,7 +56,8 @@ module ActionController #:nodoc: def caches_action(*actions) return unless cache_configured? options = actions.extract_options! - around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options)) + cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path)) + around_filter(cache_filter, {:only => actions}.merge(options)) end end @@ -81,7 +84,9 @@ module ActionController #:nodoc: if cache = controller.read_fragment(cache_path.path) controller.rendered_action_cache = true set_content_type!(controller, cache_path.extension) - controller.send!(:render_for_text, cache) + options = { :text => cache } + options.merge!(:layout => true) if cache_layout? + controller.send!(:render, options) false else controller.action_cache_path = cache_path @@ -90,7 +95,8 @@ module ActionController #:nodoc: def after(controller) return if controller.rendered_action_cache || !caching_allowed(controller) - controller.write_fragment(controller.action_cache_path.path, controller.response.body) + action_content = cache_layout? ? content_for_layout(controller) : controller.response.body + controller.write_fragment(controller.action_cache_path.path, action_content) end private @@ -105,6 +111,14 @@ module ActionController #:nodoc: def caching_allowed(controller) controller.request.get? && controller.response.headers['Status'].to_i == 200 end + + def cache_layout? + @options[:layout] == false + end + + def content_for_layout(controller) + controller.response.layout && controller.response.template.instance_variable_get('@content_for_layout') + end end class ActionCachePath diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 6e1e7a261f..fe4f6b4a7e 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -96,7 +96,7 @@ module ActionController include ActiveSupport::Callbacks define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch - def initialize(output, request = nil, response = nil) + def initialize(output = $stdout, request = nil, response = nil) @output, @request, @response = output, request, response end @@ -123,6 +123,12 @@ module ActionController failsafe_rescue exception end + def call(env) + @request = RackRequest.new(env) + @response = RackResponse.new(@request) + dispatch + end + def reload_application # Run prepare callbacks before every request in development mode run_callbacks :prepare_dispatch @@ -135,7 +141,7 @@ module ActionController # be reloaded on the next request without restarting the server. def cleanup_application ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) - Dependencies.clear + ActiveSupport::Dependencies.clear ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) end diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb new file mode 100644 index 0000000000..d5fb78c44d --- /dev/null +++ b/actionpack/lib/action_controller/rack_process.rb @@ -0,0 +1,325 @@ +require 'action_controller/cgi_ext' +require 'action_controller/session/cookie_store' + +module ActionController #:nodoc: + class RackRequest < AbstractRequest #:nodoc: + attr_accessor :env, :session_options + attr_reader :cgi + + 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 + } unless const_defined?(:DEFAULT_SESSION_OPTIONS) + + def initialize(env, session_options = DEFAULT_SESSION_OPTIONS) + @session_options = session_options + @env = env + @cgi = CGIWrapper.new(self) + super() + end + + # The request body is an IO input stream. If the RAW_POST_DATA environment + # variable is already set, wrap it in a StringIO. + def body + if raw_post = env['RAW_POST_DATA'] + StringIO.new(raw_post) + else + @env['rack.input'] + end + end + + def key?(key) + @env.key? key + end + + def query_parameters + @query_parameters ||= self.class.parse_query_parameters(query_string) + end + + def request_parameters + @request_parameters ||= parse_formatted_request_parameters + end + + def cookies + return {} unless @env["HTTP_COOKIE"] + + if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"] + @env["rack.request.cookie_hash"] + else + @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"] + # According to RFC 2109: + # If multiple cookies satisfy the criteria above, they are ordered in + # the Cookie header such that those with more specific Path attributes + # precede those with less specific. Ordering with respect to other + # attributes (e.g., Domain) is unspecified. + @env["rack.request.cookie_hash"] = + parse_query(@env["rack.request.cookie_string"], ';,').inject({}) { |h, (k,v)| + h[k] = Array === v ? v.first : v + h + } + end + end + + def host_with_port_without_standard_port_handling + if forwarded = @env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + elsif http_host = @env['HTTP_HOST'] + http_host + elsif server_name = @env['SERVER_NAME'] + server_name + else + "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + end + end + + def host + host_with_port_without_standard_port_handling.sub(/:\d+$/, '') + end + + def port + if host_with_port_without_standard_port_handling =~ /:(\d+)$/ + $1.to_i + else + standard_port + end + end + + def remote_addr + @env['REMOTE_ADDR'] + 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 + + 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)) + 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 + else + raise + end + end + + def session_options_with_string_keys + @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys + end + + # From Rack::Utils + def parse_query(qs, d = '&;') + params = {} + (qs || '').split(/[#{d}] */n).inject(params) { |h,p| + k, v = unescape(p).split('=',2) + if cur = params[k] + if cur.class == Array + params[k] << v + else + params[k] = [cur, v] + end + else + params[k] = v + end + } + + return params + end + + def unescape(s) + s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ + [$1.delete('%')].pack('H*') + } + end + end + + class RackResponse < AbstractResponse #:nodoc: + attr_accessor :status + + def initialize(request) + @request = request + @writer = lambda { |x| @body << x } + @block = nil + super() + end + + def out(output = $stdout, &block) + @block = block + normalize_headers(@headers) + if [204, 304].include?(@status.to_i) + @headers.delete "Content-Type" + [status.to_i, @headers.to_hash, []] + else + [status.to_i, @headers.to_hash, self] + end + end + alias to_a out + + def each(&callback) + if @body.respond_to?(:call) + @writer = lambda { |x| callback.call(x) } + @body.call(self, self) + else + @body.each(&callback) + end + + @writer = callback + @block.call(self) if @block + end + + def write(str) + @writer.call str.to_s + str + end + + def close + @body.close if @body.respond_to?(:close) + end + + def empty? + @block == nil && @body.empty? + end + + private + def normalize_headers(options = "text/html") + if options.is_a?(String) + headers['Content-Type'] = options unless headers['Content-Type'] + else + headers['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length'] + + headers['Content-Type'] = options.delete('type') || "text/html" + headers['Content-Type'] += "; charset=" + options.delete('charset') if options['charset'] + + headers['Content-Language'] = options.delete('language') if options['language'] + headers['Expires'] = options.delete('expires') if options['expires'] + + @status = options.delete('Status') if options['Status'] + @status ||= 200 + # Convert 'cookie' header to 'Set-Cookie' headers. + # Because Set-Cookie header can appear more the once in the response body, + # we store it in a line break seperated string that will be translated to + # multiple Set-Cookie header by the handler. + if cookie = options.delete('cookie') + cookies = [] + + case cookie + when Array then cookie.each { |c| cookies << c.to_s } + when Hash then cookie.each { |_, c| cookies << c.to_s } + else cookies << cookie.to_s + end + + @request.cgi.output_cookies.each { |c| cookies << c.to_s } if @request.cgi.output_cookies + + headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact + end + + options.each { |k,v| headers[k] = v } + end + + "" + end + end + + class CGIWrapper < ::CGI + attr_reader :output_cookies + + def initialize(request, *args) + @request = request + @args = *args + @input = request.body + + super *args + end + + def params + @params ||= @request.params + end + + def cookies + @request.cookies + end + + def query_string + @request.query_string + end + + # Used to wrap the normal args variable used inside CGI. + def args + @args + end + + # Used to wrap the normal env_table variable used inside CGI. + def env_table + @request.env + end + + # Used to wrap the normal stdinput variable used inside CGI. + def stdinput + @input + end + end +end diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index 6aa266513d..8846dcc504 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -369,7 +369,7 @@ module ActionController Routes = RouteSet.new - ::Inflector.module_eval do + ActiveSupport::Inflector.module_eval do # Ensures that routes are reloaded when Rails inflections are updated. def inflections_with_route_reloading(&block) returning(inflections_without_route_reloading(&block)) { diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 70fc1ced8c..c67654d9a8 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,8 +1,8 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 - MINOR = 0 - TINY = 991 + MINOR = 1 + TINY = 0 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index f9b6b87bc6..c6d61bb504 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -156,6 +156,7 @@ class ActionCachingTestController < ActionController::Base caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } caches_action :with_layout + caches_action :layout_false, :layout => false layout 'talk_from_action.erb' @@ -181,6 +182,7 @@ class ActionCachingTestController < ActionController::Base alias_method :show, :index alias_method :edit, :index alias_method :destroy, :index + alias_method :layout_false, :with_layout def expire expire_action :controller => 'action_caching_test', :action => 'index' @@ -263,6 +265,19 @@ class ActionCacheTest < Test::Unit::TestCase assert_equal @response.body, read_fragment('hostname.com/action_caching_test/with_layout') end + def test_action_cache_with_layout_and_layout_cache_false + get :layout_false + cached_time = content_to_cache + assert_not_equal cached_time, @response.body + assert fragment_exist?('hostname.com/action_caching_test/layout_false') + reset! + + get :layout_false + assert_not_equal cached_time, @response.body + + assert_equal cached_time, read_fragment('hostname.com/action_caching_test/layout_false') + end + def test_action_cache_conditional_options @request.env['HTTP_ACCEPT'] = 'application/json' get :index diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb index 87f72fda77..f0f3a4b826 100755 --- a/actionpack/test/controller/cgi_test.rb +++ b/actionpack/test/controller/cgi_test.rb @@ -114,3 +114,36 @@ class CgiRequestNeedsRewoundTest < BaseCgiTest assert_equal 0, request.body.pos end end + +class CgiResponseTest < BaseCgiTest + def setup + super + @fake_cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n") + @response = ActionController::CgiResponse.new(@fake_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 + @fake_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 diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb index eea0813ed5..911fcab67b 100644 --- a/actionpack/test/controller/dispatcher_test.rb +++ b/actionpack/test/controller/dispatcher_test.rb @@ -27,14 +27,14 @@ class DispatcherTest < Test::Unit::TestCase def test_clears_dependencies_after_dispatch_if_in_loading_mode ActionController::Routing::Routes.expects(:reload).once - Dependencies.expects(:clear).once + ActiveSupport::Dependencies.expects(:clear).once dispatch(@output, false) end def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode ActionController::Routing::Routes.expects(:reload).never - Dependencies.expects(:clear).never + ActiveSupport::Dependencies.expects(:clear).never dispatch end diff --git a/actionpack/test/controller/integration_upload_test.rb b/actionpack/test/controller/integration_upload_test.rb index 33df1131cb..4af9b7e697 100644 --- a/actionpack/test/controller/integration_upload_test.rb +++ b/actionpack/test/controller/integration_upload_test.rb @@ -28,7 +28,7 @@ class SessionUploadTest < ActionController::IntegrationTest # end def test_post_with_upload uses_mocha "test_post_with_upload" do - Dependencies.stubs(:load?).returns(false) + ActiveSupport::Dependencies.stubs(:load?).returns(false) with_routing do |set| set.draw do |map| map.update 'update', :controller => "upload_test", :action => "update", :method => :post diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb new file mode 100644 index 0000000000..026b0195d1 --- /dev/null +++ b/actionpack/test/controller/rack_test.rb @@ -0,0 +1,198 @@ +require 'abstract_unit' +require 'action_controller/rack_process' + +class BaseRackTest < Test::Unit::TestCase + def setup + @env = { + "HTTP_MAX_FORWARDS" => "10", + "SERVER_NAME" => "glu.ttono.us:8007", + "FCGI_ROLE" => "RESPONDER", + "HTTP_X_FORWARDED_HOST" => "glu.ttono.us", + "HTTP_ACCEPT_ENCODING" => "gzip, deflate", + "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", + "PATH_INFO" => "", + "HTTP_ACCEPT_LANGUAGE" => "en", + "HTTP_HOST" => "glu.ttono.us:8007", + "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", + "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", + "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"} + @request = ActionController::RackRequest.new(@env) + end + + def default_test; end +end + + +class RackRequestTest < BaseRackTest + def test_proxy_request + assert_equal 'glu.ttono.us', @request.host_with_port + end + + def test_http_host + @env.delete "HTTP_X_FORWARDED_HOST" + @env['HTTP_HOST'] = "rubyonrails.org:8080" + assert_equal "rubyonrails.org:8080", @request.host_with_port + + @env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org" + assert_equal "www.secondhost.org", @request.host + end + + def test_http_host_with_default_port_overrides_server_port + @env.delete "HTTP_X_FORWARDED_HOST" + @env['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 + @env.delete "HTTP_X_FORWARDED_HOST" + @env.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 + @env.delete "HTTP_X_FORWARDED_HOST" + @env.delete "HTTP_HOST" + @env.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 + @env['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 + @env['HTTP_X_FORWARDED_PROTO'] = "https" + @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443" + assert_equal "glu.ttono.us", @request.host_with_port + end + + def test_host_if_ipv6_reference + @env.delete "HTTP_X_FORWARDED_HOST" + @env['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 + @env.delete "HTTP_X_FORWARDED_HOST" + @env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008" + assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host + end + + def test_cookie_syntax_resilience + cookies = CGI::Cookie::parse(@env["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 RackRequestParamsParsingTest < BaseRackTest + def test_doesnt_break_when_content_type_has_charset + data = 'flamenco=love' + @request.env['CONTENT_LENGTH'] = data.length + @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8' + @request.env['RAW_POST_DATA'] = data + 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 RackRequestNeedsRewoundTest < BaseRackTest + def test_body_should_be_rewound + data = 'foo' + @env['rack.input'] = StringIO.new(data) + @env['CONTENT_LENGTH'] = data.length + @env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8' + + # Read the request body by parsing params. + request = ActionController::RackRequest.new(@env) + request.request_parameters + + # Should have rewound the body. + assert_equal 0, request.body.pos + end +end + + +class RackResponseTest < BaseRackTest + def setup + super + @response = ActionController::RackResponse.new(@request) + @output = StringIO.new('') + end + + def test_simple_output + @response.body = "Hello, World!" + + status, headers, body = @response.out(@output) + assert_equal 200, status + assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) + + parts = [] + body.each { |part| parts << part } + assert_equal ["Hello, World!"], parts + end + + def test_streaming_block + @response.body = Proc.new do |response, output| + 5.times { |n| output.write(n) } + end + + status, headers, body = @response.out(@output) + assert_equal 200, status + assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) + + parts = [] + body.each { |part| parts << part } + assert_equal ["0", "1", "2", "3", "4"], parts + end + + def test_set_session_cookie + cookie = CGI::Cookie.new({"name" => "name", "value" => "Josh"}) + @request.cgi.send :instance_variable_set, '@output_cookies', [cookie] + + @response.body = "Hello, World!" + + status, headers, body = @response.out(@output) + assert_equal 200, status + assert_equal({ + "Content-Type" => "text/html", + "Cache-Control" => "no-cache", + "Set-Cookie" => ["name=Josh; path="] + }, headers) + + parts = [] + body.each { |part| parts << part } + assert_equal ["Hello, World!"], parts + end +end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 5e5503fd52..068d71a8f8 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -2392,10 +2392,10 @@ uses_mocha 'route loading' do end def test_adding_inflections_forces_reload - Inflector::Inflections.instance.expects(:uncountable).with('equipment') + ActiveSupport::Inflector::Inflections.instance.expects(:uncountable).with('equipment') routes.expects(:reload!) - Inflector.inflections { |inflect| inflect.uncountable('equipment') } + ActiveSupport::Inflector.inflections { |inflect| inflect.uncountable('equipment') } end def test_load_with_configuration |