diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index 4b7febfd20..c6f625beae 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -53,7 +53,7 @@ module ActionDispatch if formatted != false # Add a constraint for wildcard route to make it non-greedy and # match the optional format part of the route by default. - wildcard_options[node.name.to_sym] ||= /.+?/ + wildcard_options[node.name.to_sym] ||= /.+?/m end end diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 8c31307d48..1ce6ec243b 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -91,7 +91,8 @@ module ActionDispatch # as requirements. def requirements @defaults.merge(path.requirements).delete_if { |_, v| - /.+?/ == v + /.+?/ == v || + /.+?/m == v } end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 3da6ab1e13..58d343eda5 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -146,7 +146,7 @@ module ActionDispatch end requirements, conditions = split_constraints ast.path_params, constraints - verify_regexp_requirements requirements.map(&:last).grep(Regexp) + verify_regexp_requirements requirements, ast.wildcard_options formats = normalize_format(formatted) @@ -246,14 +246,18 @@ module ActionDispatch end end - def verify_regexp_requirements(requirements) - requirements.each do |requirement| - if ANCHOR_CHARACTERS_REGEX.match?(requirement.source) + def verify_regexp_requirements(requirements, wildcard_options) + requirements.each do |requirement, regex| + next unless regex.is_a? Regexp + + if ANCHOR_CHARACTERS_REGEX.match?(regex.source) raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end - if requirement.multiline? - raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" + if regex.multiline? + next if wildcard_options.key?(requirement) + + raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.inspect}" end end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 3912aa1a4d..ac10b8aa06 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -624,6 +624,34 @@ class LegacyRouteSetTests < ActiveSupport::TestCase url_for(rs, controller: "content", action: "show_file", path: %w(pages boo)) end + def test_escapes_newline_character_for_dynamic_path + rs.draw do + get "/dynamic/:dynamic_segment" => "subpath_books#show", as: :dynamic + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + results = rs.recognize_path("/dynamic/a%0Anewline") + assert(results, "Recognition should have succeeded") + assert_equal("a\nnewline", results[:dynamic_segment]) + end + + def test_escapes_newline_character_for_wildcard_path + rs.draw do + get "/wildcard/*wildcard_segment" => "subpath_books#show", as: :wildcard + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + results = rs.recognize_path("/wildcard/a%0Anewline") + assert(results, "Recognition should have succeeded") + assert_equal("a\nnewline", results[:wildcard_segment]) + end + def test_dynamic_recall_paths_allowed rs.draw do get "*path" => "content#show_file" diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index 3f2841bae8..ed48b0e7ad 100644 --- a/actionpack/test/dispatch/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -92,7 +92,7 @@ module ActionDispatch scope = Mapper::Scope.new({}) ast = Journey::Parser.parse "/store/:name(*rest)" m = Mapper::Mapping.build(scope, FakeSet.new, ast, "foo", "bar", nil, [:get], nil, {}, true, options) - assert_equal(/.+?/, m.requirements[:rest]) + assert_equal(/.+?/m, m.requirements[:rest]) end def test_via_scope @@ -137,7 +137,7 @@ module ActionDispatch mapper = Mapper.new fakeset mapper.get "/*path", to: "pages#show" assert_equal "/*path(.:format)", fakeset.asts.first.to_s - assert_equal(/.+?/, fakeset.requirements.first[:path]) + assert_equal(/.+?/m, fakeset.requirements.first[:path]) end def test_map_wildcard_with_other_element @@ -145,7 +145,7 @@ module ActionDispatch mapper = Mapper.new fakeset mapper.get "/*path/foo/:bar", to: "pages#show" assert_equal "/*path/foo/:bar(.:format)", fakeset.asts.first.to_s - assert_equal(/.+?/, fakeset.requirements.first[:path]) + assert_equal(/.+?/m, fakeset.requirements.first[:path]) end def test_map_wildcard_with_multiple_wildcard @@ -153,8 +153,8 @@ module ActionDispatch mapper = Mapper.new fakeset mapper.get "/*foo/*bar", to: "pages#show" assert_equal "/*foo/*bar(.:format)", fakeset.asts.first.to_s - assert_equal(/.+?/, fakeset.requirements.first[:foo]) - assert_equal(/.+?/, fakeset.requirements.first[:bar]) + assert_equal(/.+?/m, fakeset.requirements.first[:foo]) + assert_equal(/.+?/m, fakeset.requirements.first[:bar]) end def test_map_wildcard_with_format_false diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index 0247b90c5a..d7b611b664 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -149,6 +149,22 @@ module ActionDispatch assert_equal "/users/1.json", url_helpers.user_path(1, :json) end + test "escape new line for dynamic params" do + draw do + get "/dynamic/:dynamic_segment", to: SimpleApp.new("foo#index"), as: :dynamic + end + + assert_equal "/dynamic/a%0Anewline", url_helpers.dynamic_path(dynamic_segment: "a\nnewline") + end + + test "escape new line for wildcard params" do + draw do + get "/wildcard/*wildcard_segment", to: SimpleApp.new("foo#index"), as: :wildcard + end + + assert_equal "/wildcard/a%0Anewline", url_helpers.wildcard_path(wildcard_segment: "a\nnewline") + end + private def draw(&block) @set.draw(&block) diff --git a/actionpack/test/journey/nodes/ast_test.rb b/actionpack/test/journey/nodes/ast_test.rb index 2bfcfcc87e..b809c80b6f 100644 --- a/actionpack/test/journey/nodes/ast_test.rb +++ b/actionpack/test/journey/nodes/ast_test.rb @@ -67,7 +67,7 @@ module ActionDispatch ast = Ast.new(tree, true) wildcard_options = ast.wildcard_options - assert_equal %r{.+?}, wildcard_options[:glob] + assert_equal %r{.+?}m, wildcard_options[:glob] end def test_wildcard_options_when_false @@ -83,7 +83,7 @@ module ActionDispatch ast = Ast.new(tree, nil) wildcard_options = ast.wildcard_options - assert_equal %r{.+?}, wildcard_options[:glob] + assert_equal %r{.+?}m, wildcard_options[:glob] end end end