diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 976bd98..56f946c 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -5,6 +5,7 @@ require 'action_controller/routing' require 'action_controller/resources' require 'action_controller/url_rewriter' require 'action_controller/status_codes' +require 'action_controller/url_encoded_pair_parser' require 'action_view' require 'drb' require 'set' @@ -363,6 +364,19 @@ module ActionController #:nodoc: class_inheritable_accessor :allow_forgery_protection self.allow_forgery_protection = true + # If your application relies on earlier versions of the + # UrlEncodedPairParser code, you can use this configuration + # setting to specify which parser implementation you want to use. + # This defaults to + # ActionController::UrlEncodedPairParser, which is the new + # implementation. The old parser is + # ActionController::AbstractRequest::LegacyUrlEncodedPairParser. + cattr_writer :pair_parser + + def self.pair_parser + @@pair_parser || ActionController::UrlEncodedPairParser + end + # If you are deploying to a subdirectory, you will need to set # config.action_controller.relative_url_root # This defaults to ENV['RAILS_RELATIVE_URL_ROOT'] diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 5e492e3..f22738a 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -538,11 +538,11 @@ EOM [ CGI.unescape(key), value ] end.compact - UrlEncodedPairParser.new(pairs).result + new_parser(pairs).result end def parse_request_parameters(params) - parser = UrlEncodedPairParser.new + parser = new_parser params = params.dup until params.empty? @@ -589,6 +589,10 @@ EOM private + def new_parser(params=[]) + ActionController::Base.pair_parser.new(params) + end + def get_typed_value(value) case value when String @@ -729,7 +733,7 @@ EOM end end - class UrlEncodedPairParser < StringScanner #:nodoc: + class LegacyUrlEncodedPairParser < StringScanner #:nodoc: attr_reader :top, :parent, :result def initialize(pairs = []) diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb new file mode 100644 index 0000000..b75856e --- /dev/null +++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb @@ -0,0 +1,61 @@ +require 'strscan' + +module ActionController + # This implementation of UrlEncodedPairParser supersedes the previous + # implementation in ActionController::AbstractRequest, providing + # better support for complex and nested forms in Rails. + class UrlEncodedPairParser + attr_reader :result + + def initialize(params=[]) + @result = {} + params.each { |key, value| parse(key, value) } + end + + ROOT_REGEX = /[^\[]+/ + ARRAY_REGEX = /\[([0-9]*)\]/ + HASH_REGEX = /\[([^\]]+)\]/ + + # Parse a single key/value pair into the internal parameter hash. + def parse(key, value) + lex = StringScanner.new(key) + current = lex.scan(ROOT_REGEX) or return + ref = result + + until lex.eos? + if lex.scan(ARRAY_REGEX) + ref[current] ||= [] + check_type(Array, ref, current) + if lex[1].empty? + key = ref[current].length + else + key = lex[1].to_i + end + elsif lex.scan(HASH_REGEX) + ref[current] ||= {}.with_indifferent_access + check_type(Hash, ref, current) + key = lex[1] + else + break + end + ref = ref[current] + current = key + end + + ref[current] = value + result + end + + # If we receive parameters indicating a key being treated as both an + # Array and a Hash, we complain about it. + def check_type(expected, actual, key) + if !actual[key].kind_of?(expected) + raise TypeError, + "Ambiguous type encountered while parsing parameters: expected " + + "#{expected} but got #{actual[key].class} for key #{key} in " + + "#{actual.inspect}" + end + end + end +end + diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index e79a0ea..f397176 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -434,7 +434,7 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase def test_deep_query_string_with_array_of_hash assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10')) - assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=10')) + assert_equal({'x' => {'y' => [{'z' => '10'}, {'w' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=10')) end def test_deep_query_string_with_array_of_hashes_with_one_pair @@ -445,7 +445,7 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase def test_deep_query_string_with_array_of_hashes_with_multiple_pairs assert_equal( - {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, + {'x' => {'y' => [{'z' => '10'}, {'w' => 'a'}, {'z' => '20'}, {'w' => 'b'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b') ) end @@ -513,10 +513,11 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase def test_request_hash_parsing query = { - "note[viewers][viewer][][type]" => ["User", "Group"], - "note[viewers][viewer][][id]" => ["1", "2"] + "note[viewers][viewer][0][type]" => ["User"], + "note[viewers][viewer][0][id]" => ["1"], + "note[viewers][viewer][1][type]" => ["Group"], + "note[viewers][viewer][1][id]" => ["2"], } - expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } assert_equal(expected, ActionController::AbstractRequest.parse_request_parameters(query)) @@ -663,25 +664,25 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase def test_parse_params_with_single_brackets_in_middle input = { "a/b[c]d" => %w(e) } - expected = { "a/b" => {} } + expected = { "a/b" => { "c" => "e" } } assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) end def test_parse_params_with_separated_brackets input = { "a/b@[c]d[e]" => %w(f) } - expected = { "a/b@" => { }} + expected = { "a/b@" => { "c" => "f" }} assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) end def test_parse_params_with_separated_brackets_and_array input = { "a/b@[c]d[e][]" => %w(f) } - expected = { "a/b@" => { }} + expected = { "a/b@" => { "c" => "f" }} assert_equal expected , ActionController::AbstractRequest.parse_request_parameters(input) end def test_parse_params_with_unmatched_brackets_and_array input = { "a/b@[c][d[e][]" => %w(f) } - expected = { "a/b@" => { "c" => { }}} + expected = { "a/b@" => { "c" => { "d[e" => ["f"] }}} assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) end