class Pakyow::Connection::QueryParser
Parses one or more query strings, building up a params hash. Supports nested query strings, and enforces limits for key space size and total nested parameter depth.
Aspects of this were inspired by Rack's query parser, including key space and depth limits. We decided it was worth writing our own for several reasons:
1) Avoid Rack as a dependency for the majority use-case. 2) Improve the interface so you don't have to know ahead of time if you're dealing with a nested query string or not, and to allow for params to be built up from many strings. 3) Improve performance (up to 90% faster for a simple query string, 10% for nested).
@api private
Constants
- DEFAULT_DELIMETER
- DEFAULT_DEPTH_LIMIT
- DEFAULT_KEY_SPACE_LIMIT
Attributes
depth_limit[R]
key_space_limit[R]
params[R]
Public Class Methods
new(key_space_limit: DEFAULT_KEY_SPACE_LIMIT, depth_limit: DEFAULT_DEPTH_LIMIT, params: {})
click to toggle source
# File lib/pakyow/connection/query_parser.rb, line 34 def initialize(key_space_limit: DEFAULT_KEY_SPACE_LIMIT, depth_limit: DEFAULT_DEPTH_LIMIT, params: {}) @params = params @key_space_limit = key_space_limit @key_space_size = 0 @depth_limit = depth_limit end
Public Instance Methods
add(key, value, params = @params)
click to toggle source
# File lib/pakyow/connection/query_parser.rb, line 52 def add(key, value, params = @params) unless params.key?(key) @key_space_size += key.size end if @key_space_size > @key_space_limit raise KeySpaceLimitExceeded, "key space limit (#{@key_space_limit}) exceeded by `#{key}'" else params[key] = value end end
add_value_for_key(value, key, params = @params, depth = 0)
click to toggle source
# File lib/pakyow/connection/query_parser.rb, line 64 def add_value_for_key(value, key, params = @params, depth = 0) if depth > @depth_limit raise DepthLimitExceeded, "depth limit (#{@depth_limit}) exceeded by `#{key}'" end if key && key.include?("[") && key.include?("]") opened = false read, nested = String.new, nil key.length.times do |i| char = key[i] if char == "[" opened = true elsif char == "]" && opened opened = false case params when Array nested_value = if nested if current_nested_value = params.last unless current_nested_value.is_a?(@params.class) raise InvalidParameter, "expected `#{read}' to be #{@params.class} (got #{current_nested_value.class})" end if current_nested_value.key?(nested) (params << @params.class.new).last else current_nested_value end else (params << @params.class.new).last end else if current_nested_value = params[read] unless current_nested_value.is_a?(Array) raise InvalidParameter, "expected `#{read}' to be Array (got #{current_nested_value.class})" end current_nested_value else (params << []).last end end when @params.class nested_value = if nested if current_nested_value = params[read] unless current_nested_value.is_a?(@params.class) raise InvalidParameter, "expected `#{read}' to be #{@params.class} (got #{current_nested_value.class})" end current_nested_value else @params.class.new end else if current_nested_value = params[read] unless current_nested_value.is_a?(Array) raise InvalidParameter, "expected `#{read}' to be Array (got #{current_nested_value.class})" end current_nested_value else [] end end add(read, nested_value, params) end j = i + 1 if (next_char = key[j]) && next_char != "[" raise InvalidParameter, "expected `#{nested}' to be #{params.class} (got String)" else add_value_for_key(value, (nested || String.new) << key[j..-1], nested_value, depth + 1); break end elsif opened (nested ||= String.new) << char else read << char end end else case params when Array params << value when @params.class if depth == 0 && (current_value = params[key]) && !(current_value.is_a?(Array) || current_value.is_a?(@params.class)) if current_value.is_a?(Array) current_value << value else current_value = [current_value, value] add(key, current_value, params) end else if key && !key.empty? add(key, value, params) end end end end end
parse(input, delimiter = DEFAULT_DELIMETER)
click to toggle source
# File lib/pakyow/connection/query_parser.rb, line 41 def parse(input, delimiter = DEFAULT_DELIMETER) input.to_s.split(delimiter).each do |part| key, value = part.split("=", 2) key = unescape(key).strip if key value = unescape(value).strip if value add_value_for_key(value, key) end @params end
Private Instance Methods
unescape(string)
click to toggle source
# File lib/pakyow/connection/query_parser.rb, line 169 def unescape(string) CGI.unescape(string) end