diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb index 5afeba7..d6bfe60 100644 --- a/lib/rack/utils.rb +++ b/lib/rack/utils.rb @@ -5,6 +5,69 @@ module Rack # Rack::Utils contains a grab-bag of useful methods for writing web # applications adopted from all kinds of Ruby libraries. + class MultiHash < Hash + def self.from_hash(from_hash) + hash = MultiHash.new + from_hash.each do |key, value| + hash[key] = value + end + hash + end + + alias at [] + + def [](key) + ret = super(key) + ret && ret.last + end + + def []=(key, value) + if key?(key) + self.all(key).push value + else + super(key, value.nil? ? [] : [value]) + end + end + + def update(other) + super(MultiHash.from_hash(other)) + end + alias merge update + + def update!(other) + super(MultiHash.from_hash(other)) + end + alias merge! update + + def to_hash + new_hash = {} + self.each do |key, value| + new_hash[k] = value + end + new_hash + end + + def inspect + to_hash.inspect + end + + def all(key) + at(key) + end + + alias each_array each + + def each + super do |key, value| + yield key, self[key] + end + end + + def delete(key) + super(key).last if key?(key) + end + end + module Utils # Performs URI escaping so that you can construct proper # query strings faster. Use this rather than the cgi.rb @@ -31,7 +94,7 @@ module Rack # parameter (which defaults to '&;'). def parse_query(qs, d = '&;') - params = {} + params = MultiHash.new (qs || '').split(/[#{d}] */n).each do |p| k, v = unescape(p).split('=', 2) @@ -50,14 +113,15 @@ module Rack return if k.empty? if after == "" - if cur = params[k] + cur = params[k] + if !cur || (params.is_a?(MultiHash) && !cur.is_a?(Array) && name !~ /\]$/) + params[k] = v + else if cur.is_a?(Array) params[k] << v - else + elsif name =~ /\[?#{k}\]?/ params[k] = [cur, v] - end - else - params[k] = v + end end elsif after == "[]" params[k] ||= [] diff --git a/test/spec_rack_utils.rb b/test/spec_rack_utils.rb index 1ced7c7..6660ea8 100644 --- a/test/spec_rack_utils.rb +++ b/test/spec_rack_utils.rb @@ -4,6 +4,18 @@ require 'rack/utils' require 'rack/lint' require 'rack/mock' +class Test::Spec::Should + def match_hash(hash) + matches = true + hash, obj = hash.dup, @object.dup + while !hash.empty? + key, value = hash.shift + assert_equal value, obj.delete(key), "#{value.inspect} expected for key #{key.inspect}" + end + assert obj.empty? + end +end + context "Rack::Utils" do specify "should escape correctly" do Rack::Utils.escape("fobar").should.equal "fo%3Co%3Ebar" @@ -22,71 +34,83 @@ context "Rack::Utils" do specify "should parse query strings correctly" do Rack::Utils.parse_query("foo"). - should.equal "foo" => nil + should.match_hash "foo" => nil Rack::Utils.parse_query("foo="). - should.equal "foo" => "" + should.match_hash "foo" => "" Rack::Utils.parse_query("foo=bar"). - should.equal "foo" => "bar" - - Rack::Utils.parse_query("foo=bar&foo=quux"). - should.equal "foo" => ["bar", "quux"] + should.match_hash "foo" => "bar" + Rack::Utils.parse_query("foo&foo="). - should.equal "foo" => "" + should.match_hash "foo" => "" Rack::Utils.parse_query("foo=1&bar=2"). - should.equal "foo" => "1", "bar" => "2" + should.match_hash "foo" => "1", "bar" => "2" Rack::Utils.parse_query("&foo=1&&bar=2"). - should.equal "foo" => "1", "bar" => "2" + should.match_hash "foo" => "1", "bar" => "2" Rack::Utils.parse_query("foo&bar="). - should.equal "foo" => nil, "bar" => "" + should.match_hash "foo" => nil, "bar" => "" Rack::Utils.parse_query("foo=bar&baz="). - should.equal "foo" => "bar", "baz" => "" + should.match_hash "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)?" - + should.match_hash "my weird field" => "q1!2\"'w$5&7/z8)?" + Rack::Utils.parse_query("foo[]"). - should.equal "foo" => [nil] + should.match_hash "foo" => [nil] Rack::Utils.parse_query("foo[]="). - should.equal "foo" => [""] + should.match_hash "foo" => [""] Rack::Utils.parse_query("foo[]=bar"). - should.equal "foo" => ["bar"] - + should.match_hash "foo" => ["bar"] + Rack::Utils.parse_query("foo[]=1&foo[]=2"). - should.equal "foo" => ["1", "2"] + should.match_hash "foo" => ["1", "2"] Rack::Utils.parse_query("foo=bar&baz[]=1&baz[]=2&baz[]=3"). - should.equal "foo" => "bar", "baz" => ["1", "2", "3"] + should.match_hash "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"] - + should.match_hash "foo" => ["bar"], "baz" => ["1", "2", "3"] + Rack::Utils.parse_query("x[y][z]=1"). - should.equal "x" => {"y" => {"z" => "1"}} + should.match_hash "x" => {"y" => {"z" => "1"}} Rack::Utils.parse_query("x[y][z][]=1"). - should.equal "x" => {"y" => {"z" => ["1"]}} + should.match_hash "x" => {"y" => {"z" => ["1"]}} Rack::Utils.parse_query("x[y][z]=1&x[y][z]=2"). - should.equal "x" => {"y" => {"z" => ["1", "2"]}} + should.match_hash "x" => {"y" => {"z" => ["1", "2"]}} Rack::Utils.parse_query("x[y][z][]=1&x[y][z][]=2"). - should.equal "x" => {"y" => {"z" => ["1", "2"]}} - + should.match_hash "x" => {"y" => {"z" => ["1", "2"]}} + Rack::Utils.parse_query("x[y][][z]=1"). - should.equal "x" => {"y" => [{"z" => "1"}]} + should.match_hash "x" => {"y" => [{"z" => "1"}]} Rack::Utils.parse_query("x[y][][z][]=1"). - should.equal "x" => {"y" => [{"z" => ["1"]}]} + should.match_hash "x" => {"y" => [{"z" => ["1"]}]} Rack::Utils.parse_query("x[y][][z]=1&x[y][][w]=2"). - should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]} - + should.match_hash "x" => {"y" => [{"z" => "1", "w" => "2"}]} + Rack::Utils.parse_query("x[y][][v][w]=1"). - should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]} + should.match_hash "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"}}]} - + should.match_hash "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"}]} + should.match_hash "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"}]} - + should.match_hash "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]} + lambda { Rack::Utils.parse_query("foo[bar]=1&foo[]=1") }. should.raise TypeError end + context "when the same value is supplied twice and it does not end in []" do + before do + @hash = Rack::Utils.parse_query("foo=bar&foo=quux") + end + + specify "should return the last parsed value when indexed" do + @hash.should.match_hash "foo" => "quux" + end + + specify "should return all of the values with .all" do + @hash.all("foo").should.equal ["bar", "quux"] + end + end + specify "should build query strings correctly" do Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar" Rack::Utils.build_query("foo" => ["bar", "quux"]).