Module: Datadog::AppSec::Utils::HTTP::URLEncoded

Defined in:
lib/datadog/appsec/utils/http/url_encoded.rb

Overview

Module for parsing URL encoded payloads

Constant Summary collapse

DEFAULT_BYTESIZE_LIMIT =

Matches Rack's default query bytesize limit, so parsing our own payloads keeps the same guard against CPU/memory exhaustion.

4 * 1024 * 1024
AMPERSAND_BYTE =
0x26
EQUALS_BYTE =
0x3D

Class Method Summary collapse

Class Method Details

.parse(payload, limit: DEFAULT_BYTESIZE_LIMIT) ⇒ Object

Parses a URL encoded payload (query string or form data) into a hash of keys and values, merging duplicate keys.

Example:

URLEncoded.parse("foo=bar&foo=baz&qux=quux") # => {"foo" => ["bar", "baz"], "qux" => "quux"}

Parsing stops once limit bytes have been read, and the pair being read at that point is discarded. This returns the pairs decoded so far rather than raising or discarding the whole payload.

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
# File 'lib/datadog/appsec/utils/http/url_encoded.rb', line 30

def parse(payload, limit: DEFAULT_BYTESIZE_LIMIT)
  raise ArgumentError, "limit must not be negative" if limit < 0

  return {} if payload.nil? || payload.empty? || limit.zero?

  # @type var result: URLEncoded::params
  result = {}
  payload_bytesize = payload.bytesize
  bytes_to_parse = (payload_bytesize < limit) ? payload_bytesize : limit

  index = 0
  param_start = 0
  equals_index = nil # : Integer?

  payload.each_byte do |byte|
    break if index >= bytes_to_parse

    if byte == AMPERSAND_BYTE
      param_end = index

      if equals_index
        key = payload.byteslice(param_start, equals_index - param_start) # : String
        value = payload.byteslice(equals_index + 1, param_end - equals_index - 1) # : String?
      else
        key = payload.byteslice(param_start, param_end - param_start) # : String
        value = nil
      end

      if !key.empty? || value
        key = CGI.unescape(key)
        value = CGI.unescape(value) if value

        add_param(result, key, value)
      end

      param_start = index + 1
      equals_index = nil
    elsif byte == EQUALS_BYTE
      equals_index ||= index
    end

    index += 1
  end

  if bytes_to_parse == payload_bytesize && param_start < index
    param_end = index

    if equals_index
      key = payload.byteslice(param_start, equals_index - param_start) # : String
      value = payload.byteslice(equals_index + 1, param_end - equals_index - 1) # : String?
    else
      key = payload.byteslice(param_start, param_end - param_start) # : String
      value = nil
    end

    if !key.empty? || value
      key = CGI.unescape(key)
      value = CGI.unescape(value) if value

      add_param(result, key, value)
    end
  end

  result
end