Class: Pakyow::Connection::QueryParser Private

Inherits:
Object
  • Object
show all
Defined in:
lib/pakyow/connection/query_parser.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

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).

Defined Under Namespace

Classes: DepthLimitExceeded, InvalidParameter, KeySpaceLimitExceeded

Constant Summary collapse

DEFAULT_DELIMETER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

/[&;,]/
DEFAULT_KEY_SPACE_LIMIT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

102_400
DEFAULT_DEPTH_LIMIT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

100

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key_space_limit: DEFAULT_KEY_SPACE_LIMIT, depth_limit: DEFAULT_DEPTH_LIMIT, params: {}) ⇒ QueryParser

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of QueryParser.



34
35
36
37
38
39
# 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

Instance Attribute Details

#depth_limitObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



32
33
34
# File 'lib/pakyow/connection/query_parser.rb', line 32

def depth_limit
  @depth_limit
end

#key_space_limitObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



32
33
34
# File 'lib/pakyow/connection/query_parser.rb', line 32

def key_space_limit
  @key_space_limit
end

#paramsObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



32
33
34
# File 'lib/pakyow/connection/query_parser.rb', line 32

def params
  @params
end

Instance Method Details

#add(key, value, params = @params) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



52
53
54
55
56
57
58
59
60
61
62
# 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) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# 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) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



41
42
43
44
45
46
47
48
49
50
# 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