Skip to content

Commit

Permalink
Merge remote-tracking branch 'thedarkone/nested_params_key_space2'
Browse files Browse the repository at this point in the history
* thedarkone/nested_params_key_space2:
  Rack::Utils#normalize_params should be ignorant of the provided params class.
  Correctly count the key space size for nested param queries.
  • Loading branch information
raggi committed Jan 23, 2012
2 parents 8d20282 + 1a6a57c commit ad9a4db
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 41 deletions.
14 changes: 2 additions & 12 deletions lib/rack/multipart/parser.rb
Expand Up @@ -14,9 +14,6 @@ def parse

fast_forward_to_first_boundary

max_key_space = Utils.key_space_limit
bytes = 0

loop do
head, filename, content_type, name, body =
get_current_head_and_filename_and_content_type_and_name_and_body
Expand All @@ -31,13 +28,6 @@ def parse

filename, data = get_data(filename, body, content_type, name, head)

if name
bytes += name.size
if bytes > max_key_space
raise RangeError, "exceeded available parameter key space"
end
end

Utils.normalize_params(@params, name, data) unless data.nil?

# break if we're at the end of a buffer, but not if it is the end of a field
Expand All @@ -46,7 +36,7 @@ def parse

@io.rewind

@params
@params.to_params_hash
end

private
Expand All @@ -56,7 +46,7 @@ def setup_parse
@boundary = "--#{$1}"

@buf = ""
@params = {}
@params = Utils::KeySpaceConstrainedParams.new

@content_length = @env['CONTENT_LENGTH'].to_i
@io = @env['rack.input']
Expand Down
76 changes: 48 additions & 28 deletions lib/rack/utils.rb
Expand Up @@ -61,21 +61,11 @@ class << self
# cookies by changing the characters used in the second
# parameter (which defaults to '&;').
def parse_query(qs, d = nil)
params = {}

max_key_space = Utils.key_space_limit
bytes = 0
params = KeySpaceConstrainedParams.new

(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |x| unescape(x) }

if k
bytes += k.size
if bytes > max_key_space
raise RangeError, "exceeded available parameter key space"
end
end

if cur = params[k]
if cur.class == Array
params[k] << v
Expand All @@ -87,30 +77,20 @@ def parse_query(qs, d = nil)
end
end

return params
return params.to_params_hash
end
module_function :parse_query

def parse_nested_query(qs, d = nil)
params = {}

max_key_space = Utils.key_space_limit
bytes = 0
params = KeySpaceConstrainedParams.new

(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |s| unescape(s) }

if k
bytes += k.size
if bytes > max_key_space
raise RangeError, "exceeded available parameter key space"
end
end

normalize_params(params, k, v)
end

return params
return params.to_params_hash
end
module_function :parse_nested_query

Expand All @@ -131,21 +111,26 @@ def normalize_params(params, name, v = nil)
child_key = $1
params[k] ||= []
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
normalize_params(params[k].last, child_key, v)
else
params[k] << normalize_params({}, child_key, v)
params[k] << normalize_params(params.class.new, child_key, v)
end
else
params[k] ||= {}
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
params[k] ||= params.class.new
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
params[k] = normalize_params(params[k], after, v)
end

return params
end
module_function :normalize_params

def params_hash_type?(obj)
obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
end
module_function :params_hash_type?

def build_query(params)
params.map { |k, v|
if v.class == Array
Expand Down Expand Up @@ -445,6 +430,41 @@ def replace(other)
end
end

class KeySpaceConstrainedParams
def initialize(limit = Utils.key_space_limit)
@limit = limit
@size = 0
@params = {}
end

def [](key)
@params[key]
end

def []=(key, value)
@size += key.size unless @params.key?(key)
raise RangeError, 'exceeded available parameter key space' if @size > @limit
@params[key] = value
end

def key?(key)
@params.key?(key)
end

def to_params_hash
hash = @params
hash.keys.each do |key|
value = hash[key]
if value.kind_of?(self.class)
hash[key] = value.to_params_hash
elsif value.kind_of?(Array)
value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
end
end
hash
end
end

# Every standard HTTP code mapped to the appropriate message.
# Generated with:
# curl -s http://www.iana.org/assignments/http-status-codes | \
Expand Down
13 changes: 13 additions & 0 deletions test/spec_request.rb
Expand Up @@ -137,6 +137,19 @@
end
end

should "limit the key size per nested params hash" do
nested_query = Rack::MockRequest.env_for("/?foo[bar][baz][qux]=1")
plain_query = Rack::MockRequest.env_for("/?foo_bar__baz__qux_=1")

old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 3
begin
lambda { Rack::Request.new(nested_query).GET }.should.not.raise(RangeError)
lambda { Rack::Request.new(plain_query).GET }.should.raise(RangeError)
ensure
Rack::Utils.key_space_limit = old
end
end

should "not unify GET and POST when calling params" do
mr = Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'POST',
Expand Down
2 changes: 1 addition & 1 deletion test/spec_utils.rb
Expand Up @@ -188,7 +188,7 @@ def kcodeu

lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
should.raise(TypeError).
message.should.equal "expected Array (got Hash) for param `x'"
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).
Expand Down

0 comments on commit ad9a4db

Please sign in to comment.