Is it expected that these parameters always raise a 500 in any Rails application?

rack, ruby-on-rails, ruby-on-rails-3, ruby-on-rails-4

Solution

This technically affects Rack, not Rails and I'm guessing it's a bug... Rack seems to go out of its way to properly parse a nested query...

should "parse nested query strings correctly" do
    Rack::Utils.parse_nested_query("foo").
      should.equal "foo" => nil
    Rack::Utils.parse_nested_query("foo=").
      should.equal "foo" => ""
    Rack::Utils.parse_nested_query("foo=bar").
      should.equal "foo" => "bar"
    Rack::Utils.parse_nested_query("foo=\"bar\"").
      should.equal "foo" => "\"bar\""

    Rack::Utils.parse_nested_query("foo=bar&foo=quux").
      should.equal "foo" => "quux"
    Rack::Utils.parse_nested_query("foo&foo=").
      should.equal "foo" => ""
    Rack::Utils.parse_nested_query("foo=1&bar=2").
      should.equal "foo" => "1", "bar" => "2"
    Rack::Utils.parse_nested_query("&foo=1&&bar=2").
      should.equal "foo" => "1", "bar" => "2"
    Rack::Utils.parse_nested_query("foo&bar=").
      should.equal "foo" => nil, "bar" => ""
    Rack::Utils.parse_nested_query("foo=bar&baz=").
      should.equal "foo" => "bar", "baz" => ""
    Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
      should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"

    Rack::Utils.parse_nested_query("a=b&pid%3D1234=1023").
      should.equal "pid=1234" => "1023", "a" => "b"

    Rack::Utils.parse_nested_query("foo[]").
      should.equal "foo" => [nil]
    Rack::Utils.parse_nested_query("foo[]=").
      should.equal "foo" => [""]
    Rack::Utils.parse_nested_query("foo[]=bar").
      should.equal "foo" => ["bar"]

    Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
      should.equal "foo" => ["1", "2"]
    Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
      should.equal "foo" => "bar", "baz" => ["1", "2", "3"]
    Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
      should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"]

    Rack::Utils.parse_nested_query("x[y][z]=1").
      should.equal "x" => {"y" => {"z" => "1"}}
    Rack::Utils.parse_nested_query("x[y][z][]=1").
      should.equal "x" => {"y" => {"z" => ["1"]}}
    Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
      should.equal "x" => {"y" => {"z" => "2"}}
    Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
      should.equal "x" => {"y" => {"z" => ["1", "2"]}}

    Rack::Utils.parse_nested_query("x[y][][z]=1").
      should.equal "x" => {"y" => [{"z" => "1"}]}
    Rack::Utils.parse_nested_query("x[y][][z][]=1").
      should.equal "x" => {"y" => [{"z" => ["1"]}]}
    Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
      should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}

    Rack::Utils.parse_nested_query("x[y][][v][w]=1").
      should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
    Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
      should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}

    Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
      should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
    Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
      should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}

    lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
      should.raise(TypeError).
      message.should.equal "expected Hash (got String) for param `y'"

    lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
      should.raise(TypeError).
      message.should.match(/expected Array \(got [^)]*\) for param `x'/)

    lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
      should.raise(TypeError).
      message.should.equal "expected Array (got String) for param `y'"
  end

On the other hand, I suspect that the query would more properly be written as:

`http://example.com/?b[]=1&b[a]=2` or `http://example.com/?b[a]=1&b[a]=2`

You might get a kick out of http://codefol.io/posts/How-Does-Rack-Parse-Query-Params-With-parse-nested-query .

Problem

Hitting a rails application with the following parameters ``` http://example.com/?b=1&b[a]=2 ``` Always makes it raise a 500 error which seems to be non-catchable. For e.g. - Github - Heroku - Basecamp It raises the following error: ``` Invalid query parameters: expected Hash (got String) for param `b' ``` The request never hits the Rails application code. Here are the last lines of the full backtrace: ``` ActionController::BadRequest (Invalid query parameters: expected Hash (got String) for param `b'): rack (1.5.2) lib/rack/utils.rb:127:in `normalize_params' rack (1.5.2) lib/rack/utils.rb:96:in `block in parse_nested_query' rack (1.5.2) lib/rack/utils.rb:93:in `each' rack (1.5.2) lib/rack/utils.rb:93:in `parse_nested_query' rack (1.5.2) lib/rack/request.rb:373:in `parse_query' actionpack (4.1.4) lib/action_dispatch/http/request.rb:313:in `parse_query' rack (1.5.2) lib/rack/request.rb:188:in `GET' actionpack (4.1.4) lib/action_dispatch/http/request.rb:274:in `GET' actionpack (4.1.4) lib/action_dispatch/http/parameters.rb:16:in `parameters' actionpack (4.1.4) lib/action_dispatch/http/filter_parameters.rb:37:in `filtered_parameters' actionpack (4.1.4) lib/action_controller/metal/instrumentation.rb:22:in `process_action' actionpack (4.1.4) lib/action_controller/metal/params_wrapper.rb:250:in `process_action' activerecord (4.1.4) lib/active_record/railties/controller_runtime.rb:18:in `process_action' actionpack (4.1.4) lib/abstract_controller/base.rb:136:in `process' actionview (4.1.4) lib/action_view/rendering.rb:30:in `process' actionpack (4.1.4) lib/action_controller/metal.rb:196:in `dispatch' actionpack (4.1.4) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch' actionpack (4.1.4) lib/action_controller/metal.rb:232:in `block in action' actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:82:in `call' actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:82:in `dispatch' actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:50:in `call' actionpack (4.1.4) lib/action_dispatch/journey/router.rb:71:in `block in call' actionpack (4.1.4) lib/action_dispatch/journey/router.rb:59:in `each' actionpack (4.1.4) lib/action_dispatch/journey/router.rb:59:in `call' actionpack (4.1.4) lib/action_dispatch/routing/route_set.rb:678:in `call' rack (1.5.2) lib/rack/etag.rb:23:in `call' rack (1.5.2) lib/rack/conditionalget.rb:25:in `call' rack (1.5.2) lib/rack/head.rb:11:in `call' actionpack (4.1.4) lib/action_dispatch/middleware/params_parser.rb:27:in `call' ``` I discovered this a few years back in Rails 3.2 and I wonder why it's still crashes on Rails 4.1.4. Anyone has a good explanation about what is happening here?

Original source