2013-07-11 10:30:59 +02:00
|
|
|
local util = require("util.utils") --required for string:split()
|
2013-07-05 17:26:39 +02:00
|
|
|
local urlcode = require("util.urlcode")
|
2013-07-10 00:32:43 +02:00
|
|
|
local config = require("config")
|
2013-07-09 01:49:56 +02:00
|
|
|
local ResponseClass = require("rest.response")
|
2013-07-05 17:26:39 +02:00
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
local M = {}
|
|
|
|
M.__index = M
|
2013-07-05 17:26:39 +02:00
|
|
|
|
2013-07-09 01:49:56 +02:00
|
|
|
local GLOBAL_API_FUNCTION_NAME = "_global"
|
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
|
|
|
|
--NOTE: requestedApi* contain what was extracted from the request data
|
|
|
|
-- regarding the other variables: either both resolvedApiFunction and realApiFunctionName
|
|
|
|
-- are nil and resolutionError is not, or exactly the other way around
|
|
|
|
M.requestedApiModule = nil
|
|
|
|
M.requestedApiFunction = nil
|
|
|
|
M.resolvedApiFunction = nil --will contain function address, or nil
|
|
|
|
M.realApiFunctionName = nil --will contain requested name, or global name, or nil
|
|
|
|
M.resolutionError = nil --non-nil means function could not be resolved
|
2013-07-17 08:06:04 +02:00
|
|
|
M.moduleAccessTable = nil
|
2013-07-10 00:32:43 +02:00
|
|
|
|
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
local function kvTableFromUrlEncodedString(encodedText)
|
|
|
|
local args = {}
|
|
|
|
if (encodedText ~= nil) then
|
|
|
|
urlcode.parsequery(encodedText, args)
|
|
|
|
end
|
|
|
|
return args
|
2013-07-10 00:32:43 +02:00
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
end
|
2013-07-05 17:26:39 +02:00
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
local function kvTableFromArray(argArray)
|
|
|
|
local args = {}
|
|
|
|
|
2013-07-17 08:06:04 +02:00
|
|
|
if not argArray then return args end
|
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
for _, v in ipairs(argArray) do
|
2013-07-05 17:26:39 +02:00
|
|
|
local split = v:find("=")
|
|
|
|
if split ~= nil then
|
2013-07-08 13:34:27 +02:00
|
|
|
args[v:sub(1, split - 1)] = v:sub(split + 1)
|
|
|
|
else
|
|
|
|
args[v] = true
|
2013-07-05 17:26:39 +02:00
|
|
|
end
|
|
|
|
end
|
2013-07-08 13:34:27 +02:00
|
|
|
|
|
|
|
return args
|
|
|
|
end
|
|
|
|
|
2013-07-11 10:30:59 +02:00
|
|
|
--NOTE: this function ignores empty tokens (e.g. '/a//b/' yields { [1] = a, [2] = b })
|
|
|
|
local function arrayFromPath(pathText)
|
2013-07-17 08:06:04 +02:00
|
|
|
return pathText and pathText:split("/") or {}
|
|
|
|
end
|
|
|
|
|
|
|
|
--returns true if acceptable is nil or empty or 'ANY' or if it contains requested
|
|
|
|
local function matchRequestMethod(acceptable, requested)
|
|
|
|
return acceptable == nil or acceptable == '' or acceptable == 'ANY' or string.find(acceptable, requested)
|
2013-07-11 10:30:59 +02:00
|
|
|
end
|
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
--returns either a module object, or nil+errmsg
|
|
|
|
local function resolveApiModule(modname)
|
|
|
|
if modname == nil then return nil, "missing module name" end
|
|
|
|
if string.find(modname, "_") == 1 then return nil, "module names starting with '_' are preserved for internal use" end
|
|
|
|
|
|
|
|
local reqModName = "rest.api.api_" .. modname
|
|
|
|
local ok, modObj
|
|
|
|
|
|
|
|
if config.DEBUG_PCALLS then ok, modObj = true, require(reqModName)
|
|
|
|
else ok, modObj = pcall(require, reqModName)
|
|
|
|
end
|
|
|
|
|
|
|
|
if ok == false then return nil, "API module does not exist" end
|
|
|
|
if modObj == nil then return nil, "API module could not be found" end
|
|
|
|
if modObj.isApi ~= true then return nil, "module is not part of the CGI API" end
|
|
|
|
|
|
|
|
return modObj
|
|
|
|
end
|
|
|
|
|
2013-07-17 08:06:04 +02:00
|
|
|
--returns resultData+nil (usual), or nil+errmsg (unresolvable or inaccessible)
|
|
|
|
--resultData contains 'func', 'accessTable' and if found, also 'blankArg'
|
2013-07-10 00:32:43 +02:00
|
|
|
local function resolveApiFunction(modname, funcname)
|
2013-07-17 08:06:04 +02:00
|
|
|
local resultData = {}
|
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
if funcname and string.find(funcname, "_") == 1 then return nil, "function names starting with '_' are preserved for internal use" end
|
|
|
|
|
|
|
|
local mod, msg = resolveApiModule(modname)
|
|
|
|
|
2013-07-11 10:30:59 +02:00
|
|
|
if mod == nil then
|
|
|
|
return nil, msg
|
|
|
|
end
|
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
if (funcname == nil or funcname == '') then funcname = GLOBAL_API_FUNCTION_NAME end --treat empty function name as nil
|
|
|
|
local f = mod[funcname]
|
|
|
|
local funcNumber = tonumber(funcname)
|
|
|
|
|
|
|
|
if (type(f) == "function") then
|
2013-07-17 08:06:04 +02:00
|
|
|
resultData.func = f
|
|
|
|
resultData.accessTable = mod._access
|
2013-07-10 00:32:43 +02:00
|
|
|
elseif funcNumber ~= nil then
|
2013-07-17 08:06:04 +02:00
|
|
|
resultData.func = mod[GLOBAL_API_FUNCTION_NAME]
|
|
|
|
resultData.accessTable = mod._access
|
|
|
|
resultData.blankArg = funcNumber
|
2013-07-10 00:32:43 +02:00
|
|
|
else
|
|
|
|
return nil, ("function '" .. funcname .. "' does not exist in API module '" .. modname .. "'")
|
|
|
|
end
|
2013-07-17 08:06:04 +02:00
|
|
|
|
|
|
|
return resultData
|
2013-07-10 00:32:43 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
setmetatable(M, {
|
|
|
|
__call = function(cls, ...)
|
|
|
|
return cls.new(...)
|
|
|
|
end
|
|
|
|
})
|
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
--This function initializes itself using various environment variables, the arg array and the given postData
|
|
|
|
--NOTE: if debugging is enabled, commandline arguments 'm' and 'f' override requested module and function
|
2013-07-08 13:34:27 +02:00
|
|
|
function M.new(postData, debug)
|
|
|
|
local self = setmetatable({}, M)
|
|
|
|
|
|
|
|
--NOTE: is it correct to assume that absence of REQUEST_METHOD indicates command line invocation?
|
|
|
|
self.requestMethod = os.getenv("REQUEST_METHOD")
|
|
|
|
if self.requestMethod ~= nil then
|
|
|
|
self.remoteHost = os.getenv("REMOTE_HOST")
|
|
|
|
self.remotePort = os.getenv("REMOTE_PORT")
|
|
|
|
self.userAgent = os.getenv("HTTP_USER_AGENT")
|
|
|
|
else
|
|
|
|
self.requestMethod = "CMDLINE"
|
|
|
|
end
|
|
|
|
|
|
|
|
self.cmdLineArgs = kvTableFromArray(arg)
|
|
|
|
self.getArgs = kvTableFromUrlEncodedString(os.getenv("QUERY_STRING"))
|
|
|
|
self.postArgs = kvTableFromUrlEncodedString(postData)
|
2013-07-11 10:30:59 +02:00
|
|
|
self.pathArgs = arrayFromPath(os.getenv("PATH_INFO"))
|
2013-07-08 13:34:27 +02:00
|
|
|
|
2013-07-11 10:30:59 +02:00
|
|
|
--override path arguments with command line parameter if debugging is enabled
|
|
|
|
if debug and self.requestMethod == "CMDLINE" then
|
|
|
|
self.pathArgs = arrayFromPath(self.cmdLineArgs["p"])
|
2013-07-10 00:32:43 +02:00
|
|
|
end
|
2013-07-17 08:06:04 +02:00
|
|
|
table.remove(self.pathArgs, 1) --drop the first 'empty' field caused by the opening slash of the query string
|
2013-07-10 00:32:43 +02:00
|
|
|
|
2013-07-11 10:30:59 +02:00
|
|
|
|
|
|
|
if #self.pathArgs >= 1 then self.requestedApiModule = self.pathArgs[1] end
|
|
|
|
if #self.pathArgs >= 2 then self.requestedApiFunction = self.pathArgs[2] end
|
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
if self.requestedApiModule == "" then self.requestedApiModule = nil end
|
|
|
|
if self.requestedApiFunction == "" then self.requestedApiFunction = nil end
|
|
|
|
|
|
|
|
|
|
|
|
-- Perform module/function resolution
|
2013-07-17 08:06:04 +02:00
|
|
|
local rData, errMsg = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction())
|
2013-07-10 00:32:43 +02:00
|
|
|
|
2013-07-17 08:06:04 +02:00
|
|
|
if rData ~= nil and rData.func ~= nil then --function (possibly the global one) could be resolved
|
|
|
|
self.resolvedApiFunction = rData.func
|
|
|
|
self.moduleAccessTable = rData.accessTable
|
|
|
|
if rData.blankArg ~= nil then --apparently it was the global one, and we received a 'blank argument'
|
|
|
|
self:setBlankArgument(rData.blankArg)
|
2013-07-10 00:32:43 +02:00
|
|
|
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
|
|
|
else --resolved without blank argument but still potentially the global function, hence the _or_ construction
|
2013-07-11 10:30:59 +02:00
|
|
|
if self:getRequestedApiFunction() ~= nil then
|
|
|
|
self.realApiFunctionName = self:getRequestedApiFunction()
|
|
|
|
if #self.pathArgs >= 3 then self:setBlankArgument(self.pathArgs[3]) end --aha, we have both a function and a blank argument
|
|
|
|
else
|
|
|
|
self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME
|
|
|
|
end
|
2013-07-10 00:32:43 +02:00
|
|
|
end
|
|
|
|
else
|
|
|
|
--instead of throwing an error, save the message for handle() which is expected to return a response anyway
|
2013-07-17 08:06:04 +02:00
|
|
|
self.resolutionError = errMsg
|
2013-07-08 13:34:27 +02:00
|
|
|
end
|
|
|
|
|
2013-07-08 16:53:45 +02:00
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2013-07-17 08:06:04 +02:00
|
|
|
function M:getRequestMethod() return self.requestMethod end --returns either GET or POST or CMDLINE
|
|
|
|
function M:getRequestedApiModule() return self.requestedApiModule end
|
|
|
|
function M:getRequestedApiFunction() return self.requestedApiFunction end
|
|
|
|
function M:getRealApiFunctionName() return self.realApiFunctionName end
|
|
|
|
function M:getBlankArgument() return self.blankArgument end
|
|
|
|
function M:setBlankArgument(arg) self.blankArgument = arg end
|
2013-07-08 13:34:27 +02:00
|
|
|
function M:getRemoteHost() return self.remoteHost or "" end
|
|
|
|
function M:getRemotePort() return self.remotePort or 0 end
|
|
|
|
function M:getUserAgent() return self.userAgent or "" end
|
|
|
|
|
|
|
|
function M:get(key)
|
|
|
|
if self.requestMethod == "GET" then
|
|
|
|
return self.getArgs[key]
|
|
|
|
elseif self.requestMethod == "POST" then
|
|
|
|
return self.postArgs[key]
|
|
|
|
elseif self.requestMethod == "CMDLINE" then
|
|
|
|
return self.cmdLineArgs[key]
|
|
|
|
else
|
|
|
|
return nil
|
|
|
|
end
|
2013-07-05 17:26:39 +02:00
|
|
|
end
|
2013-07-08 13:34:27 +02:00
|
|
|
|
|
|
|
function M:getAll()
|
|
|
|
if self.requestMethod == "GET" then
|
|
|
|
return self.getArgs
|
|
|
|
elseif self.requestMethod == "POST" then
|
|
|
|
return self.postArgs
|
|
|
|
elseif self.requestMethod == "CMDLINE" then
|
|
|
|
return self.cmdLineArgs
|
|
|
|
else
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-07-11 10:30:59 +02:00
|
|
|
function M:getPathData()
|
|
|
|
return self.pathArgs
|
|
|
|
end
|
|
|
|
|
2013-07-09 01:49:56 +02:00
|
|
|
--returns either a response object+nil, or response object+errmsg
|
|
|
|
function M:handle()
|
2013-07-10 00:32:43 +02:00
|
|
|
local modname = self:getRequestedApiModule()
|
|
|
|
local resp = ResponseClass.new(self)
|
2013-07-09 01:49:56 +02:00
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
if (self.resolvedApiFunction ~= nil) then --we found a function (possible the global function)
|
2013-07-17 08:06:04 +02:00
|
|
|
--check access type
|
|
|
|
local accessText = self.moduleAccessTable[self.realApiFunctionName]
|
|
|
|
if not matchRequestMethod(accessText, self.requestMethod) then
|
|
|
|
resp:setError("function '" .. modname .. "/" .. self.realApiFunctionName .. "' requires different request method ('" .. accessText .. "')")
|
|
|
|
return resp, "incorrect access method (" .. accessText .. " != " .. self.requestMethod .. ")"
|
|
|
|
end
|
|
|
|
|
2013-07-10 00:32:43 +02:00
|
|
|
--invoke the function
|
2013-07-09 01:49:56 +02:00
|
|
|
local ok, r
|
2013-07-10 00:32:43 +02:00
|
|
|
if config.DEBUG_PCALLS then ok, r = true, self.resolvedApiFunction(self, resp)
|
|
|
|
else ok, r = pcall(self.resolvedApiFunction, self, resp)
|
2013-07-09 01:49:56 +02:00
|
|
|
end
|
2013-07-10 00:32:43 +02:00
|
|
|
|
|
|
|
--handle the result
|
2013-07-09 01:49:56 +02:00
|
|
|
if ok == true then
|
2013-07-10 00:32:43 +02:00
|
|
|
return resp, nil
|
2013-07-09 01:49:56 +02:00
|
|
|
else
|
2013-07-10 00:32:43 +02:00
|
|
|
resp:setError("call to function '" .. modname .. "/" .. self.realApiFunctionName .. "' failed")
|
|
|
|
return resp, ("calling function '" .. self.realApiFunctionName .. "' in API module '" .. modname .. "' somehow failed ('" .. r .. "')")
|
2013-07-09 01:49:56 +02:00
|
|
|
end
|
|
|
|
else
|
2013-07-11 10:30:59 +02:00
|
|
|
resp:setError("function or module unknown '" .. (modname or "<empty>") .. "/" .. (self:getRequestedApiFunction() or "<empty>") .. "'")
|
2013-07-10 00:32:43 +02:00
|
|
|
return resp, ("could not resolve requested API function ('" .. self.resolutionError .. "')")
|
2013-07-09 01:49:56 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
return resp
|
|
|
|
end
|
|
|
|
|
2013-07-08 13:34:27 +02:00
|
|
|
return M
|