lib/rack/query_parser.rb in rack-2.2.10 vs lib/rack/query_parser.rb in rack-3.0.0.beta1
- old
+ new
@@ -1,12 +1,10 @@
# frozen_string_literal: true
module Rack
class QueryParser
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
-
- DEFAULT_SEP = /[&;] */n
+ DEFAULT_SEP = /[&] */n
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
# ParameterTypeError is the error that is raised when incoming structural
# parameters (parsed by parse_nested_query) contain conflicting types.
class ParameterTypeError < TypeError; end
@@ -18,33 +16,39 @@
# ParamsTooDeepError is the error that is raised when params are recursively
# nested over the specified limit.
class ParamsTooDeepError < RangeError; end
- def self.make_default(key_space_limit, param_depth_limit)
- new Params, key_space_limit, param_depth_limit
+ def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit)
+ unless not_deprecated
+ warn("`first argument `key_space limit` is deprecated and no longer has an effect. Please call with only one argument, which will be required in a future version of Rack", uplevel: 1)
+ end
+
+ new Params, param_depth_limit
end
- attr_reader :key_space_limit, :param_depth_limit
+ attr_reader :param_depth_limit
- def initialize(params_class, key_space_limit, param_depth_limit)
+ def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit)
+ unless not_deprecated
+ warn("`second argument `key_space limit` is deprecated and no longer has an effect. Please call with only two arguments, which will be required in a future version of Rack", uplevel: 1)
+ end
+
@params_class = params_class
- @key_space_limit = key_space_limit
@param_depth_limit = param_depth_limit
end
# Stolen from Mongrel, with some small modifications:
- # Parses a query string by breaking it up at the '&'
- # and ';' characters. You can also use this to parse
- # cookies by changing the characters used in the second
- # parameter (which defaults to '&;').
- def parse_query(qs, d = nil, &unescaper)
+ # Parses a query string by breaking it up at the '&'. You can also use this
+ # to parse cookies by changing the characters used in the second parameter
+ # (which defaults to '&').
+ def parse_query(qs, separator = nil, &unescaper)
unescaper ||= method(:unescape)
params = make_params
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
next if p.empty?
k, v = p.split('=', 2).map!(&unescaper)
if cur = params[k]
if cur.class == Array
@@ -63,80 +67,111 @@
# parse_nested_query expands a query string into structural types. Supported
# types are Arrays, Hashes and basic value types. It is possible to supply
# query strings with parameters of conflicting types, in this case a
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
# case.
- def parse_nested_query(qs, d = nil)
+ def parse_nested_query(qs, separator = nil)
params = make_params
unless qs.nil? || qs.empty?
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map! { |s| unescape(s) }
- normalize_params(params, k, v, param_depth_limit)
+ _normalize_params(params, k, v, 0)
end
end
return params.to_h
rescue ArgumentError => e
raise InvalidParameterError, e.message, e.backtrace
end
# normalize_params recursively expands parameters into structural types. If
# the structural types represented by two different parameter names are in
- # conflict, a ParameterTypeError is raised.
- def normalize_params(params, name, v, depth)
- raise ParamsTooDeepError if depth <= 0
+ # conflict, a ParameterTypeError is raised. The depth argument is deprecated
+ # and should no longer be used, it is kept for backwards compatibility with
+ # earlier versions of rack.
+ def normalize_params(params, name, v, _depth=nil)
+ _normalize_params(params, name, v, 0)
+ end
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
- k = $1 || ''
- after = $' || ''
+ private def _normalize_params(params, name, v, depth)
+ raise ParamsTooDeepError if depth >= param_depth_limit
- if k.empty?
- if !v.nil? && name == "[]"
- return Array(v)
+ if !name
+ # nil name, treat same as empty string (required by tests)
+ k = after = ''
+ elsif depth == 0
+ # Start of parsing, don't treat [] or [ at start of string specially
+ if start = name.index('[', 1)
+ # Start of parameter nesting, use part before brackets as key
+ k = name[0, start]
+ after = name[start, name.length]
else
- return
+ # Plain parameter with no nesting
+ k = name
+ after = ''
end
+ elsif name.start_with?('[]')
+ # Array nesting
+ k = '[]'
+ after = name[2, name.length]
+ elsif name.start_with?('[') && (start = name.index(']', 1))
+ # Hash nesting, use the part inside brackets as the key
+ k = name[1, start-1]
+ after = name[start+1, name.length]
+ else
+ # Probably malformed input, nested but not starting with [
+ # treat full name as key for backwards compatibility.
+ k = name
+ after = ''
end
+ return if k.empty?
+
+ v ||= String.new
+
if after == ''
- params[k] = v
+ if k == '[]' && depth != 0
+ return [v]
+ else
+ params[k] = v
+ end
elsif after == "["
params[name] = v
elsif after == "[]"
params[k] ||= []
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
params[k] << v
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
- child_key = $1
+ elsif after.start_with?('[]')
+ # Recognize x[][y] (hash inside array) parameters
+ unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
+ # Handle other nested array parameters
+ child_key = after[2, after.length]
+ end
params[k] ||= []
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
- normalize_params(params[k].last, child_key, v, depth - 1)
+ _normalize_params(params[k].last, child_key, v, depth + 1)
else
- params[k] << normalize_params(make_params, child_key, v, depth - 1)
+ params[k] << _normalize_params(make_params, child_key, v, depth + 1)
end
else
params[k] ||= make_params
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
- params[k] = normalize_params(params[k], after, v, depth - 1)
+ params[k] = _normalize_params(params[k], after, v, depth + 1)
end
params
end
def make_params
- @params_class.new @key_space_limit
+ @params_class.new
end
- def new_space_limit(key_space_limit)
- self.class.new @params_class, key_space_limit, param_depth_limit
- end
-
def new_depth_limit(param_depth_limit)
- self.class.new @params_class, key_space_limit, param_depth_limit
+ self.class.new @params_class, param_depth_limit
end
private
def params_hash_type?(obj)
@@ -158,22 +193,19 @@
def unescape(s)
Utils.unescape(s)
end
class Params
- def initialize(limit)
- @limit = limit
+ def initialize
@size = 0
@params = {}
end
def [](key)
@params[key]
end
def []=(key, value)
- @size += key.size if key && !@params.key?(key)
- raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
@params[key] = value
end
def key?(key)
@params.key?(key)