util.xmlrpc: Support for multiple parameters in requests
[prosody.git] / util / xmlrpc.lua
1 -- Prosody IM v0.4
2 -- Copyright (C) 2008-2009 Matthew Wild
3 -- Copyright (C) 2008-2009 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9
10 local pairs = pairs;
11 local type = type;
12 local error = error;
13 local t_concat = table.concat;
14 local t_insert = table.insert;
15 local tostring = tostring;
16 local tonumber = tonumber;
17 local select = select;
18 local st = require "util.stanza";
19
20 module "xmlrpc"
21
22 local _lua_to_xmlrpc;
23 local map = {
24         table=function(stanza, object)
25                 stanza:tag("struct");
26                 for name, value in pairs(object) do
27                         stanza:tag("member");
28                                 stanza:tag("name"):text(tostring(name)):up();
29                                 stanza:tag("value");
30                                         _lua_to_xmlrpc(stanza, value);
31                                 stanza:up();
32                         stanza:up();
33                 end
34                 stanza:up();
35         end;
36         boolean=function(stanza, object)
37                 stanza:tag("boolean"):text(object and "1" or "0"):up();
38         end;
39         string=function(stanza, object)
40                 stanza:tag("string"):text(object):up();
41         end;
42         number=function(stanza, object)
43                 stanza:tag("int"):text(tostring(object)):up();
44         end;
45         ["nil"]=function(stanza, object) -- nil extension
46                 stanza:tag("nil"):up();
47         end;
48 };
49 _lua_to_xmlrpc = function(stanza, ...)
50         for i=1,select('#', ...) do
51                 stanza:tag("param"):tag("value");
52                 local object = select(i, ...);
53                 local h = map[type(object)];
54                 if h then
55                         h(stanza, object);
56                 else
57                         error("Type not supported by XML-RPC: " .. type(object));
58                 end
59                 stanza:up():up();
60         end
61 end
62 function create_response(object)
63         local stanza = st.stanza("methodResponse"):tag("params"):tag("param"):tag("value");
64         _lua_to_xmlrpc(stanza, object);
65         stanza:up():up():up();
66         return stanza;
67 end
68 function create_error_response(faultCode, faultString)
69         local stanza = st.stanza("methodResponse"):tag("fault"):tag("value");
70         _lua_to_xmlrpc(stanza, {faultCode=faultCode, faultString=faultString});
71         stanza:up():up();
72         return stanza;
73 end
74
75 function create_request(method_name, ...)
76         local stanza = st.stanza("methodCall")
77                 :tag("methodName"):text(method_name):up()
78                 :tag("params");
79         _lua_to_xmlrpc(stanza, ...);
80         stanza:up():up():up();
81         return stanza;
82 end
83
84 local _xmlrpc_to_lua;
85 local int_parse = function(stanza)
86         if #stanza.tags ~= 0 or #stanza == 0 then error("<"..stanza.name.."> must have a single text child"); end
87         local n = tonumber(t_concat(stanza));
88         if n then return n; end
89         error("Failed to parse content of <"..stanza.name..">");
90 end
91 local rmap = {
92         methodCall=function(stanza)
93                 if #stanza.tags ~= 2 then error("<methodCall> must have exactly two subtags"); end -- FIXME <params> is optional
94                 if stanza.tags[1].name ~= "methodName" then error("First <methodCall> child tag must be <methodName>") end
95                 if stanza.tags[2].name ~= "params" then error("Second <methodCall> child tag must be <params>") end
96                 return _xmlrpc_to_lua(stanza.tags[1]), _xmlrpc_to_lua(stanza.tags[2]);
97         end;
98         methodName=function(stanza)
99                 if #stanza.tags ~= 0 then error("<methodName> must not have any subtags"); end
100                 if #stanza == 0 then error("<methodName> must have text content"); end
101                 return t_concat(stanza);
102         end;
103         params=function(stanza)
104                 local t = {};
105                 for _, child in pairs(stanza.tags) do
106                         if child.name ~= "param" then error("<params> can only have <param> children"); end;
107                         t_insert(t, _xmlrpc_to_lua(child));
108                 end
109                 return t;
110         end;
111         param=function(stanza)
112                 if not(#stanza.tags == 1 and stanza.tags[1].name == "value") then error("<param> must have exactly one <value> child"); end
113                 return _xmlrpc_to_lua(stanza.tags[1]);
114         end;
115         value=function(stanza)
116                 if #stanza.tags == 0 then return t_concat(stanza); end
117                 if #stanza.tags ~= 1 then error("<value> must have a single child"); end
118                 return _xmlrpc_to_lua(stanza.tags[1]);
119         end;
120         int=int_parse;
121         i4=int_parse;
122         double=int_parse;
123         boolean=function(stanza)
124                 if #stanza.tags ~= 0 or #stanza == 0 then error("<boolean> must have a single text child"); end
125                 local b = t_concat(stanza);
126                 if b ~= "1" and b ~= "0" then error("Failed to parse content of <boolean>"); end
127                 return b == "1" and true or false;
128         end;
129         string=function(stanza)
130                 if #stanza.tags ~= 0 then error("<string> must have a single text child"); end
131                 return t_concat(stanza);
132         end;
133         array=function(stanza)
134                 if #stanza.tags ~= 1 then error("<array> must have a single <data> child"); end
135                 return _xmlrpc_to_lua(stanza.tags[1]);
136         end;
137         data=function(stanza)
138                 local t = {};
139                 for _,child in pairs(stanza.tags) do
140                         if child.name ~= "value" then error("<data> can only have <value> children"); end
141                         t_insert(t, _xmlrpc_to_lua(child));
142                 end
143                 return t;
144         end;
145         struct=function(stanza)
146                 local t = {};
147                 for _,child in pairs(stanza.tags) do
148                         if child.name ~= "member" then error("<struct> can only have <member> children"); end
149                         local name, value = _xmlrpc_to_lua(child);
150                         t[name] = value;
151                 end
152                 return t;
153         end;
154         member=function(stanza)
155                 if #stanza.tags ~= 2 then error("<member> must have exactly two subtags"); end -- FIXME <params> is optional
156                 if stanza.tags[1].name ~= "name" then error("First <member> child tag must be <name>") end
157                 if stanza.tags[2].name ~= "value" then error("Second <member> child tag must be <value>") end
158                 return _xmlrpc_to_lua(stanza.tags[1]), _xmlrpc_to_lua(stanza.tags[2]);
159         end;
160         name=function(stanza)
161                 if #stanza.tags ~= 0 then error("<name> must have a single text child"); end
162                 local n = t_concat(stanza)
163                 if tostring(tonumber(n)) == n then n = tonumber(n); end
164                 return n;
165         end;
166         ["nil"]=function(stanza) -- nil extension
167                 return nil;
168         end;
169 }
170 _xmlrpc_to_lua = function(stanza)
171         local h = rmap[stanza.name];
172         if h then
173                 return h(stanza);
174         else
175                 error("Unknown element: "..stanza.name);
176         end
177 end
178 function translate_request(stanza)
179         if stanza.name ~= "methodCall" then error("XML-RPC requests must have <methodCall> as root element"); end
180         return _xmlrpc_to_lua(stanza);
181 end
182
183 return _M;