lib/rack/query_parser.rb in rack-2.0.9.4 vs lib/rack/query_parser.rb in rack-2.1.0

- old
+ new

@@ -1,7 +1,13 @@ +# frozen_string_literal: true + +require_relative 'core_ext/regexp' + module Rack class QueryParser + using ::Rack::RegexpExtensions + 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. @@ -34,11 +40,11 @@ params = make_params (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| next if p.empty? - k, v = p.split('='.freeze, 2).map!(&unescaper) + k, v = p.split('=', 2).map!(&unescaper) if cur = params[k] if cur.class == Array params[k] << v else @@ -47,11 +53,11 @@ else params[k] = v end end - return params.to_params_hash + return params.to_h end # 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 @@ -59,17 +65,17 @@ # case. def parse_nested_query(qs, d = nil) return {} if qs.nil? || qs.empty? params = make_params - (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| - k, v = p.split('='.freeze, 2).map! { |s| unescape(s) } + qs.split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p| + k, v = p.split('=', 2).map! { |s| unescape(s) } normalize_params(params, k, v, param_depth_limit) end - return params.to_params_hash + return params.to_h rescue ArgumentError => e raise InvalidParameterError, e.message end # normalize_params recursively expands parameters into structural types. If @@ -77,26 +83,26 @@ # conflict, a ParameterTypeError is raised. def normalize_params(params, name, v, depth) raise RangeError if depth <= 0 name =~ %r(\A[\[\]]*([^\[\]]+)\]*) - k = $1 || ''.freeze - after = $' || ''.freeze + k = $1 || '' + after = $' || '' if k.empty? - if !v.nil? && name == "[]".freeze + if !v.nil? && name == "[]" return Array(v) else return end end - if after == ''.freeze + if after == '' params[k] = v - elsif after == "[".freeze + elsif after == "[" params[name] = v - elsif after == "[]".freeze + 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 @@ -133,11 +139,11 @@ def params_hash_type?(obj) obj.kind_of?(@params_class) end def params_hash_has_key?(hash, key) - return false if 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] @@ -169,24 +175,44 @@ 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) - if value.object_id == self.object_id - hash[key] = hash - else - hash[key] = value.to_params_hash - end - elsif value.kind_of?(Array) - value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x} + # Recursively unwraps nested `Params` objects and constructs an object + # of the same shape, but using the objects' internal representations + # (Ruby hashes) in place of the objects. The result is a hash consisting + # purely of Ruby primitives. + # + # Mutation warning! + # + # 1. This method mutates the internal representation of the `Params` + # objects in order to save object allocations. + # + # 2. The value you get back is a reference to the internal hash + # representation, not a copy. + # + # 3. Because the `Params` object's internal representation is mutable + # through the `#[]=` method, it is not thread safe. The result of + # getting the hash representation while another thread is adding a + # key to it is non-deterministic. + # + def to_h + @params.each do |key, value| + case value + when self + # Handle circular references gracefully. + @params[key] = @params + when Params + @params[key] = value.to_h + when Array + value.map! { |v| v.kind_of?(Params) ? v.to_h : v } + else + # Ignore anything that is not a `Params` object or + # a collection that can contain one. end end - hash + @params end + alias_method :to_params_hash, :to_h end end end