Merge 0.10->trunk
[prosody.git] / plugins / mod_pubsub / pubsub.lib.lua
1 local st = require "util.stanza";
2 local uuid_generate = require "util.uuid".generate;
3 local dataform = require"util.dataforms".new;
4
5 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
6 local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
7 local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
8
9 local _M = {};
10
11 local handlers = {};
12 _M.handlers = handlers;
13
14 local pubsub_errors = {
15         ["conflict"] = { "cancel", "conflict" };
16         ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
17         ["jid-required"] = { "modify", "bad-request", nil, "jid-required" };
18         ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" };
19         ["item-not-found"] = { "cancel", "item-not-found" };
20         ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
21         ["forbidden"] = { "auth", "forbidden" };
22         ["not-allowed"] = { "cancel", "not-allowed" };
23 };
24 local function pubsub_error_reply(stanza, error)
25         local e = pubsub_errors[error];
26         local reply = st.error_reply(stanza, unpack(e, 1, 3));
27         if e[4] then
28                 reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
29         end
30         return reply;
31 end
32 _M.pubsub_error_reply = pubsub_error_reply;
33
34 local node_config_form = dataform {
35         {
36                 type = "hidden";
37                 name = "FORM_TYPE";
38                 value = "http://jabber.org/protocol/pubsub#node_config";
39         };
40         {
41                 type = "text-single";
42                 name = "pubsub#max_items";
43                 label = "Max # of items to persist";
44         };
45 };
46
47 function handlers.get_items(origin, stanza, items, service)
48         local node = items.attr.node;
49         local item = items:get_child("item");
50         local id = item and item.attr.id;
51
52         if not node then
53                 origin.send(pubsub_error_reply(stanza, "nodeid-required"));
54                 return true;
55         end
56         local ok, results = service:get_items(node, stanza.attr.from, id);
57         if not ok then
58                 origin.send(pubsub_error_reply(stanza, results));
59                 return true;
60         end
61
62         local data = st.stanza("items", { node = node });
63         for _, id in ipairs(results) do
64                 data:add_child(results[id]);
65         end
66         local reply;
67         if data then
68                 reply = st.reply(stanza)
69                         :tag("pubsub", { xmlns = xmlns_pubsub })
70                                 :add_child(data);
71         else
72                 reply = pubsub_error_reply(stanza, "item-not-found");
73         end
74         origin.send(reply);
75         return true;
76 end
77
78 function handlers.get_subscriptions(origin, stanza, subscriptions, service)
79         local node = subscriptions.attr.node;
80         local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
81         if not ok then
82                 origin.send(pubsub_error_reply(stanza, ret));
83                 return true;
84         end
85         local reply = st.reply(stanza)
86                 :tag("pubsub", { xmlns = xmlns_pubsub })
87                         :tag("subscriptions");
88         for _, sub in ipairs(ret) do
89                 reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
90         end
91         origin.send(reply);
92         return true;
93 end
94
95 function handlers.set_create(origin, stanza, create, service)
96         local node = create.attr.node;
97         local ok, ret, reply;
98         if node then
99                 ok, ret = service:create(node, stanza.attr.from);
100                 if ok then
101                         reply = st.reply(stanza);
102                 else
103                         reply = pubsub_error_reply(stanza, ret);
104                 end
105         else
106                 repeat
107                         node = uuid_generate();
108                         ok, ret = service:create(node, stanza.attr.from);
109                 until ok or ret ~= "conflict";
110                 if ok then
111                         reply = st.reply(stanza)
112                                 :tag("pubsub", { xmlns = xmlns_pubsub })
113                                         :tag("create", { node = node });
114                 else
115                         reply = pubsub_error_reply(stanza, ret);
116                 end
117         end
118         origin.send(reply);
119         return true;
120 end
121
122 function handlers.set_delete(origin, stanza, delete, service)
123         local node = delete.attr.node;
124
125         local reply, notifier;
126         if not node then
127                 origin.send(pubsub_error_reply(stanza, "nodeid-required"));
128                 return true;
129         end
130         local ok, ret = service:delete(node, stanza.attr.from);
131         if ok then
132                 reply = st.reply(stanza);
133         else
134                 reply = pubsub_error_reply(stanza, ret);
135         end
136         origin.send(reply);
137         return true;
138 end
139
140 function handlers.set_subscribe(origin, stanza, subscribe, service)
141         local node, jid = subscribe.attr.node, subscribe.attr.jid;
142         if not (node and jid) then
143                 origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
144                 return true;
145         end
146         --[[
147         local options_tag, options = stanza.tags[1]:get_child("options"), nil;
148         if options_tag then
149                 options = options_form:data(options_tag.tags[1]);
150         end
151         --]]
152         local options_tag, options; -- FIXME
153         local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
154         local reply;
155         if ok then
156                 reply = st.reply(stanza)
157                         :tag("pubsub", { xmlns = xmlns_pubsub })
158                                 :tag("subscription", {
159                                         node = node,
160                                         jid = jid,
161                                         subscription = "subscribed"
162                                 }):up();
163                 if options_tag then
164                         reply:add_child(options_tag);
165                 end
166         else
167                 reply = pubsub_error_reply(stanza, ret);
168         end
169         origin.send(reply);
170 end
171
172 function handlers.set_unsubscribe(origin, stanza, unsubscribe, service)
173         local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
174         if not (node and jid) then
175                 origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
176                 return true;
177         end
178         local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
179         local reply;
180         if ok then
181                 reply = st.reply(stanza);
182         else
183                 reply = pubsub_error_reply(stanza, ret);
184         end
185         origin.send(reply);
186         return true;
187 end
188
189 function handlers.set_publish(origin, stanza, publish, service)
190         local node = publish.attr.node;
191         if not node then
192                 origin.send(pubsub_error_reply(stanza, "nodeid-required"));
193                 return true;
194         end
195         local item = publish:get_child("item");
196         local id = (item and item.attr.id);
197         if not id then
198                 id = uuid_generate();
199                 if item then
200                         item.attr.id = id;
201                 end
202         end
203         local ok, ret = service:publish(node, stanza.attr.from, id, item);
204         local reply;
205         if ok then
206                 reply = st.reply(stanza)
207                         :tag("pubsub", { xmlns = xmlns_pubsub })
208                                 :tag("publish", { node = node })
209                                         :tag("item", { id = id });
210         else
211                 reply = pubsub_error_reply(stanza, ret);
212         end
213         origin.send(reply);
214         return true;
215 end
216
217 function handlers.set_retract(origin, stanza, retract, service)
218         local node, notify = retract.attr.node, retract.attr.notify;
219         notify = (notify == "1") or (notify == "true");
220         local item = retract:get_child("item");
221         local id = item and item.attr.id
222         if not (node and id) then
223                 origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required"));
224                 return true;
225         end
226         local reply, notifier;
227         if notify then
228                 notifier = st.stanza("retract", { id = id });
229         end
230         local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
231         if ok then
232                 reply = st.reply(stanza);
233         else
234                 reply = pubsub_error_reply(stanza, ret);
235         end
236         origin.send(reply);
237         return true;
238 end
239
240 function handlers.set_purge(origin, stanza, purge, service)
241         local node, notify = purge.attr.node, purge.attr.notify;
242         notify = (notify == "1") or (notify == "true");
243         local reply;
244         if not node then
245                 origin.send(pubsub_error_reply(stanza, "nodeid-required"));
246                 return true;
247         end
248         local ok, ret = service:purge(node, stanza.attr.from, notify);
249         if ok then
250                 reply = st.reply(stanza);
251         else
252                 reply = pubsub_error_reply(stanza, ret);
253         end
254         origin.send(reply);
255         return true;
256 end
257
258 function handlers.get_configure(origin, stanza, config, service)
259         local node = config.attr.node;
260         if not node then
261                 origin.send(pubsub_error_reply(stanza, "nodeid-required"));
262                 return true;
263         end
264
265         if not service:may(node, stanza.attr.from, "configure") then
266                 origin.send(pubsub_error_reply(stanza, "forbidden"));
267                 return true;
268         end
269
270         local node_obj = service.nodes[node];
271         if not node_obj then
272                 origin.send(pubsub_error_reply(stanza, "item-not-found"));
273                 return true;
274         end
275
276         local reply = st.reply(stanza)
277                 :tag("pubsub", { xmlns = xmlns_pubsub_owner })
278                         :tag("configure", { node = node })
279                                 :add_child(node_config_form:form(node_obj.config));
280         origin.send(reply);
281         return true;
282 end
283
284 function handlers.set_configure(origin, stanza, config, service)
285         local node = config.attr.node;
286         if not node then
287                 origin.send(pubsub_error_reply(stanza, "nodeid-required"));
288                 return true;
289         end
290         if not service:may(node, stanza.attr.from, "configure") then
291                 origin.send(pubsub_error_reply(stanza, "forbidden"));
292                 return true;
293         end
294         local new_config, err = node_config_form:data(config.tags[1]);
295         if not new_config then
296                 origin.send(st.error_reply(stanza, "modify", "bad-request", err));
297                 return true;
298         end
299         local ok, err = service:set_node_config(node, stanza.attr.from, new_config);
300         if not ok then
301                 origin.send(pubsub_error_reply(stanza, err));
302                 return true;
303         end
304         origin.send(st.reply(stanza));
305         return true;
306 end
307
308 function handlers.get_default(origin, stanza, default, service)
309         local reply = st.reply(stanza)
310                 :tag("pubsub", { xmlns = xmlns_pubsub_owner })
311                         :tag("default")
312                                 :add_child(node_config_form:form(service.node_defaults));
313         origin.send(reply);
314         return true;
315 end
316
317 return _M;