class Rack::QueryParser
def self.make_default(param_depth_limit, **options)
def self.make_default(param_depth_limit, **options) new(Params, param_depth_limit, **options) end
def _normalize_params(params, name, v, depth)
def _normalize_params(params, name, v, depth) ParamsTooDeepError if depth >= param_depth_limit me l name, treat same as empty string (required by tests) after = '' depth == 0 art of parsing, don't treat [] or [ at start of string specially tart = name.index('[', 1) Start of parameter nesting, use part before brackets as key = name[0, start] ter = name[start, name.length] Plain parameter with no nesting = name ter = '' name.start_with?('[]') ray nesting '[]' r = name[2, name.length] name.start_with?('[') && (start = name.index(']', 1)) sh nesting, use the part inside brackets as the key name[1, start-1] r = name[start+1, name.length] obably malformed input, nested but not starting with [ eat full name as key for backwards compatibility. name r = '' if k.empty? er == '' == '[]' && depth != 0 turn [v] rams[k] = v after == "[" ms[name] = v after == "[]" ms[k] ||= [] e ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) ms[k] << v after.start_with?('[]') cognize x[][y] (hash inside array) parameters ss 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 ild_key = after[2, after.length] ms[k] ||= [] e ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) arams_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key) ormalize_params(params[k].last, child_key, v, depth + 1) rams[k] << _normalize_params(make_params, child_key, v, depth + 1) ms[k] ||= make_params e ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k]) ms[k] = _normalize_params(params[k], after, v, depth + 1)
def check_query_string(qs, sep)
def check_query_string(qs, sep) if qs if qs.bytesize > @bytesize_limit raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})" end if (param_count = qs.count(sep.is_a?(String) ? sep : '&')) >= @params_limit raise QueryLimitError, "total number of query parameters (#{param_count+1}) exceeds limit (#{@params_limit})" end qs else '' end end
def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT) @params_class = params_class @param_depth_limit = param_depth_limit @bytesize_limit = bytesize_limit @params_limit = params_limit end
def make_params
def make_params @params_class.new end
def new_depth_limit(param_depth_limit)
def new_depth_limit(param_depth_limit) self.class.new @params_class, param_depth_limit end
def normalize_params(params, name, v, _depth=nil)
and should no longer be used, it is kept for backwards compatibility with
conflict, a ParameterTypeError is raised. The depth argument is deprecated
the structural types represented by two different parameter names are in
normalize_params recursively expands parameters into structural types. If
def normalize_params(params, name, v, _depth=nil) _normalize_params(params, name, v, 0) end
def params_hash_has_key?(hash, key)
def params_hash_has_key?(hash, key) return false if /\[\]/.match?(key) key.split(/[\[\]]+/).inject(hash) do |h, part| next h if part == '' return false unless params_hash_type?(h) && h.key?(part) h[part] end true end
def params_hash_type?(obj)
def params_hash_type?(obj) obj.kind_of?(@params_class) end
def parse_nested_query(qs, separator = nil)
ParameterTypeError is raised. Users are encouraged to return a 400 in this
query strings with parameters of conflicting types, in this case a
types are Arrays, Hashes and basic value types. It is possible to supply
parse_nested_query expands a query string into structural types. Supported
def parse_nested_query(qs, separator = nil) params = make_params unless qs.nil? || qs.empty? check_query_string(qs, separator).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, 0) end end return params.to_h rescue ArgumentError => e raise InvalidParameterError, e.message, e.backtrace end
def parse_query(qs, separator = nil, &unescaper)
to parse cookies by changing the characters used in the second parameter
Parses a query string by breaking it up at the '&'. You can also use this
Stolen from Mongrel, with some small modifications:
def parse_query(qs, separator = nil, &unescaper) unescaper ||= method(:unescape) params = make_params check_query_string(qs, separator).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 params[k] << v else params[k] = [cur, v] end else params[k] = v end end return params.to_h end
def unescape(string, encoding = Encoding::UTF_8)
def unescape(string, encoding = Encoding::UTF_8) URI.decode_www_form_component(string, encoding) end