1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/test/journey/router_test.rb
Andrew White 6520ea5f7e Deprecate :controller and :action path parameters
Allowing :controller and :action values to be specified via the path
in config/routes.rb has been an underlying cause of a number of issues
in Rails that have resulted in security releases. In light of this it's
better that controllers and actions are explicitly whitelisted rather
than trying to blacklist or sanitize 'bad' values.
2016-03-01 08:48:53 +00:00

533 lines
16 KiB
Ruby

require 'abstract_unit'
module ActionDispatch
module Journey
class TestRouter < ActiveSupport::TestCase
attr_reader :mapper, :routes, :route_set, :router
def setup
@app = Routing::RouteSet::Dispatcher.new({})
@route_set = ActionDispatch::Routing::RouteSet.new
@routes = @route_set.router.routes
@router = @route_set.router
@formatter = @route_set.formatter
@mapper = ActionDispatch::Routing::Mapper.new @route_set
end
def test_dashes
get '/foo-bar-baz', to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/foo-bar-baz'
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_unicode
get '/ほげ', to: 'foo#bar'
#match the escaped version of /ほげ
env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_regexp_first_precedence
get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar"
get "/whois/:id(.:format)", to: "foo#baz"
env = rails_env 'PATH_INFO' => '/whois/example.com'
list = []
router.recognize(env) do |r, params|
list << r
end
assert_equal 2, list.length
r = list.first
assert_equal '/whois/:domain(.:format)', r.path.spec.to_s
end
def test_required_parts_verified_are_anchored
get "/foo/:id", :id => /\d/, anchor: false, to: "foo#bar"
assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
end
end
def test_required_parts_are_verified_when_building
get "/foo/:id", :id => /\d+/, anchor: false, to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(nil, { :id => 'aa' }, { })
end
end
def test_only_required_parts_are_verified
get "/foo(/:id)", :id => /\d/, :to => "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { })
assert_equal '/foo', path
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => 'aa' }, { })
assert_equal '/foo/aa', path
end
def test_knows_what_parts_are_missing_from_named_route
route_name = "gorby_thunderhorse"
get "/foo/:id", :as => route_name, :id => /\d+/, :to => "foo#bar"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, { }, { })
end
assert_match(/missing required keys: \[:id\]/, error.message)
end
def test_does_not_include_missing_keys_message
route_name = "gorby_thunderhorse"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, { }, { })
end
assert_no_match(/missing required keys: \[\]/, error.message)
end
def test_X_Cascade
get "/messages(.:format)", to: "foo#bar"
resp = router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))
assert_equal ['Not Found'], resp.last
assert_equal 'pass', resp[1]['X-Cascade']
assert_equal 404, resp.first
end
def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
app = lambda { |env| [200, {}, ['success!']] }
get '/weblog', :to => app
env = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog')
resp = route_set.call env
assert_equal ['success!'], resp.last
assert_equal '', env['SCRIPT_NAME']
end
def test_defaults_merge_correctly
get '/foo(/:id)', to: "foo#bar", id: nil
env = rails_env 'PATH_INFO' => '/foo/10'
router.recognize(env) do |r, params|
assert_equal({:id => '10', :controller => "foo", :action => "bar"}, params)
end
env = rails_env 'PATH_INFO' => '/foo'
router.recognize(env) do |r, params|
assert_equal({:id => nil, :controller => "foo", :action => "bar"}, params)
end
end
def test_recognize_with_unbound_regexp
get "/foo", anchor: false, to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo/bar'
router.recognize(env) { |*_| }
assert_equal '/foo', env.env['SCRIPT_NAME']
assert_equal '/bar', env.env['PATH_INFO']
end
def test_bound_regexp_keeps_path_info
get "/foo", to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo'
before = env.env['SCRIPT_NAME']
router.recognize(env) { |*_| }
assert_equal before, env.env['SCRIPT_NAME']
assert_equal '/foo', env.env['PATH_INFO']
end
def test_path_not_found
[
"/messages(.:format)",
"/messages/new(.:format)",
"/messages/:id/edit(.:format)",
"/messages/:id(.:format)"
].each do |path|
get path, to: "foo#bar"
end
env = rails_env 'PATH_INFO' => '/messages/unknown/path'
yielded = false
router.recognize(env) do |*whatever|
yielded = true
end
assert_not yielded
end
def test_required_part_in_recall
get "/messages/:a/:b", to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :a => 'a' }, { :b => 'b' })
assert_equal "/messages/a/b", path
end
def test_splat_in_recall
get "/*path", to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { :path => 'b' })
assert_equal "/b", path
end
def test_recall_should_be_used_when_scoring
get "/messages/:action(/:id(.:format))", to: 'foo#bar'
get "/messages/:id(.:format)", to: 'bar#baz'
path, _ = @formatter.generate(nil, { :controller => "foo", :id => 10 }, { :action => 'index' })
assert_equal "/messages/index/10", path
end
def test_nil_path_parts_are_ignored
get "/:controller(/:action(.:format))", to: "tasks#lol"
params = { :controller => "tasks", :format => nil }
extras = { :action => 'lol' }
path, _ = @formatter.generate(nil, params, extras)
assert_equal '/tasks', path
end
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
get "/", Hash[params]
path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
end
def test_generate_calls_param_proc
get '/:controller(/:action)', to: "foo#bar"
parameterized = []
params = [ [:controller, "tasks"],
[:action, "show"] ]
@formatter.generate(
nil,
Hash[params],
{},
lambda { |k,v| parameterized << [k,v]; v })
assert_equal params.map(&:to_s).sort, parameterized.map(&:to_s).sort
end
def test_generate_id
get '/:controller(/:action)', to: 'foo#bar'
path, params = @formatter.generate(
nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
assert_equal '/tasks/show', path
assert_equal({:id => 1}, params)
end
def test_generate_escapes
get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(nil,
{ :controller => "tasks",
:action => "a/b c+d",
}, {})
assert_equal '/tasks/a%2Fb%20c+d', path
end
def test_generate_escapes_with_namespaced_controller
get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(
nil, { :controller => "admin/tasks",
:action => "a/b c+d",
}, {})
assert_equal '/admin/tasks/a%2Fb%20c+d', path
end
def test_generate_extra_params
get '/:controller(/:action)', to: "foo#bar"
path, params = @formatter.generate(
nil, { :id => 1,
:controller => "tasks",
:action => "show",
:relative_url_root => nil
}, {})
assert_equal '/tasks/show', path
assert_equal({:id => 1, :relative_url_root => nil}, params)
end
def test_generate_missing_keys_no_matches_different_format_keys
get '/:controller/:action/:name', to: "foo#bar"
primarty_parameters = {
:id => 1,
:controller => "tasks",
:action => "show",
:relative_url_root => nil
}
redirection_parameters = {
'action'=>'show',
}
missing_key = 'name'
missing_parameters ={
missing_key => "task_1"
}
request_parameters = primarty_parameters.merge(redirection_parameters).merge(missing_parameters)
message = "No route matches #{Hash[request_parameters.sort_by{|k,v|k.to_s}].inspect} missing required keys: #{[missing_key.to_sym].inspect}"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(
nil, request_parameters, request_parameters)
end
assert_equal message, error.message
end
def test_generate_uses_recall_if_needed
get '/:controller(/:action(/:id))', to: "foo#bar"
path, params = @formatter.generate(
nil,
{:controller =>"tasks", :id => 10},
{:action =>"index"})
assert_equal '/tasks/index/10', path
assert_equal({}, params)
end
def test_generate_with_name
get '/:controller(/:action)', to: 'foo#bar', as: 'tasks'
path, params = @formatter.generate(
"tasks",
{:controller=>"tasks"},
{:controller=>"tasks", :action=>"index"})
assert_equal '/tasks', path
assert_equal({}, params)
end
{
'/content' => { :controller => 'content' },
'/content/list' => { :controller => 'content', :action => 'list' },
'/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
}.each do |request_path, expected|
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
get "/:controller(/:action(/:id))", to: 'foo#bar'
route = @routes.first
env = rails_env 'PATH_INFO' => request_path
called = false
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal({ :action => "bar" }.merge(expected), params)
called = true
end
assert called
end
end
{
:segment => ['/a%2Fb%20c+d/splat', { :segment => 'a/b c+d', :splat => 'splat' }],
:splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
}.each do |name, (request_path, expected)|
define_method("test_recognize_#{name}") do
get '/:segment/*splat', to: 'foo#bar'
env = rails_env 'PATH_INFO' => request_path
called = false
route = @routes.first
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected.merge(:controller=>"foo", :action=>"bar"), params)
called = true
end
assert called
end
end
def test_namespaced_controller
get "/:controller(/:action(/:id))", { :controller => /.+?/ }
route = @routes.first
env = rails_env 'PATH_INFO' => '/admin/users/show/10'
called = false
expected = {
:controller => 'admin/users',
:action => 'show',
:id => '10'
}
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
end
assert called
end
def test_recognize_literal
get "/books(/:action(.:format))", controller: "books"
route = @routes.first
env = rails_env 'PATH_INFO' => '/books/list.rss'
expected = { :controller => 'books', :action => 'list', :format => 'rss' }
called = false
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
end
assert called
end
def test_recognize_head_route
match "/books(/:action(.:format))", via: 'head', to: 'foo#bar'
env = rails_env(
'PATH_INFO' => '/books/list.rss',
'REQUEST_METHOD' => 'HEAD'
)
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_recognize_head_request_as_get_route
get "/books(/:action(.:format))", to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "HEAD"
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_recognize_cares_about_get_verbs
match "/books(/:action(.:format))", to: "foo#bar", via: :get
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
called = false
router.recognize(env) do |r, params|
called = true
end
assert_not called
end
def test_recognize_cares_about_post_verbs
match "/books(/:action(.:format))", to: "foo#bar", via: :post
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_multi_verb_recognition
match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get]
%w( POST GET ).each do |verb|
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => verb
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => 'PUT'
called = false
router.recognize(env) do |r, params|
called = true
end
assert_not called
end
private
def get *args
ActiveSupport::Deprecation.silence do
mapper.get(*args)
end
end
def match *args
ActiveSupport::Deprecation.silence do
mapper.match(*args)
end
end
def rails_env env, klass = ActionDispatch::Request
klass.new(rack_env(env))
end
def rack_env env
{
"rack.version" => [1, 1],
"rack.input" => StringIO.new,
"rack.errors" => StringIO.new,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,
"REQUEST_METHOD" => "GET",
"SERVER_NAME" => "example.org",
"SERVER_PORT" => "80",
"QUERY_STRING" => "",
"PATH_INFO" => "/content",
"rack.url_scheme" => "http",
"HTTPS" => "off",
"SCRIPT_NAME" => "",
"CONTENT_LENGTH" => "0"
}.merge env
end
end
end
end