diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index db82254..f92896a 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -35,9 +35,21 @@ module Rack (qs || '').split(/[#{d}] */n).each do |p| k, v = unescape(p).split('=', 2) + normalize_params(params, k, v) + end + + return params + end + module_function :parse_query + + def normalize_params(params, name, v = nil) + name =~ %r([\[\]]*([^\[\]]+)\]*) + k = $1 || '' + after = $' || '' + if after == "" if cur = params[k] - if cur.class == Array + if cur.is_a?(Array) params[k] << v else params[k] = [cur, v] @@ -45,11 +57,27 @@ module Rack else params[k] = v end + elsif after == "[]" + params[k] ||= [] + raise TypeError unless params[k].is_a?(Array) + params[k] << v + elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$) + child_key = $1 + params[k] ||= [] + raise TypeError unless params[k].is_a?(Array) + if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key) + normalize_params(params[k].last, child_key, v) + else + params[k] << normalize_params({}, child_key, v) + end + else + params[k] ||= {} + params[k] = normalize_params(params[k], after, v) end return params end - module_function :parse_query + module_function :normalize_params def build_query(params) params.map { |k, v| @@ -310,14 +338,7 @@ module Rack data = body end - if name - if name =~ /\[\]\z/ - params[name] ||= [] - params[name] << data - else - params[name] = data - end - end + Utils.normalize_params(params, name, data) break if buf.empty? || content_length == -1 } diff --git a/test/multipart/nested b/test/multipart/nested new file mode 100644 index 0000000..5197882 --- /dev/null +++ b/test/multipart/nested @@ -0,0 +1,10 @@ +--AaB03x +Content-Disposition: form-data; name="foo[submit-name]" + +Larry +--AaB03x +Content-Disposition: form-data; name="foo[files]"; filename="file1.txt" +Content-Type: text/plain + +contents +--AaB03x-- diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb index 73fa406..acd7083 100644 --- a/test/spec_rack_utils.rb +++ b/test/spec_rack_utils.rb @@ -21,13 +21,68 @@ context "Rack::Utils" do end specify "should parse query strings correctly" do - Rack::Utils.parse_query("foo=bar").should.equal "foo" => "bar" + Rack::Utils.parse_query("foo"). + should.equal "foo" => nil + Rack::Utils.parse_query("foo="). + should.equal "foo" => "" + Rack::Utils.parse_query("foo=bar"). + should.equal "foo" => "bar" + Rack::Utils.parse_query("foo=bar&foo=quux"). should.equal "foo" => ["bar", "quux"] + Rack::Utils.parse_query("foo&foo="). + should.equal "foo" => "" Rack::Utils.parse_query("foo=1&bar=2"). should.equal "foo" => "1", "bar" => "2" + Rack::Utils.parse_query("foo&bar="). + should.equal "foo" => nil, "bar" => "" + Rack::Utils.parse_query("foo=bar&baz="). + should.equal "foo" => "bar", "baz" => "" Rack::Utils.parse_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_query("foo[]"). + should.equal "foo" => [nil] + Rack::Utils.parse_query("foo[]="). + should.equal "foo" => [""] + Rack::Utils.parse_query("foo[]=bar"). + should.equal "foo" => ["bar"] + + Rack::Utils.parse_query("foo[]=1&foo[]=2"). + should.equal "foo" => ["1", "2"] + Rack::Utils.parse_query("foo=bar&baz[]=1&baz[]=2&baz[]=3"). + should.equal "foo" => "bar", "baz" => ["1", "2", "3"] + Rack::Utils.parse_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3"). + should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"] + + Rack::Utils.parse_query("x[y][z]=1"). + should.equal "x" => {"y" => {"z" => "1"}} + Rack::Utils.parse_query("x[y][z][]=1"). + should.equal "x" => {"y" => {"z" => ["1"]}} + Rack::Utils.parse_query("x[y][z]=1&x[y][z]=2"). + should.equal "x" => {"y" => {"z" => ["1", "2"]}} + Rack::Utils.parse_query("x[y][z][]=1&x[y][z][]=2"). + should.equal "x" => {"y" => {"z" => ["1", "2"]}} + + Rack::Utils.parse_query("x[y][][z]=1"). + should.equal "x" => {"y" => [{"z" => "1"}]} + Rack::Utils.parse_query("x[y][][z][]=1"). + should.equal "x" => {"y" => [{"z" => ["1"]}]} + Rack::Utils.parse_query("x[y][][z]=1&x[y][][w]=2"). + should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]} + + Rack::Utils.parse_query("x[y][][v][w]=1"). + should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]} + Rack::Utils.parse_query("x[y][][z]=1&x[y][][v][w]=2"). + should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]} + + Rack::Utils.parse_query("x[y][][z]=1&x[y][][z]=2"). + should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]} + Rack::Utils.parse_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_query("foo[bar]=1&foo[]=1") }. + should.raise TypeError end specify "should build query strings correctly" do @@ -171,6 +226,19 @@ context "Rack::Utils::Multipart" do params["files"][:tempfile].read.should.equal "contents" end + specify "should parse multipart upload with nested parameters" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:nested)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["foo"]["submit-name"].should.equal "Larry" + params["foo"]["files"][:type].should.equal "text/plain" + params["foo"]["files"][:filename].should.equal "file1.txt" + params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + params["foo"]["files"][:name].should.equal "foo[files]" + params["foo"]["files"][:tempfile].read.should.equal "contents" + end + specify "should parse multipart upload with binary file" do env = Rack::MockRequest.env_for("/", multipart_fixture(:binary)) params = Rack::Utils::Multipart.parse_multipart(env)