diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2009-04-22 15:26:03 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2009-04-22 15:26:03 +0100 |
commit | 5f3f100ce2d689480da85abc88e5e940cf90189e (patch) | |
tree | 15c1a05a5308a9eea56d7f0889ac46d9cac5b57c /actionpack/test/dispatch | |
parent | d758d996d1b66e2a65640f79f01ce2ac674d7ed5 (diff) | |
parent | ca49299434bc764b667cd86846d892e91a150ef3 (diff) | |
download | rails-5f3f100ce2d689480da85abc88e5e940cf90189e.tar.gz rails-5f3f100ce2d689480da85abc88e5e940cf90189e.tar.bz2 rails-5f3f100ce2d689480da85abc88e5e940cf90189e.zip |
Merge branch 'master' into active_model
Conflicts:
activeresource/lib/active_resource/validations.rb
Diffstat (limited to 'actionpack/test/dispatch')
-rw-r--r-- | actionpack/test/dispatch/header_test.rb | 16 | ||||
-rw-r--r-- | actionpack/test/dispatch/middleware_stack_test.rb | 90 | ||||
-rw-r--r-- | actionpack/test/dispatch/mime_type_test.rb | 96 | ||||
-rw-r--r-- | actionpack/test/dispatch/rack_test.rb | 293 | ||||
-rw-r--r-- | actionpack/test/dispatch/request/json_params_parsing_test.rb | 45 | ||||
-rw-r--r-- | actionpack/test/dispatch/request/multipart_params_parsing_test.rb | 223 | ||||
-rw-r--r-- | actionpack/test/dispatch/request/query_string_parsing_test.rb | 120 | ||||
-rw-r--r-- | actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb | 184 | ||||
-rw-r--r-- | actionpack/test/dispatch/request/xml_params_parsing_test.rb | 88 | ||||
-rw-r--r-- | actionpack/test/dispatch/request_test.rb | 405 | ||||
-rw-r--r-- | actionpack/test/dispatch/session/cookie_store_test.rb | 250 | ||||
-rw-r--r-- | actionpack/test/dispatch/session/mem_cache_store_test.rb | 127 | ||||
-rw-r--r-- | actionpack/test/dispatch/session/test_session_test.rb | 58 |
13 files changed, 1995 insertions, 0 deletions
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb new file mode 100644 index 0000000000..ec6ba494dc --- /dev/null +++ b/actionpack/test/dispatch/header_test.rb @@ -0,0 +1,16 @@ +require 'abstract_unit' + +class HeaderTest < ActiveSupport::TestCase + def setup + @headers = ActionDispatch::Http::Headers.new( + "HTTP_CONTENT_TYPE" => "text/plain" + ) + end + + test "content type" do + assert_equal "text/plain", @headers["Content-Type"] + assert_equal "text/plain", @headers["content-type"] + assert_equal "text/plain", @headers["CONTENT_TYPE"] + assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"] + end +end diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb new file mode 100644 index 0000000000..f4e18308ae --- /dev/null +++ b/actionpack/test/dispatch/middleware_stack_test.rb @@ -0,0 +1,90 @@ +require 'abstract_unit' + +class MiddlewareStackTest < ActiveSupport::TestCase + class FooMiddleware; end + class BarMiddleware; end + class BazMiddleware; end + + def setup + @stack = ActionDispatch::MiddlewareStack.new + @stack.use FooMiddleware + @stack.use BarMiddleware + end + + test "use should push middleware as class onto the stack" do + assert_difference "@stack.size" do + @stack.use BazMiddleware + end + assert_equal BazMiddleware, @stack.last.klass + end + + test "use should push middleware as a string onto the stack" do + assert_difference "@stack.size" do + @stack.use "MiddlewareStackTest::BazMiddleware" + end + assert_equal BazMiddleware, @stack.last.klass + end + + test "use should push middleware as a symbol onto the stack" do + assert_difference "@stack.size" do + @stack.use :"MiddlewareStackTest::BazMiddleware" + end + assert_equal BazMiddleware, @stack.last.klass + end + + test "use should push middleware class with arguments onto the stack" do + assert_difference "@stack.size" do + @stack.use BazMiddleware, true, :foo => "bar" + end + assert_equal BazMiddleware, @stack.last.klass + assert_equal([true, {:foo => "bar"}], @stack.last.args) + end + + test "insert inserts middleware at the integer index" do + @stack.insert(1, BazMiddleware) + assert_equal BazMiddleware, @stack[1].klass + end + + test "insert_after inserts middleware after the integer index" do + @stack.insert_after(1, BazMiddleware) + assert_equal BazMiddleware, @stack[2].klass + end + + test "insert_before inserts middleware before another middleware class" do + @stack.insert_before(BarMiddleware, BazMiddleware) + assert_equal BazMiddleware, @stack[1].klass + end + + test "insert_after inserts middleware after another middleware class" do + @stack.insert_after(BarMiddleware, BazMiddleware) + assert_equal BazMiddleware, @stack[2].klass + end + + test "swaps one middleware out for another" do + assert_equal FooMiddleware, @stack[0].klass + @stack.swap(FooMiddleware, BazMiddleware) + assert_equal BazMiddleware, @stack[0].klass + end + + test "active returns all only enabled middleware" do + assert_no_difference "@stack.active.size" do + assert_difference "@stack.size" do + @stack.use BazMiddleware, :if => lambda { false } + end + end + end + + test "lazy evaluates middleware class" do + assert_difference "@stack.size" do + @stack.use lambda { BazMiddleware } + end + assert_equal BazMiddleware, @stack.last.klass + end + + test "lazy evaluates middleware arguments" do + assert_difference "@stack.size" do + @stack.use BazMiddleware, lambda { :foo } + end + assert_equal [:foo], @stack.last.send(:build_args) + end +end diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb new file mode 100644 index 0000000000..2fdf4819bb --- /dev/null +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -0,0 +1,96 @@ +require 'abstract_unit' + +class MimeTypeTest < ActiveSupport::TestCase + Mime::Type.register "image/png", :png unless defined? Mime::PNG + Mime::Type.register "application/pdf", :pdf unless defined? Mime::PDF + + test "parse single" do + Mime::LOOKUP.keys.each do |mime_type| + assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) + end + end + + test "parse without q" do + accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*" + expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL] + assert_equal expect, Mime::Type.parse(accept) + end + + test "parse with q" do + accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,application/pdf,*/*; q=0.2" + expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PDF, Mime::TEXT, Mime::YAML, Mime::ALL] + assert_equal expect, Mime::Type.parse(accept) + end + + # Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP) + test "parse crappy broken acceptlines" do + accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5" + expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL] + assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s } + end + + # Accept header send with user HTTP_USER_AGENT: Mozilla/4.0 + # (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1) + test "parse crappy broken acceptlines2" do + accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*" + expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::ALL ] + assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s } + end + + test "custom type" do + begin + Mime::Type.register("image/gif", :gif) + assert_nothing_raised do + Mime::GIF + assert_equal Mime::GIF, Mime::SET.last + end + ensure + Mime.module_eval { remove_const :GIF if const_defined?(:GIF) } + end + end + + test "type should be equal to symbol" do + assert_equal Mime::HTML, 'application/xhtml+xml' + assert_equal Mime::HTML, :html + end + + test "type convenience methods" do + # Don't test Mime::ALL, since it Mime::ALL#html? == true + types = Mime::SET.to_a.map(&:to_sym).uniq - [:all] + + # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE + types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } + + types.each do |type| + mime = Mime.const_get(type.to_s.upcase) + assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?" + invalid_types = types - [type] + invalid_types.delete(:html) if Mime::Type.html_types.include?(type) + invalid_types.each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" } + end + end + + test "mime all is html" do + assert Mime::ALL.all?, "Mime::ALL is not all?" + assert Mime::ALL.html?, "Mime::ALL is not html?" + end + + test "verifiable mime types" do + all_types = Mime::SET.to_a.map(&:to_sym) + all_types.uniq! + # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE + all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } + verified, unverified = all_types.partition { |type| Mime::Type.browser_generated_types.include? type } + assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" } + assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" } + end + + test "regexp matcher" do + assert Mime::JS =~ "text/javascript" + assert Mime::JS =~ "application/javascript" + assert Mime::JS !~ "text/html" + assert !(Mime::JS !~ "text/javascript") + assert !(Mime::JS !~ "application/javascript") + assert Mime::HTML =~ 'application/xhtml+xml' + end +end diff --git a/actionpack/test/dispatch/rack_test.rb b/actionpack/test/dispatch/rack_test.rb new file mode 100644 index 0000000000..9fad4b22ee --- /dev/null +++ b/actionpack/test/dispatch/rack_test.rb @@ -0,0 +1,293 @@ +require 'abstract_unit' + +# TODO: Merge these tests into RequestTest + +class BaseRackTest < ActiveSupport::TestCase + def setup + @env = { + "HTTP_MAX_FORWARDS" => "10", + "SERVER_NAME" => "glu.ttono.us", + "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" + } + @request = ActionDispatch::Request.new(@env) + # 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 = ActionDispatch::Request.new(@env.merge({"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"})) + 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['rack.input'] = StringIO.new(data) + end +end + +class RackRequestTest < BaseRackTest + test "proxy request" do + assert_equal 'glu.ttono.us', @request.host_with_port + end + + test "http host" do + @env.delete "HTTP_X_FORWARDED_HOST" + @env['HTTP_HOST'] = "rubyonrails.org:8080" + assert_equal "rubyonrails.org", @request.host + 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 + + test "http host with default port overrides server port" do + @env.delete "HTTP_X_FORWARDED_HOST" + @env['HTTP_HOST'] = "rubyonrails.org" + assert_equal "rubyonrails.org", @request.host_with_port + end + + test "host with port defaults to server name if no host headers" do + @env.delete "HTTP_X_FORWARDED_HOST" + @env.delete "HTTP_HOST" + assert_equal "glu.ttono.us:8007", @request.host_with_port + end + + test "host with port falls back to server addr if necessary" do + @env.delete "HTTP_X_FORWARDED_HOST" + @env.delete "HTTP_HOST" + @env.delete "SERVER_NAME" + assert_equal "207.7.108.53", @request.host + assert_equal 8007, @request.port + assert_equal "207.7.108.53:8007", @request.host_with_port + end + + test "host with port if http standard port is specified" do + @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80" + assert_equal "glu.ttono.us", @request.host_with_port + end + + test "host with port if https standard port is specified" do + @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 + + test "host if ipv6 reference" do + @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 + + test "host if ipv6 reference with port" do + @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 + + test "cgi environment variables" do + 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", @request.server_name + assert_equal 8007, @request.server_port + assert_equal "HTTP/1.1", @request.server_protocol + assert_equal "lighttpd", @request.server_software + end + + test "cookie syntax resilience" do + cookies = @request.cookies + assert_equal "c84ace84796670c052c6ceb2451fb0f2", cookies["_session_id"], cookies.inspect + assert_equal "yes", cookies["is_admin"], cookies.inspect + + alt_cookies = @alt_cookie_fmt_request.cookies + #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 + test "doesnt break when content type has charset" do + set_content_data 'flamenco=love' + + assert_equal({"flamenco"=> "love"}, @request.request_parameters) + end + + test "doesnt interpret request uri as query string when missing" do + @request.env['REQUEST_URI'] = 'foo' + assert_equal({}, @request.query_parameters) + end +end + +class RackRequestContentTypeTest < BaseRackTest + test "html content type verification" do + @request.env['CONTENT_TYPE'] = Mime::HTML.to_s + assert @request.content_type.verify_request? + end + + test "xml content type verification" do + @request.env['CONTENT_TYPE'] = Mime::XML.to_s + assert !@request.content_type.verify_request? + end +end + +class RackRequestNeedsRewoundTest < BaseRackTest + test "body should be rewound" do + 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 = ActionDispatch::Request.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 = ActionDispatch::Response.new + end + + test "simple output" do + @response.body = "Hello, World!" + @response.prepare! + + status, headers, body = @response.to_a + assert_equal 200, status + assert_equal({ + "Content-Type" => "text/html; charset=utf-8", + "Cache-Control" => "private, max-age=0, must-revalidate", + "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"', + "Set-Cookie" => "", + "Content-Length" => "13" + }, headers) + + parts = [] + body.each { |part| parts << part } + assert_equal ["Hello, World!"], parts + end + + def test_utf8_output + @response.body = [1090, 1077, 1089, 1090].pack("U*") + @response.prepare! + + status, headers, body = @response.to_a + assert_equal 200, status + assert_equal({ + "Content-Type" => "text/html; charset=utf-8", + "Cache-Control" => "private, max-age=0, must-revalidate", + "ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"', + "Set-Cookie" => "", + "Content-Length" => "8" + }, headers) + end + + def test_streaming_block + @response.body = Proc.new do |response, output| + 5.times { |n| output.write(n) } + end + @response.prepare! + + status, headers, body = @response.to_a + assert_equal 200, status + assert_equal({ + "Content-Type" => "text/html; charset=utf-8", + "Cache-Control" => "no-cache", + "Set-Cookie" => "" + }, headers) + + parts = [] + body.each { |part| parts << part.to_s } + assert_equal ["0", "1", "2", "3", "4"], parts + end +end + +class RackResponseHeadersTest < BaseRackTest + def setup + super + @response = ActionDispatch::Response.new + @response.status = "200 OK" + end + + test "content type" do + [204, 304].each do |c| + @response.status = c.to_s + assert !response_headers.has_key?("Content-Type"), "#{c} should not have Content-Type header" + end + + [200, 302, 404, 500].each do |c| + @response.status = c.to_s + assert response_headers.has_key?("Content-Type"), "#{c} did not have Content-Type header" + end + end + + test "status" do + assert !response_headers.has_key?('Status') + end + + private + def response_headers + @response.prepare! + @response.to_a[1] + end +end diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb new file mode 100644 index 0000000000..a3dde72c4e --- /dev/null +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -0,0 +1,45 @@ +require 'abstract_unit' + +class JsonParamsParsingTest < ActionController::IntegrationTest + class TestController < ActionController::Base + class << self + attr_accessor :last_request_parameters + end + + def parse + self.class.last_request_parameters = request.request_parameters + head :ok + end + end + + def teardown + TestController.last_request_parameters = nil + end + + test "parses json params for application json" do + assert_parses( + {"person" => {"name" => "David"}}, + "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + + test "parses json params for application jsonrequest" do + assert_parses( + {"person" => {"name" => "David"}}, + "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/jsonrequest' } + ) + end + + private + def assert_parses(expected, actual, headers = {}) + with_routing do |set| + set.draw do |map| + map.connect ':action', :controller => "json_params_parsing_test/test" + end + + post "/parse", actual, headers + assert_response :ok + assert_equal(expected, TestController.last_request_parameters) + end + end +end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb new file mode 100644 index 0000000000..88b81dc493 --- /dev/null +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -0,0 +1,223 @@ +require 'abstract_unit' + +class MultipartParamsParsingTest < ActionController::IntegrationTest + class TestController < ActionController::Base + class << self + attr_accessor :last_request_parameters + end + + def parse + self.class.last_request_parameters = request.request_parameters + head :ok + end + + def read + render :text => "File: #{params[:uploaded_data].read}" + end + end + + FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/multipart' + + def teardown + TestController.last_request_parameters = nil + end + + test "parses single parameter" do + assert_equal({ 'foo' => 'bar' }, parse_multipart('single_parameter')) + end + + test "parses bracketed parameters" do + assert_equal({ 'foo' => { 'baz' => 'bar'}}, parse_multipart('bracketed_param')) + end + + test "parses text file" do + params = parse_multipart('text_file') + assert_equal %w(file foo), params.keys.sort + assert_equal 'bar', params['foo'] + + file = params['file'] + assert_kind_of Tempfile, file + assert_equal 'file.txt', file.original_filename + assert_equal "text/plain", file.content_type + assert_equal 'contents', file.read + end + + test "parses boundary problem file" do + params = parse_multipart('boundary_problem_file') + assert_equal %w(file foo), params.keys.sort + + file = params['file'] + foo = params['foo'] + + assert_kind_of Tempfile, file + + assert_equal 'file.txt', file.original_filename + assert_equal "text/plain", file.content_type + + assert_equal 'bar', foo + end + + test "parses large text file" do + params = parse_multipart('large_text_file') + assert_equal %w(file foo), params.keys.sort + assert_equal 'bar', params['foo'] + + file = params['file'] + + assert_kind_of Tempfile, file + + assert_equal 'file.txt', file.original_filename + assert_equal "text/plain", file.content_type + assert ('a' * 20480) == file.read + end + + test "parses binary file" do + params = parse_multipart('binary_file') + assert_equal %w(file flowers foo), params.keys.sort + assert_equal 'bar', params['foo'] + + file = params['file'] + assert_kind_of Tempfile, file + assert_equal 'file.csv', file.original_filename + assert_nil file.content_type + assert_equal 'contents', file.read + + file = params['flowers'] + assert_kind_of Tempfile, file + assert_equal 'flowers.jpg', file.original_filename + assert_equal "image/jpeg", file.content_type + assert_equal 19512, file.size + end + + test "parses mixed files" do + params = parse_multipart('mixed_files') + assert_equal %w(files foo), params.keys.sort + assert_equal 'bar', params['foo'] + + # Ruby CGI doesn't handle multipart/mixed for us. + files = params['files'] + assert_kind_of String, files + files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) + assert_equal 19756, files.size + end + + test "does not create tempfile if no file has been selected" do + params = parse_multipart('none') + assert_equal %w(submit-name), params.keys.sort + assert_equal 'Larry', params['submit-name'] + assert_equal nil, params['files'] + end + + test "parses empty upload file" do + params = parse_multipart('empty') + assert_equal %w(files submit-name), params.keys.sort + assert_equal 'Larry', params['submit-name'] + assert params['files'] + assert_equal "", params['files'].read + end + + test "uploads and reads binary file" do + with_test_routing do + fixture = FIXTURE_PATH + "/mona_lisa.jpg" + params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") } + post '/read', params + expected_length = 'File: '.length + File.size(fixture) + assert_equal expected_length, response.content_length + end + end + + test "uploads and reads file" do + with_test_routing do + post '/read', :uploaded_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") + assert_equal "File: Hello", response.body + end + end + + # The lint wrapper is used in integration tests + # instead of a normal StringIO class + InputWrapper = Rack::Lint::InputWrapper + + test "parses unwindable stream" do + InputWrapper.any_instance.stubs(:rewind).raises(Errno::ESPIPE) + params = parse_multipart('large_text_file') + assert_equal %w(file foo), params.keys.sort + assert_equal 'bar', params['foo'] + end + + test "uploads and reads file with unwindable input" do + InputWrapper.any_instance.stubs(:rewind).raises(Errno::ESPIPE) + + with_test_routing do + post '/read', :uploaded_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") + assert_equal "File: Hello", response.body + end + end + + test "passes through rack middleware and uploads file" do + with_muck_middleware do + with_test_routing do + post '/read', :uploaded_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") + assert_equal "File: Hello", response.body + end + end + end + + test "passes through rack middleware and uploads file with unwindable input" do + InputWrapper.any_instance.stubs(:rewind).raises(Errno::ESPIPE) + + with_muck_middleware do + with_test_routing do + post '/read', :uploaded_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") + assert_equal "File: Hello", response.body + end + end + end + + private + def fixture(name) + File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| + { "rack.input" => file.read, + "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", + "CONTENT_LENGTH" => file.stat.size.to_s } + end + end + + def parse_multipart(name) + with_test_routing do + headers = fixture(name) + post "/parse", headers.delete("rack.input"), headers + assert_response :ok + TestController.last_request_parameters + end + end + + def with_test_routing + with_routing do |set| + set.draw do |map| + map.connect ':action', :controller => "multipart_params_parsing_test/test" + end + yield + end + end + + class MuckMiddleware + def initialize(app) + @app = app + end + + def call(env) + env['rack.input'].read + env['rack.input'].rewind + @app.call(env) + end + end + + def with_muck_middleware + original_middleware = ActionController::Dispatcher.middleware + middleware = original_middleware.dup + middleware.insert_after ActionDispatch::RewindableInput, MuckMiddleware + ActionController::Dispatcher.middleware = middleware + yield + ActionController::Dispatcher.middleware = original_middleware + end +end diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb new file mode 100644 index 0000000000..a31e326ddf --- /dev/null +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -0,0 +1,120 @@ +require 'abstract_unit' + +class QueryStringParsingTest < ActionController::IntegrationTest + class TestController < ActionController::Base + class << self + attr_accessor :last_query_parameters + end + + def parse + self.class.last_query_parameters = request.query_parameters + head :ok + end + end + + def teardown + TestController.last_query_parameters = nil + end + + test "query string" do + assert_parses( + {"action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"}, + "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" + ) + end + + test "deep query string" do + assert_parses( + {'x' => {'y' => {'z' => '10'}}}, + "x[y][z]=10" + ) + end + + test "deep query string with array" do + assert_parses({'x' => {'y' => {'z' => ['10']}}}, 'x[y][z][]=10') + assert_parses({'x' => {'y' => {'z' => ['10', '5']}}}, 'x[y][z][]=10&x[y][z][]=5') + end + + test "deep query string with array of hash" do + assert_parses({'x' => {'y' => [{'z' => '10'}]}}, 'x[y][][z]=10') + assert_parses({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, 'x[y][][z]=10&x[y][][w]=10') + assert_parses({'x' => {'y' => [{'z' => '10', 'v' => {'w' => '10'}}]}}, 'x[y][][z]=10&x[y][][v][w]=10') + end + + test "deep query string with array of hashes with one pair" do + assert_parses({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, 'x[y][][z]=10&x[y][][z]=20') + end + + test "deep query string with array of hashes with multiple pairs" do + assert_parses( + {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, + 'x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b' + ) + end + + test "query string with nil" do + assert_parses( + { "action" => "create_customer", "full_name" => ''}, + "action=create_customer&full_name=" + ) + end + + test "query string with array" do + assert_parses( + { "action" => "create_customer", "selected" => ["1", "2", "3"]}, + "action=create_customer&selected[]=1&selected[]=2&selected[]=3" + ) + end + + test "query string with amps" do + assert_parses( + { "action" => "create_customer", "name" => "Don't & Does"}, + "action=create_customer&name=Don%27t+%26+Does" + ) + end + + test "query string with many equal" do + assert_parses( + { "action" => "create_customer", "full_name" => "abc=def=ghi"}, + "action=create_customer&full_name=abc=def=ghi" + ) + end + + test "query string without equal" do + assert_parses({ "action" => nil }, "action") + end + + test "query string with empty key" do + assert_parses( + { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" }, + "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save" + ) + end + + test "query string with many ampersands" do + assert_parses( + { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"}, + "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson" + ) + end + + test "unbalanced query string with array" do + assert_parses( + {'location' => ["1", "2"], 'age_group' => ["2"]}, + "location[]=1&location[]=2&age_group[]=2" + ) + end + + private + def assert_parses(expected, actual) + with_routing do |set| + set.draw do |map| + map.connect ':action', :controller => "query_string_parsing_test/test" + end + + get "/parse", actual + assert_response :ok + assert_equal(expected, TestController.last_query_parameters) + end + end +end diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb new file mode 100644 index 0000000000..8de4a83d76 --- /dev/null +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -0,0 +1,184 @@ +require 'abstract_unit' + +class UrlEncodedParamsParsingTest < ActionController::IntegrationTest + class TestController < ActionController::Base + class << self + attr_accessor :last_request_parameters, :last_request_type + end + + def parse + self.class.last_request_parameters = request.request_parameters + head :ok + end + end + + def teardown + TestController.last_request_parameters = nil + end + + test "parses unbalanced query string with array" do + assert_parses( + {'location' => ["1", "2"], 'age_group' => ["2"]}, + "location[]=1&location[]=2&age_group[]=2" + ) + end + + test "parses nested hash" do + query = [ + "note[viewers][viewer][][type]=User", + "note[viewers][viewer][][id]=1", + "note[viewers][viewer][][type]=Group", + "note[viewers][viewer][][id]=2" + ].join("&") + + expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } + assert_parses(expected, query) + end + + test "parses more complex nesting" do + query = [ + "customers[boston][first][name]=David", + "customers[boston][first][url]=http://David", + "customers[boston][second][name]=Allan", + "customers[boston][second][url]=http://Allan", + "something_else=blah", + "something_nil=", + "something_empty=", + "products[first]=Apple Computer", + "products[second]=Pc", + "=Save" + ].join("&") + + expected = { + "customers" => { + "boston" => { + "first" => { + "name" => "David", + "url" => "http://David" + }, + "second" => { + "name" => "Allan", + "url" => "http://Allan" + } + } + }, + "something_else" => "blah", + "something_empty" => "", + "something_nil" => "", + "products" => { + "first" => "Apple Computer", + "second" => "Pc" + } + } + + assert_parses expected, query + end + + test "parses params with array" do + query = "selected[]=1&selected[]=2&selected[]=3" + expected = { "selected" => [ "1", "2", "3" ] } + assert_parses expected, query + end + + test "parses params with nil key" do + query = "=&test2=value1" + expected = { "test2" => "value1" } + assert_parses expected, query + end + + test "parses params with array prefix and hashes" do + query = "a[][b][c]=d" + expected = {"a" => [{"b" => {"c" => "d"}}]} + assert_parses expected, query + end + + test "parses params with complex nesting" do + query = "a[][b][c][][d][]=e" + expected = {"a" => [{"b" => {"c" => [{"d" => ["e"]}]}}]} + assert_parses expected, query + end + + test "parses params with file path" do + query = [ + "customers[boston][first][name]=David", + "something_else=blah", + "logo=#{File.expand_path(__FILE__)}" + ].join("&") + + expected = { + "customers" => { + "boston" => { + "first" => { + "name" => "David" + } + } + }, + "something_else" => "blah", + "logo" => File.expand_path(__FILE__), + } + + assert_parses expected, query + end + + test "parses params with Safari 2 trailing null character" do + query = "selected[]=1&selected[]=2&selected[]=3\0" + expected = { "selected" => [ "1", "2", "3" ] } + assert_parses expected, query + end + + test "passes through rack middleware and parses params" do + with_muck_middleware do + assert_parses({ "a" => { "b" => "c" } }, "a[b]=c") + end + end + + # The lint wrapper is used in integration tests + # instead of a normal StringIO class + InputWrapper = Rack::Lint::InputWrapper + + test "passes through rack middleware and parses params with unwindable input" do + InputWrapper.any_instance.stubs(:rewind).raises(Errno::ESPIPE) + with_muck_middleware do + assert_parses({ "a" => { "b" => "c" } }, "a[b]=c") + end + end + + private + class MuckMiddleware + def initialize(app) + @app = app + end + + def call(env) + env['rack.input'].read + env['rack.input'].rewind + @app.call(env) + end + end + + def with_muck_middleware + original_middleware = ActionController::Dispatcher.middleware + middleware = original_middleware.dup + middleware.insert_after ActionDispatch::RewindableInput, MuckMiddleware + ActionController::Dispatcher.middleware = middleware + yield + ActionController::Dispatcher.middleware = original_middleware + end + + def with_test_routing + with_routing do |set| + set.draw do |map| + map.connect ':action', :controller => "url_encoded_params_parsing_test/test" + end + yield + end + end + + def assert_parses(expected, actual) + with_test_routing do + post "/parse", actual + assert_response :ok + assert_equal(expected, TestController.last_request_parameters) + end + end +end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb new file mode 100644 index 0000000000..ee764e726e --- /dev/null +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -0,0 +1,88 @@ +require 'abstract_unit' + +class XmlParamsParsingTest < ActionController::IntegrationTest + class TestController < ActionController::Base + class << self + attr_accessor :last_request_parameters + end + + def parse + self.class.last_request_parameters = request.request_parameters + head :ok + end + end + + def teardown + TestController.last_request_parameters = nil + end + + test "parses hash params" do + with_test_routing do + xml = "<person><name>David</name></person>" + post "/parse", xml, default_headers + assert_response :ok + assert_equal({"person" => {"name" => "David"}}, TestController.last_request_parameters) + end + end + + test "parses single file" do + with_test_routing do + xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></person>" + post "/parse", xml, default_headers + assert_response :ok + + person = TestController.last_request_parameters + assert_equal "image/jpg", person['person']['avatar'].content_type + assert_equal "me.jpg", person['person']['avatar'].original_filename + assert_equal "ABC", person['person']['avatar'].read + end + end + + test "parses multiple files" do + xml = <<-end_body + <person> + <name>David</name> + <avatars> + <avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar> + <avatar type='file' name='you.gif' content_type='image/gif'>#{ActiveSupport::Base64.encode64('DEF')}</avatar> + </avatars> + </person> + end_body + + with_test_routing do + post "/parse", xml, default_headers + assert_response :ok + end + + person = TestController.last_request_parameters + + assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type + assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename + assert_equal "ABC", person['person']['avatars']['avatar'].first.read + + assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type + assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename + assert_equal "DEF", person['person']['avatars']['avatar'].last.read + end + + private + def with_test_routing + with_routing do |set| + set.draw do |map| + map.connect ':action', :controller => "xml_params_parsing_test/test" + end + yield + end + end + + def default_headers + {'CONTENT_TYPE' => 'application/xml'} + end +end + +class LegacyXmlParamsParsingTest < XmlParamsParsingTest + private + def default_headers + {'HTTP_X_POST_DATA_FORMAT' => 'xml'} + end +end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb new file mode 100644 index 0000000000..d57a2a611f --- /dev/null +++ b/actionpack/test/dispatch/request_test.rb @@ -0,0 +1,405 @@ +require 'abstract_unit' + +class RequestTest < ActiveSupport::TestCase + def setup + ActionController::Base.relative_url_root = nil + end + + def teardown + ActionController::Base.relative_url_root = nil + end + + test "remote ip" do + request = stub_request 'REMOTE_ADDR' => '1.2.3.4' + assert_equal '1.2.3.4', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' + assert_equal '1.2.3.4', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '1.2.3.4', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + assert_equal '1.2.3.4', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '127.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' + assert_equal 'unknown', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', + 'HTTP_CLIENT_IP' => '2.2.2.2' + e = assert_raise(ActionController::ActionControllerError) { + request.remote_ip + } + assert_match /IP spoofing attack/, e.message + assert_match /HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message + assert_match /HTTP_CLIENT_IP="2.2.2.2"/, e.message + + # turn IP Spoofing detection off. + # This is useful for sites that are aimed at non-IP clients. The typical + # example is WAP. Since the cellular network is not IP based, it's a + # leap of faith to assume that their proxies are ever going to set the + # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + ActionController::Base.ip_spoofing_check = false + request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', + 'HTTP_CLIENT_IP' => '2.2.2.2' + assert_equal '2.2.2.2', request.remote_ip + ActionController::Base.ip_spoofing_check = true + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' + assert_equal '9.9.9.9', request.remote_ip + end + + test "domains" do + request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org' + assert_equal "rubyonrails.org", request.domain + + request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" + assert_equal "rubyonrails.co.uk", request.domain(2) + + request = stub_request 'HTTP_HOST' => "192.168.1.200" + assert_nil request.domain + + request = stub_request 'HTTP_HOST' => "foo.192.168.1.200" + assert_nil request.domain + + request = stub_request 'HTTP_HOST' => "192.168.1.200.com" + assert_equal "200.com", request.domain + end + + test "subdomains" do + request = stub_request 'HTTP_HOST' => "www.rubyonrails.org" + assert_equal %w( www ), request.subdomains + + request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" + assert_equal %w( www ), request.subdomains(2) + + request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk" + assert_equal %w( dev www ), request.subdomains(2) + + request = stub_request 'HTTP_HOST' => "foobar.foobar.com" + assert_equal %w( foobar ), request.subdomains + + request = stub_request 'HTTP_HOST' => "192.168.1.200" + assert_equal [], request.subdomains + + request = stub_request 'HTTP_HOST' => "foo.192.168.1.200" + assert_equal [], request.subdomains + + request = stub_request 'HTTP_HOST' => "192.168.1.200.com" + assert_equal %w( 192 168 1 ), request.subdomains + + request = stub_request 'HTTP_HOST' => nil + assert_equal [], request.subdomains + end + + test "port string" do + request = stub_request 'HTTP_HOST' => 'www.example.org:80' + assert_equal "", request.port_string + + request = stub_request 'HTTP_HOST' => 'www.example.org:8080' + assert_equal ":8080", request.port_string + end + + test "request uri" do + request = stub_request 'REQUEST_URI' => "http://www.rubyonrails.org/path/of/some/uri?mapped=1" + assert_equal "/path/of/some/uri?mapped=1", request.request_uri + assert_equal "/path/of/some/uri", request.path + + request = stub_request 'REQUEST_URI' => "http://www.rubyonrails.org/path/of/some/uri" + assert_equal "/path/of/some/uri", request.request_uri + assert_equal "/path/of/some/uri", request.path + + request = stub_request 'REQUEST_URI' => "/path/of/some/uri" + assert_equal "/path/of/some/uri", request.request_uri + assert_equal "/path/of/some/uri", request.path + + request = stub_request 'REQUEST_URI' => "/" + assert_equal "/", request.request_uri + assert_equal "/", request.path + + request = stub_request 'REQUEST_URI' => "/?m=b" + assert_equal "/?m=b", request.request_uri + assert_equal "/", request.path + + request = stub_request 'REQUEST_URI' => "/", 'SCRIPT_NAME' => '/dispatch.cgi' + assert_equal "/", request.request_uri + assert_equal "/", request.path + + ActionController::Base.relative_url_root = "/hieraki" + request = stub_request 'REQUEST_URI' => "/hieraki/", 'SCRIPT_NAME' => "/hieraki/dispatch.cgi" + assert_equal "/hieraki/", request.request_uri + assert_equal "/", request.path + ActionController::Base.relative_url_root = nil + + ActionController::Base.relative_url_root = "/collaboration/hieraki" + request = stub_request 'REQUEST_URI' => "/collaboration/hieraki/books/edit/2", + 'SCRIPT_NAME' => "/collaboration/hieraki/dispatch.cgi" + assert_equal "/collaboration/hieraki/books/edit/2", request.request_uri + assert_equal "/books/edit/2", request.path + ActionController::Base.relative_url_root = nil + + # The following tests are for when REQUEST_URI is not supplied (as in IIS) + request = stub_request 'PATH_INFO' => "/path/of/some/uri?mapped=1", + 'SCRIPT_NAME' => nil, + 'REQUEST_URI' => nil + assert_equal "/path/of/some/uri?mapped=1", request.request_uri + assert_equal "/path/of/some/uri", request.path + + ActionController::Base.relative_url_root = '/path' + request = stub_request 'PATH_INFO' => "/path/of/some/uri?mapped=1", + 'SCRIPT_NAME' => "/path/dispatch.rb", + 'REQUEST_URI' => nil + assert_equal "/path/of/some/uri?mapped=1", request.request_uri + assert_equal "/of/some/uri", request.path + ActionController::Base.relative_url_root = nil + + request = stub_request 'PATH_INFO' => "/path/of/some/uri", + 'SCRIPT_NAME' => nil, + 'REQUEST_URI' => nil + assert_equal "/path/of/some/uri", request.request_uri + assert_equal "/path/of/some/uri", request.path + + request = stub_request 'PATH_INFO' => '/', 'REQUEST_URI' => nil + assert_equal "/", request.request_uri + assert_equal "/", request.path + + request = stub_request 'PATH_INFO' => '/?m=b', 'REQUEST_URI' => nil + assert_equal "/?m=b", request.request_uri + assert_equal "/", request.path + + request = stub_request 'PATH_INFO' => "/", + 'SCRIPT_NAME' => "/dispatch.cgi", + 'REQUEST_URI' => nil + assert_equal "/", request.request_uri + assert_equal "/", request.path + + ActionController::Base.relative_url_root = '/hieraki' + request = stub_request 'PATH_INFO' => "/hieraki/", + 'SCRIPT_NAME' => "/hieraki/dispatch.cgi", + 'REQUEST_URI' => nil + assert_equal "/hieraki/", request.request_uri + assert_equal "/", request.path + ActionController::Base.relative_url_root = nil + + request = stub_request 'REQUEST_URI' => '/hieraki/dispatch.cgi' + ActionController::Base.relative_url_root = '/hieraki' + assert_equal "/dispatch.cgi", request.path + ActionController::Base.relative_url_root = nil + + request = stub_request 'REQUEST_URI' => '/hieraki/dispatch.cgi' + ActionController::Base.relative_url_root = '/foo' + assert_equal "/hieraki/dispatch.cgi", request.path + ActionController::Base.relative_url_root = nil + + # This test ensures that Rails uses REQUEST_URI over PATH_INFO + ActionController::Base.relative_url_root = nil + request = stub_request 'REQUEST_URI' => "/some/path", + 'PATH_INFO' => "/another/path", + 'SCRIPT_NAME' => "/dispatch.cgi" + assert_equal "/some/path", request.request_uri + assert_equal "/some/path", request.path + end + + + test "host with default port" do + request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80' + assert_equal "rubyonrails.org", request.host_with_port + end + + test "host with non default port" do + request = stub_request 'HTTP_HOST' => 'rubyonrails.org:81' + assert_equal "rubyonrails.org:81", request.host_with_port + end + + test "server software" do + request = stub_request + assert_equal nil, request.server_software + + request = stub_request 'SERVER_SOFTWARE' => 'Apache3.422' + assert_equal 'apache', request.server_software + + request = stub_request 'SERVER_SOFTWARE' => 'lighttpd(1.1.4)' + assert_equal 'lighttpd', request.server_software + end + + test "xml http request" do + request = stub_request + + assert !request.xml_http_request? + assert !request.xhr? + + request = stub_request 'HTTP_X_REQUESTED_WITH' => 'DefinitelyNotAjax1.0' + assert !request.xml_http_request? + assert !request.xhr? + + request = stub_request 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' + assert request.xml_http_request? + assert request.xhr? + end + + test "reports ssl" do + request = stub_request + assert !request.ssl? + + request = stub_request 'HTTPS' => 'on' + assert request.ssl? + end + + test "reports ssl when proxied via lighttpd" do + request = stub_request + assert !request.ssl? + + request = stub_request 'HTTP_X_FORWARDED_PROTO' => 'https' + assert request.ssl? + end + + test "symbolized request methods" do + [:get, :post, :put, :delete].each do |method| + request = stub_request 'REQUEST_METHOD' => method.to_s.upcase + assert_equal method, request.method + end + end + + test "invalid http method raises exception" do + assert_raise(ActionController::UnknownHttpMethod) do + request = stub_request 'REQUEST_METHOD' => 'RANDOM_METHOD' + request.request_method + end + end + + test "allow method hacking on post" do + [:get, :head, :options, :put, :post, :delete].each do |method| + request = stub_request "REQUEST_METHOD" => method.to_s.upcase + assert_equal(method == :head ? :get : method, request.method) + end + end + + test "invalid method hacking on post raises exception" do + assert_raise(ActionController::UnknownHttpMethod) do + request = stub_request "REQUEST_METHOD" => "_RANDOM_METHOD" + request.request_method + end + end + + test "restrict method hacking" do + [:get, :put, :delete].each do |method| + request = stub_request 'REQUEST_METHOD' => method.to_s.upcase, + 'action_controller.request.request_parameters' => { :_method => 'put' } + assert_equal method, request.method + end + end + + test "head masquerading as get" do + request = stub_request 'REQUEST_METHOD' => 'HEAD' + assert_equal :get, request.method + assert request.get? + assert request.head? + end + + test "xml format" do + request = stub_request + request.expects(:parameters).at_least_once.returns({ :format => 'xml' }) + assert_equal Mime::XML, request.format + end + + test "xhtml format" do + request = stub_request + request.expects(:parameters).at_least_once.returns({ :format => 'xhtml' }) + assert_equal Mime::HTML, request.format + end + + test "txt format" do + request = stub_request + request.expects(:parameters).at_least_once.returns({ :format => 'txt' }) + assert_equal Mime::TEXT, request.format + end + + test "XMLHttpRequest" do + begin + ActionController::Base.use_accept_header, old = + false, ActionController::Base.use_accept_header + + request = stub_request 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' + request.expects(:parameters).at_least_once.returns({}) + assert request.xhr? + assert_equal Mime::JS, request.format + ensure + ActionController::Base.use_accept_header = old + end + end + + test "content type" do + request = stub_request 'CONTENT_TYPE' => 'text/html' + assert_equal Mime::HTML, request.content_type + end + + test "can override format with parameter" do + request = stub_request + request.expects(:parameters).at_least_once.returns({ :format => :txt }) + assert !request.format.xml? + + request = stub_request + request.expects(:parameters).at_least_once.returns({ :format => :xml }) + assert request.format.xml? + end + + test "no content type" do + request = stub_request + assert_equal nil, request.content_type + end + + test "content type is XML" do + request = stub_request 'CONTENT_TYPE' => 'application/xml' + assert_equal Mime::XML, request.content_type + end + + test "content type with charset" do + request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8' + assert_equal Mime::XML, request.content_type + end + + test "user agent" do + request = stub_request 'HTTP_USER_AGENT' => 'TestAgent' + assert_equal 'TestAgent', request.user_agent + end + + test "parameters" do + request = stub_request + request.stubs(:request_parameters).returns({ "foo" => 1 }) + request.stubs(:query_parameters).returns({ "bar" => 2 }) + + assert_equal({"foo" => 1, "bar" => 2}, request.parameters) + assert_equal({"foo" => 1}, request.request_parameters) + assert_equal({"bar" => 2}, request.query_parameters) + end + +protected + + def stub_request(env={}) + ActionDispatch::Request.new(env) + end + +end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb new file mode 100644 index 0000000000..b9bf8cf411 --- /dev/null +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -0,0 +1,250 @@ +require 'abstract_unit' +require 'stringio' + +class CookieStoreTest < ActionController::IntegrationTest + SessionKey = '_myapp_session' + SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' + + DispatcherApp = ActionController::Dispatcher.new + CookieStoreApp = ActionDispatch::Session::CookieStore.new(DispatcherApp, + :key => SessionKey, :secret => SessionSecret) + + Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1') + + SignedBar = "BAh7BjoIZm9vIghiYXI%3D--fef868465920f415f2c0652d6910d3af288a0367" + + class TestController < ActionController::Base + def no_session_access + head :ok + end + + def persistent_session_id + render :text => session[:session_id] + end + + def set_session_value + session[:foo] = "bar" + render :text => Rack::Utils.escape(Verifier.generate(session.to_hash)) + end + + def get_session_value + render :text => "foo: #{session[:foo].inspect}" + end + + def get_session_id + render :text => "foo: #{session[:foo].inspect}; id: #{request.session_options[:id]}" + end + + def call_reset_session + reset_session + head :ok + end + + def raise_data_overflow + session[:foo] = 'bye!' * 1024 + head :ok + end + + def rescue_action(e) raise end + end + + def setup + @integration_session = open_session(CookieStoreApp) + end + + def test_raises_argument_error_if_missing_session_key + assert_raise(ArgumentError, nil.inspect) { + ActionDispatch::Session::CookieStore.new(nil, + :key => nil, :secret => SessionSecret) + } + + assert_raise(ArgumentError, ''.inspect) { + ActionDispatch::Session::CookieStore.new(nil, + :key => '', :secret => SessionSecret) + } + end + + def test_raises_argument_error_if_missing_secret + assert_raise(ArgumentError, nil.inspect) { + ActionDispatch::Session::CookieStore.new(nil, + :key => SessionKey, :secret => nil) + } + + assert_raise(ArgumentError, ''.inspect) { + ActionDispatch::Session::CookieStore.new(nil, + :key => SessionKey, :secret => '') + } + end + + def test_raises_argument_error_if_secret_is_probably_insecure + assert_raise(ArgumentError, "password".inspect) { + ActionDispatch::Session::CookieStore.new(nil, + :key => SessionKey, :secret => "password") + } + + assert_raise(ArgumentError, "secret".inspect) { + ActionDispatch::Session::CookieStore.new(nil, + :key => SessionKey, :secret => "secret") + } + + assert_raise(ArgumentError, "12345678901234567890123456789".inspect) { + ActionDispatch::Session::CookieStore.new(nil, + :key => SessionKey, :secret => "12345678901234567890123456789") + } + end + + def test_setting_session_value + with_test_route_set do + get '/set_session_value' + assert_response :success + assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", + headers['Set-Cookie'] + end + end + + def test_getting_session_value + with_test_route_set do + cookies[SessionKey] = SignedBar + get '/get_session_value' + assert_response :success + assert_equal 'foo: "bar"', response.body + end + end + + def test_getting_session_id + with_test_route_set do + cookies[SessionKey] = SignedBar + get '/persistent_session_id' + assert_response :success + assert_equal response.body.size, 32 + session_id = response.body + + get '/get_session_id' + assert_response :success + assert_equal "foo: \"bar\"; id: #{session_id}", response.body + end + end + + def test_disregards_tampered_sessions + with_test_route_set do + cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--123456780" + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end + + def test_close_raises_when_data_overflows + with_test_route_set do + assert_raise(ActionDispatch::Session::CookieStore::CookieOverflow) { + get '/raise_data_overflow' + } + end + end + + def test_doesnt_write_session_cookie_if_session_is_not_accessed + with_test_route_set do + get '/no_session_access' + assert_response :success + assert_equal "", headers['Set-Cookie'] + end + end + + def test_doesnt_write_session_cookie_if_session_is_unchanged + with_test_route_set do + cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--" + + "fef868465920f415f2c0652d6910d3af288a0367" + get '/no_session_access' + assert_response :success + assert_equal "", headers['Set-Cookie'] + end + end + + def test_setting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + session_payload = response.body + assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", + headers['Set-Cookie'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + assert_not_equal session_payload, cookies[SessionKey] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end + + def test_persistent_session_id + with_test_route_set do + cookies[SessionKey] = SignedBar + get '/persistent_session_id' + assert_response :success + assert_equal response.body.size, 32 + session_id = response.body + get '/persistent_session_id' + assert_equal session_id, response.body + reset! + get '/persistent_session_id' + assert_not_equal session_id, response.body + end + end + + def test_session_store_with_expire_after + app = ActionDispatch::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret, :expire_after => 5.hours) + @integration_session = open_session(app) + + with_test_route_set do + # First request accesses the session + time = Time.local(2008, 4, 24) + Time.stubs(:now).returns(time) + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + + cookies[SessionKey] = SignedBar + + get '/set_session_value' + assert_response :success + + cookie_body = response.body + assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", + headers['Set-Cookie'] + + # Second request does not access the session + time = Time.local(2008, 4, 25) + Time.stubs(:now).returns(time) + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + + get '/no_session_access' + assert_response :success + + # Mystery bug that came up in 2.3 as well. What is this trying to test?! + # assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", + # headers['Set-Cookie'] + end + end + + private + def with_test_route_set + with_routing do |set| + set.draw do |map| + map.with_options :controller => "cookie_store_test/test" do |c| + c.connect "/:action" + end + end + yield + end + end + + def unmarshal_session(cookie_string) + session = Rack::Utils.parse_query(cookie_string, ';,').inject({}) {|h,(k,v)| + h[k] = Array === v ? v.first : v + h + }[SessionKey] + verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1') + verifier.verify(session) + end +end diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb new file mode 100644 index 0000000000..7561c93e4a --- /dev/null +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -0,0 +1,127 @@ +require 'abstract_unit' + +# You need to start a memcached server inorder to run these tests +class MemCacheStoreTest < ActionController::IntegrationTest + class TestController < ActionController::Base + def no_session_access + head :ok + end + + def set_session_value + session[:foo] = "bar" + head :ok + end + + def get_session_value + render :text => "foo: #{session[:foo].inspect}" + end + + def get_session_id + session[:foo] + render :text => "#{request.session_options[:id]}" + end + + def call_reset_session + session[:bar] + reset_session + session[:bar] = "baz" + head :ok + end + + def rescue_action(e) raise end + end + + begin + DispatcherApp = ActionController::Dispatcher.new + MemCacheStoreApp = ActionDispatch::Session::MemCacheStore.new( + DispatcherApp, :key => '_session_id') + + + def setup + @integration_session = open_session(MemCacheStoreApp) + end + + def test_setting_and_getting_session_value + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: "bar"', response.body + end + end + + def test_getting_nil_session_value + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end + + def test_setting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_id = cookies['_session_id'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + + get '/get_session_id' + assert_response :success + assert_not_equal session_id, response.body + end + end + + def test_getting_session_id + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_id = cookies['_session_id'] + + get '/get_session_id' + assert_response :success + assert_equal session_id, response.body + end + end + + def test_prevents_session_fixation + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + session_id = cookies['_session_id'] + + reset! + + get '/set_session_value', :_session_id => session_id + assert_response :success + assert_equal nil, cookies['_session_id'] + end + end + rescue LoadError, RuntimeError + $stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again." + end + + private + def with_test_route_set + with_routing do |set| + set.draw do |map| + map.with_options :controller => "mem_cache_store_test/test" do |c| + c.connect "/:action" + end + end + yield + end + end +end diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb new file mode 100644 index 0000000000..de6539e1cc --- /dev/null +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -0,0 +1,58 @@ +require 'abstract_unit' +require 'stringio' + +class ActionController::TestSessionTest < ActiveSupport::TestCase + + def test_calling_delete_without_parameters_raises_deprecation_warning_and_calls_to_clear_test_session + assert_deprecated(/use clear instead/){ ActionController::TestSession.new.delete } + end + + def test_calling_update_without_parameters_raises_deprecation_warning_and_calls_to_clear_test_session + assert_deprecated(/use replace instead/){ ActionController::TestSession.new.update } + end + + def test_calling_close_raises_deprecation_warning + assert_deprecated(/sessions should no longer be closed/){ ActionController::TestSession.new.close } + end + + def test_defaults + session = ActionController::TestSession.new + assert_equal({}, session.data) + assert_equal('', session.session_id) + end + + def test_ctor_allows_setting + session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) + assert_equal('one', session[:one]) + assert_equal('two', session[:two]) + end + + def test_setting_session_item_sets_item + session = ActionController::TestSession.new + session[:key] = 'value' + assert_equal('value', session[:key]) + end + + def test_calling_delete_removes_item + session = ActionController::TestSession.new + session[:key] = 'value' + assert_equal('value', session[:key]) + session.delete(:key) + assert_nil(session[:key]) + end + + def test_calling_update_with_params_passes_to_attributes + session = ActionController::TestSession.new() + session.update('key' => 'value') + assert_equal('value', session[:key]) + end + + def test_clear_emptys_session + params = {:one => 'one', :two => 'two'} + session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) + session.clear + assert_nil(session[:one]) + assert_nil(session[:two]) + end + +end
\ No newline at end of file |