Allow multiline to be passed in routes when using wildcards.

Fixed a bug in action_dispatch where routes with newlines weren't detected when using wildcard segments
This commit is contained in:
ignacio-chiazzo 2021-10-12 20:40:14 -04:00
parent e7fb2b114e
commit 4df32bc139
7 changed files with 64 additions and 15 deletions

@ -53,7 +53,7 @@ def visit_tree(formatted)
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

@ -91,7 +91,8 @@ def eager_load!
# as requirements.
def requirements
@defaults.merge(path.requirements).delete_if { |_, v|
/.+?/ == v
/.+?/ == v ||
/.+?/m == v
}
end

@ -146,7 +146,7 @@ def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:,
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 @@ def normalize_format(formatted)
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

@ -624,6 +624,34 @@ def test_dynamic_path_allowed
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"

@ -92,7 +92,7 @@ def test_mapping_requirements
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 @@ def test_map_wildcard
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 @@ def test_map_wildcard_with_other_element
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 @@ def test_map_wildcard_with_multiple_wildcard
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

@ -149,6 +149,22 @@ def call(env)
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)

@ -67,7 +67,7 @@ def test_wildcard_options_when_formatted
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 @@ def test_wildcard_options_when_nil
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