chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
local getmetatable = getmetatable
|
||||
local tonumber = tonumber
|
||||
local rawget = rawget
|
||||
local insert = table.insert
|
||||
local unpack = table.unpack -- luacheck: ignore table
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
local ngx = ngx
|
||||
local req = ngx.req
|
||||
local log = ngx.log
|
||||
local re_match = ngx.re.match
|
||||
local re_gmatch = ngx.re.gmatch
|
||||
local re_gsub = ngx.re.gsub
|
||||
local get_method = req.get_method
|
||||
|
||||
|
||||
local NOTICE = ngx.NOTICE
|
||||
|
||||
local ENC_LEFT_SQUARE_BRACKET = "%5B"
|
||||
local ENC_RIGHT_SQUARE_BRACKET = "%5D"
|
||||
|
||||
|
||||
local multipart_mt = {}
|
||||
|
||||
|
||||
function multipart_mt:__tostring()
|
||||
return self.data
|
||||
end
|
||||
|
||||
|
||||
function multipart_mt:__index(name)
|
||||
local json = rawget(self, "json")
|
||||
if json then
|
||||
return json[name]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- Extracts keys from a string representing a nested table
|
||||
-- e.g. [foo][bar][21].key => ["foo", "bar", 21, "key"].
|
||||
-- is_map is meant to label patterns that use the bracket map syntax.
|
||||
local function extract_param_keys(keys_string)
|
||||
local is_map = false
|
||||
|
||||
-- iterate through keys (split by dots or square brackets)
|
||||
local iterator, err = re_gmatch(keys_string, [=[\.([^\[\.]+)|\[([^\]]*)\]]=], "jos")
|
||||
if not iterator then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local keys = {}
|
||||
for captures, it_err in iterator do
|
||||
if it_err then
|
||||
log(NOTICE, it_err)
|
||||
|
||||
else
|
||||
local key_name
|
||||
if captures[1] then
|
||||
-- The first capture: `\.([^\[\.]+)` matches dot-separated keys
|
||||
key_name = captures[1]
|
||||
|
||||
else
|
||||
-- The second capture: \[([^\]]*)\] matches bracket-separated keys
|
||||
key_name = captures[2]
|
||||
|
||||
-- If a bracket-separated key is non-empty and non-numeric, set
|
||||
-- is_map to true: foo[test] is a map, foo[] and bar[42] are arrays
|
||||
local map_key_found = key_name ~= "" and tonumber(key_name) == nil
|
||||
if map_key_found then
|
||||
is_map = true
|
||||
end
|
||||
end
|
||||
|
||||
insert(keys, key_name)
|
||||
end
|
||||
end
|
||||
|
||||
return keys, nil, is_map
|
||||
end
|
||||
|
||||
|
||||
-- Extracts the parameter name and keys from a string
|
||||
-- e.g. myparam[foo][bar][21].key => myparam, ["foo", "bar", 21, "key"]
|
||||
local function get_param_name_and_keys(name_and_keys)
|
||||
-- key delimiter must appear after the first character
|
||||
-- e.g. for `[5][foo][bar].key`, `[5]` is the parameter name
|
||||
local first_key_delimiter = name_and_keys:find('[%[%.]', 2)
|
||||
if not first_key_delimiter then
|
||||
return nil, "keys not found"
|
||||
end
|
||||
|
||||
local param_name = name_and_keys:sub(1, first_key_delimiter - 1)
|
||||
local keys_string = name_and_keys:sub(first_key_delimiter)
|
||||
|
||||
local keys, err, is_map = extract_param_keys(keys_string)
|
||||
if not keys then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return param_name, nil, keys, is_map
|
||||
end
|
||||
|
||||
|
||||
-- Nests the provided path into container
|
||||
-- e.g. nest_path({}, {"foo", "bar", 21, "key"}, 42) => { foo = { bar = { [21] = { key = 42 } } } }
|
||||
local function nest_path(container, path, value)
|
||||
container = container or {}
|
||||
|
||||
if type(path) ~= "table" then
|
||||
return nil, "path must be a table"
|
||||
end
|
||||
|
||||
for i = 1, #path do
|
||||
local segment = path[i]
|
||||
|
||||
local arr_index = tonumber(segment)
|
||||
-- if it looks like: foo[] or bar[42], it's an array
|
||||
local isarray = segment == "" or arr_index ~= nil
|
||||
|
||||
if isarray then
|
||||
if i == #path then
|
||||
|
||||
if arr_index then
|
||||
insert(container, arr_index, value)
|
||||
return container[arr_index]
|
||||
end
|
||||
|
||||
if type(value) == "table" and getmetatable(value) ~= multipart_mt then
|
||||
for j, v in ipairs(value) do
|
||||
insert(container, j, v)
|
||||
end
|
||||
|
||||
return container
|
||||
end
|
||||
|
||||
container[#container + 1] = value
|
||||
return container
|
||||
|
||||
else
|
||||
local position = arr_index or 1
|
||||
if not container[position] then
|
||||
container[position] = {}
|
||||
container = container[position]
|
||||
end
|
||||
end
|
||||
|
||||
else -- it's a map
|
||||
if i == #path then
|
||||
container[segment] = value
|
||||
return container[segment]
|
||||
|
||||
elseif not container[segment] then
|
||||
container[segment] = {}
|
||||
container = container[segment]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Decodes a complex argument (map, array or mixed), into a nested table
|
||||
-- e.g. foo[bar][21].key, 42 => { foo = { bar = { [21] = { key = 42 } } } }
|
||||
local function decode_map_array_arg(name, value, container)
|
||||
local param_name, err, keys, is_map = get_param_name_and_keys(name)
|
||||
if not param_name or not keys or #keys == 0 then
|
||||
return nil, err or "not a map or array"
|
||||
end
|
||||
|
||||
-- the meaning of square brackets varies depending on the http method.
|
||||
-- It is considered a map when a non numeric value exists between brackets
|
||||
-- if the method is POST, PUT, or PATCH, otherwise it is interpreted as LHS
|
||||
-- brackets used for search capabilities (only in EE).
|
||||
if is_map then
|
||||
local method = get_method()
|
||||
if method ~= "POST" and method ~= "PUT" and method ~= "PATCH" then
|
||||
return nil, "map not supported for this method"
|
||||
end
|
||||
end
|
||||
|
||||
local path = {param_name, unpack(keys)}
|
||||
return nest_path(container, path, value)
|
||||
end
|
||||
|
||||
|
||||
local function decode_complex_arg(name, value, container)
|
||||
container = container or {}
|
||||
|
||||
if type(name) ~= "string" then
|
||||
container[name] = value
|
||||
return container[name]
|
||||
end
|
||||
|
||||
local decoded = decode_map_array_arg(name, value, container)
|
||||
if not decoded then
|
||||
container[name] = value
|
||||
return container[name]
|
||||
end
|
||||
|
||||
return decoded
|
||||
end
|
||||
|
||||
|
||||
local function decode_arg(raw_name, value)
|
||||
if type(raw_name) ~= "string" or re_match(raw_name, [[^\.+|\.$]], "jos") then
|
||||
return { name = value }
|
||||
end
|
||||
|
||||
-- unescape `[` and `]` characters when the array / map syntax is detected
|
||||
local array_map_pattern = ENC_LEFT_SQUARE_BRACKET .. "(.*?)" .. ENC_RIGHT_SQUARE_BRACKET
|
||||
local name = re_gsub(raw_name, array_map_pattern, "[$1]", "josi")
|
||||
|
||||
-- treat test[foo.bar] as a single match instead of splitting on the dot
|
||||
local iterator, err = re_gmatch(name, [=[([^.](?:\[[^\]]*\])*)+]=], "jos")
|
||||
if not iterator then
|
||||
if err then
|
||||
log(NOTICE, err)
|
||||
end
|
||||
|
||||
return decode_complex_arg(name, value)
|
||||
end
|
||||
|
||||
local names = {}
|
||||
local count = 0
|
||||
|
||||
while true do
|
||||
local captures, err = iterator()
|
||||
if captures then
|
||||
count = count + 1
|
||||
names[count] = captures[0]
|
||||
|
||||
elseif err then
|
||||
log(NOTICE, err)
|
||||
break
|
||||
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if count == 0 then
|
||||
return decode_complex_arg(name, value)
|
||||
end
|
||||
|
||||
local container = {}
|
||||
local bucket = container
|
||||
|
||||
for i = 1, count do
|
||||
if i == count then
|
||||
decode_complex_arg(names[i], value, bucket)
|
||||
return container
|
||||
|
||||
else
|
||||
bucket = decode_complex_arg(names[i], {}, bucket)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function decode(args)
|
||||
local i = 0
|
||||
local r = {}
|
||||
|
||||
if type(args) ~= "table" then
|
||||
return r
|
||||
end
|
||||
|
||||
for name, value in pairs(args) do
|
||||
i = i + 1
|
||||
r[i] = decode_arg(name, value)
|
||||
end
|
||||
|
||||
return r
|
||||
end
|
||||
|
||||
|
||||
return {
|
||||
decode = decode,
|
||||
multipart_mt = multipart_mt,
|
||||
_decode_arg = decode_arg,
|
||||
_extract_param_keys = extract_param_keys,
|
||||
_get_param_name_and_keys = get_param_name_and_keys,
|
||||
_nest_path = nest_path,
|
||||
_decode_map_array_arg = decode_map_array_arg,
|
||||
}
|
||||
Reference in New Issue
Block a user