You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
609 lines
19 KiB
Lua
609 lines
19 KiB
Lua
-- Copyright 2003-2010 Kepler Project
|
|
-- XML-RPC implementation for Lua.
|
|
|
|
local lxp = require "lxp"
|
|
local lom = require "lxp.lom"
|
|
|
|
local assert, error, ipairs, pairs, select, type, tonumber, unpack = assert, error, ipairs, pairs, select, type, tonumber, unpack
|
|
local format, gsub, strfind, strsub = string.format, string.gsub, string.find, string.sub
|
|
local concat, tinsert = table.concat, table.insert
|
|
local ceil = math.ceil
|
|
local parse = lom.parse
|
|
|
|
module (...)
|
|
|
|
_COPYRIGHT = "Copyright (C) 2003-2014 Kepler Project"
|
|
_DESCRIPTION = "LuaXMLRPC is a library to make remote procedure calls using XML-RPC"
|
|
_PKGNAME = "LuaXMLRPC"
|
|
_VERSION_MAJOR = 1
|
|
_VERSION_MINOR = 2
|
|
_VERSION_MICRO = 2
|
|
_VERSION = _VERSION_MAJOR .. "." .. _VERSION_MINOR .. "." .. _VERSION_MICRO
|
|
|
|
---------------------------------------------------------------------
|
|
-- XML-RPC Parser
|
|
---------------------------------------------------------------------
|
|
|
|
---------------------------------------------------------------------
|
|
local function trim (s)
|
|
return (type(s) == "string" and gsub (s, "^%s*(.-)%s*$", "%1"))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local function is_space (s)
|
|
return type(s) == "string" and trim(s) == ""
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Get next non-space element from tab starting from index i.
|
|
-- @param tab Table.
|
|
-- @param i Numeric index.
|
|
-- @return Object and its position on table; nil and an invalid index
|
|
-- when there is no more elements.
|
|
---------------------------------------------------------------------
|
|
function next_nonspace (tab, i)
|
|
if not i then i = 1 end
|
|
while is_space (tab[i]) do i = i+1 end
|
|
return tab[i], i
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Get next element of tab with the given tag starting from index i.
|
|
-- @param tab Table.
|
|
-- @param tag String with the name of the tag.
|
|
-- @param i Numeric index.
|
|
-- @return Object and its position on table; nil and an invalid index
|
|
-- when there is no more elements.
|
|
---------------------------------------------------------------------
|
|
local function next_tag (tab, tag, i)
|
|
if not i then i = 1 end
|
|
while tab[i] do
|
|
if type (tab[i]) == "table" and tab[i].tag == tag then
|
|
return tab[i], i
|
|
end
|
|
i = i + 1
|
|
end
|
|
return nil, i
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local function x2number (tab)
|
|
if tab.tag == "int" or tab.tag == "i4" or tab.tag == "i8" or tab.tag == "double" then
|
|
return tonumber (next_nonspace (tab, 1), 10)
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local function x2boolean (tab)
|
|
if tab.tag == "boolean" then
|
|
local v = next_nonspace (tab, 1)
|
|
return v == true or v == "true" or tonumber (v) == 1 or false
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local function x2string (tab)
|
|
return tab.tag == "string" and (tab[1] or "")
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local function x2date (tab)
|
|
return tab.tag == "dateTime.iso8601" and next_nonspace (tab, 1)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local function x2base64 (tab)
|
|
return tab.tag == "base64" and next_nonspace (tab, 1)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local function x2name (tab)
|
|
return tab.tag == "name" and next_nonspace (tab, 1)
|
|
end
|
|
|
|
local x2value
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a member object in its name and value parts.
|
|
-- @param tab Table with a DOM representation.
|
|
-- @return String (name) and Object (value).
|
|
-- @see x2name, x2value.
|
|
---------------------------------------------------------------------
|
|
local function x2member (tab)
|
|
return
|
|
x2name (next_tag(tab,"name")),
|
|
x2value (next_tag(tab,"value"))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a struct object into a Lua table.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Table with "name = value" pairs.
|
|
---------------------------------------------------------------------
|
|
local function x2struct (tab)
|
|
if tab.tag == "struct" then
|
|
local res = {}
|
|
for i = 1, #tab do
|
|
if not is_space (tab[i]) then
|
|
local name, val = x2member (tab[i])
|
|
res[name] = val
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble an array object into a Lua table.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Table.
|
|
---------------------------------------------------------------------
|
|
local function x2array (tab)
|
|
if tab.tag == "array" then
|
|
local d = next_tag (tab, "data")
|
|
local res = {}
|
|
for i = 1, #d do
|
|
if not is_space (d[i]) then
|
|
tinsert (res, x2value (d[i]))
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
local xmlrpc_types = {
|
|
int = x2number,
|
|
i4 = x2number,
|
|
i8 = x2number,
|
|
boolean = x2boolean,
|
|
string = x2string,
|
|
double = x2number,
|
|
["dateTime.iso8601"] = x2date,
|
|
base64 = x2base64,
|
|
struct = x2struct,
|
|
array = x2array,
|
|
}
|
|
|
|
local x2param, x2fault
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a methodResponse into a Lua object.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Boolean (indicating wether the response was successful)
|
|
-- and (a Lua object representing the return values OR the fault
|
|
-- string and the fault code).
|
|
---------------------------------------------------------------------
|
|
local function x2methodResponse (tab)
|
|
assert (type(tab) == "table", "Not a table")
|
|
assert (tab.tag == "methodResponse",
|
|
"Not a `methodResponse' tag: "..tab.tag)
|
|
local t = next_nonspace (tab, 1)
|
|
if t.tag == "params" then
|
|
return true, unpack (x2param (t))
|
|
elseif t.tag == "fault" then
|
|
local f = x2fault (t)
|
|
return false, f.faultString, f.faultCode
|
|
else
|
|
error ("Couldn't find a <params> nor a <fault> element")
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a value element into a Lua object.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Object.
|
|
---------------------------------------------------------------------
|
|
x2value = function (tab)
|
|
local t = tab.tag
|
|
assert (t == "value", "Not a `value' tag: "..t)
|
|
local n = next_nonspace (tab)
|
|
if type(n) == "string" or type(n) == "number" then
|
|
return n
|
|
elseif type (n) == "table" then
|
|
local t = n.tag
|
|
local get = xmlrpc_types[t]
|
|
if not get then error ("Invalid <"..t.."> element") end
|
|
return get (next_nonspace (tab))
|
|
elseif type(n) == "nil" then
|
|
-- the next best thing is to assume it's an empty string
|
|
return ""
|
|
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a fault element into a Lua object.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Object.
|
|
---------------------------------------------------------------------
|
|
x2fault = function (tab)
|
|
assert (tab.tag == "fault", "Not a `fault' tag: "..tab.tag)
|
|
return x2value (next_nonspace (tab))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a param element into a Lua object.
|
|
-- Ignore white spaces between elements.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Object.
|
|
---------------------------------------------------------------------
|
|
x2param = function (tab)
|
|
assert (tab.tag == "params", "Not a `params' tag")
|
|
local res = {}
|
|
local p, i = next_nonspace (tab, 1)
|
|
while p do
|
|
if p.tag == "param" then
|
|
tinsert (res, x2value (next_tag (p, "value")))
|
|
end
|
|
p, i = next_nonspace (tab, i+1)
|
|
end
|
|
return res
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a methodName element into a Lua object.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Object.
|
|
---------------------------------------------------------------------
|
|
local function x2methodName (tab)
|
|
assert (tab.tag == "methodName", "Not a `methodName' tag: "..tab.tag)
|
|
return (next_nonspace (tab, 1))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Disassemble a methodCall element into its name and a list of parameters.
|
|
-- @param tab Table with DOM representation.
|
|
-- @return Object.
|
|
---------------------------------------------------------------------
|
|
local function x2methodCall (tab)
|
|
assert (tab.tag == "methodCall", "Not a `methodCall' tag: "..tab.tag)
|
|
return
|
|
x2methodName (next_tag (tab,"methodName")),
|
|
x2param (next_tag (tab,"params"))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- End of XML-RPC Parser
|
|
---------------------------------------------------------------------
|
|
|
|
---------------------------------------------------------------------
|
|
-- Convert a Lua Object into an XML-RPC string.
|
|
---------------------------------------------------------------------
|
|
|
|
---------------------------------------------------------------------
|
|
local formats = {
|
|
boolean = "<boolean>%d</boolean>",
|
|
number = "<double>%d</double>",
|
|
string = "<string>%s</string>",
|
|
base64 = "<base64>%s</base64>",
|
|
|
|
array = "<array><data>\n%s\n</data></array>",
|
|
double = "<double>%s</double>",
|
|
int = "<int>%s</int>",
|
|
struct = "<struct>%s</struct>",
|
|
|
|
member = "<member><name>%s</name>%s</member>",
|
|
value = "<value>%s</value>",
|
|
|
|
param = "<param>%s</param>",
|
|
|
|
params = [[
|
|
<params>
|
|
%s
|
|
</params>]],
|
|
|
|
fault = [[
|
|
<fault>
|
|
%s
|
|
</fault>]],
|
|
|
|
methodCall = [[
|
|
<?xml version="1.0"?>
|
|
<methodCall>
|
|
<methodName>%s</methodName>
|
|
%s
|
|
</methodCall>
|
|
]],
|
|
|
|
methodResponse = [[
|
|
<?xml version="1.0"?>
|
|
<methodResponse>
|
|
%s
|
|
</methodResponse>]],
|
|
}
|
|
formats.table = formats.struct
|
|
|
|
local toxml = {}
|
|
toxml.double = function (v,t) return format (formats.double, v) end
|
|
toxml.int = function (v,t) return format (formats.int, v) end
|
|
toxml.string = function (v,t) return format (formats.string, v) end
|
|
toxml.base64 = function (v,t) return format (formats.base64, v) end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Build a XML-RPC representation of a boolean.
|
|
-- @param v Object.
|
|
-- @return String.
|
|
---------------------------------------------------------------------
|
|
function toxml.boolean (v)
|
|
local n = (v and 1) or 0
|
|
return format (formats.boolean, n)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Build a XML-RPC representation of a number.
|
|
-- @param v Object.
|
|
-- @param t Object representing the XML-RPC type of the value.
|
|
-- @return String.
|
|
---------------------------------------------------------------------
|
|
function toxml.number (v, t)
|
|
local tt = (type(t) == "table") and t["*type"]
|
|
if tt == "int" or tt == "i4" or tt == "i8" then
|
|
return toxml.int (v, t)
|
|
elseif tt == "double" then
|
|
return toxml.double (v, t)
|
|
elseif v == ceil(v) then
|
|
return toxml.int (v, t)
|
|
else
|
|
return toxml.double (v, t)
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- @param typ Object representing a type.
|
|
-- @return Function that generate an XML element of the given type.
|
|
-- The object could be a string (as usual in Lua) or a table with
|
|
-- a field named "type" that should be a string with the XML-RPC
|
|
-- type name.
|
|
---------------------------------------------------------------------
|
|
local function format_func (typ)
|
|
if type (typ) == "table" then
|
|
return toxml[typ.type]
|
|
else
|
|
return toxml[typ]
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- @param val Object representing an array of values.
|
|
-- @param typ Object representing the type of the value.
|
|
-- @return String representing the equivalent XML-RPC value.
|
|
---------------------------------------------------------------------
|
|
function toxml.array (val, typ)
|
|
local ret = {}
|
|
local et = typ.elemtype
|
|
local f = format_func (et)
|
|
for i,v in ipairs (val) do
|
|
if et and et ~= "array" then
|
|
tinsert (ret, format (formats.value, f (v, et)))
|
|
else
|
|
local ct,cv = type_val(v)
|
|
local cf = format_func(ct)
|
|
tinsert (ret, format (formats.value, cf(cv, ct)))
|
|
end
|
|
|
|
end
|
|
return format (formats.array, concat (ret, '\n'))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
---------------------------------------------------------------------
|
|
function toxml.struct (val, typ)
|
|
local ret = {}
|
|
if type (typ) == "table" then
|
|
for n,t in pairs (typ.elemtype) do
|
|
local f = format_func (t)
|
|
tinsert (ret, format (formats.member, n, f (val[n], t)))
|
|
end
|
|
else
|
|
for i, v in pairs (val) do
|
|
tinsert (ret, toxml.member (i, v))
|
|
end
|
|
end
|
|
return format (formats.struct, concat (ret))
|
|
end
|
|
|
|
toxml.table = toxml.struct
|
|
|
|
---------------------------------------------------------------------
|
|
---------------------------------------------------------------------
|
|
function toxml.member (n, v)
|
|
return format (formats.member, n, toxml.value (v))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Get type and value of object.
|
|
---------------------------------------------------------------------
|
|
function type_val (obj)
|
|
local t = type (obj)
|
|
local v = obj
|
|
if t == "table" then
|
|
t = obj["*type"] or "table"
|
|
v = obj["*value"] or obj
|
|
end
|
|
return t, v
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Convert a Lua object to a XML-RPC object (plain string).
|
|
---------------------------------------------------------------------
|
|
function toxml.value (obj)
|
|
local to, val = type_val (obj)
|
|
if type(to) == "table" then
|
|
return format (formats.value, toxml[to.type] (val, to))
|
|
else
|
|
-- primitive (not structured) types.
|
|
--return format (formats[to], val)
|
|
return format (formats.value, toxml[to] (val, to))
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- @param ... List of parameters.
|
|
-- @return String representing the `params' XML-RPC element.
|
|
---------------------------------------------------------------------
|
|
function toxml.params (...)
|
|
local params_list = {}
|
|
for i = 1, select ("#", ...) do
|
|
params_list[i] = format (formats.param, toxml.value (select (i, ...)))
|
|
end
|
|
return format (formats.params, concat (params_list, '\n '))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- @param method String with method's name.
|
|
-- @param ... List of parameters.
|
|
-- @return String representing the `methodCall' XML-RPC element.
|
|
---------------------------------------------------------------------
|
|
function toxml.methodCall (method, ...)
|
|
local idx = strfind (method, "[^A-Za-z_.:/0-9]")
|
|
if idx then
|
|
error (format ("Invalid character `%s'", strsub (method, idx, idx)))
|
|
end
|
|
return format (formats.methodCall, method, toxml.params (...))
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- @param err String with error message.
|
|
-- @return String representing the `fault' XML-RPC element.
|
|
---------------------------------------------------------------------
|
|
function toxml.fault (err)
|
|
local code
|
|
local message = err
|
|
if type (err) == "table" then
|
|
code = err.code
|
|
message = err.message
|
|
end
|
|
return format (formats.fault, toxml.value {
|
|
faultCode = { ["*type"] = "int", ["*value"] = code or err.faultCode or 1 },
|
|
faultString = message or err.faultString or "fatal error",
|
|
})
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- @param ok Boolean indicating if the response was correct or a
|
|
-- fault one.
|
|
-- @param params Object containing the response contents.
|
|
-- @return String representing the `methodResponse' XML-RPC element.
|
|
---------------------------------------------------------------------
|
|
function toxml.methodResponse (ok, params)
|
|
local resp
|
|
if ok then
|
|
resp = toxml.params (params)
|
|
else
|
|
resp = toxml.fault (params)
|
|
end
|
|
return format (formats.methodResponse, resp)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- End of converter from Lua to XML-RPC.
|
|
---------------------------------------------------------------------
|
|
|
|
|
|
---------------------------------------------------------------------
|
|
-- Create a representation of an array with the given element type.
|
|
---------------------------------------------------------------------
|
|
function newArray (elemtype)
|
|
return { type = "array", elemtype = elemtype, }
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Create a representation of a structure with the given members.
|
|
---------------------------------------------------------------------
|
|
function newStruct (members)
|
|
return { type = "struct", elemtype = members, }
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Create a representation of a value according to a type.
|
|
-- @param val Any Lua value.
|
|
-- @param typ A XML-RPC type.
|
|
---------------------------------------------------------------------
|
|
function newTypedValue (val, typ)
|
|
return { ["*type"] = typ, ["*value"] = val }
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Create the XML-RPC string used to call a method.
|
|
-- @param method String with method name.
|
|
-- @param ... Parameters to the call.
|
|
-- @return String with the XML string/document.
|
|
---------------------------------------------------------------------
|
|
function clEncode (method, ...)
|
|
return toxml.methodCall (method, ...)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Convert the method response document to a Lua table.
|
|
-- @param meth_resp String with XML document.
|
|
-- @return Boolean indicating whether the call was successful or not;
|
|
-- and a Lua object with the converted response element.
|
|
---------------------------------------------------------------------
|
|
function clDecode (meth_resp)
|
|
local d = parse (meth_resp)
|
|
if type(d) ~= "table" then
|
|
error ("Not an XML document: "..meth_resp)
|
|
end
|
|
return x2methodResponse (d)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Convert the method call (client request) document to a name and
|
|
-- a list of parameters.
|
|
-- @param request String with XML document.
|
|
-- @return String with method's name AND the table of arguments.
|
|
---------------------------------------------------------------------
|
|
function srvDecode (request)
|
|
local d = parse (request)
|
|
if type(d) ~= "table" then
|
|
error ("Not an XML document: "..request)
|
|
end
|
|
return x2methodCall (d)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Convert a table into an XML-RPC methodReponse element.
|
|
-- @param obj Lua object.
|
|
-- @param is_fault Boolean indicating wether the result should be
|
|
-- a `fault' element (default = false).
|
|
-- @return String with XML-RPC response.
|
|
---------------------------------------------------------------------
|
|
function srvEncode (obj, is_fault)
|
|
local ok = not (is_fault or false)
|
|
return toxml.methodResponse (ok, obj)
|
|
end
|
|
|
|
---------------------------------------------------------------------
|
|
-- Register the methods.
|
|
-- @param tab_or_func Table or mapping function.
|
|
-- If a table is given, it can have one level of objects and then the
|
|
-- methods;
|
|
-- if a function is given, it will be used as the dispatcher.
|
|
-- The given function should return a Lua function that implements.
|
|
---------------------------------------------------------------------
|
|
dispatch = error
|
|
function srvMethods (tab_or_func)
|
|
local t = type (tab_or_func)
|
|
if t == "function" then
|
|
dispatch = tab_or_func
|
|
elseif t == "table" then
|
|
dispatch = function (name)
|
|
local ok, _, obj, method = strfind (name, "^([^.]+)%.(.+)$")
|
|
if not ok then
|
|
return tab_or_func[name]
|
|
else
|
|
if tab_or_func[obj] and tab_or_func[obj][method] then
|
|
return function (...)
|
|
return tab_or_func[obj][method] (obj, ...)
|
|
end
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
else
|
|
error ("Argument is neither a table nor a function")
|
|
end
|
|
end
|