Merge 0.9->0.10
[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 = require"util.dataforms".new {
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                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
54         end
55         local ok, results = service:get_items(node, stanza.attr.from, id);
56         if not ok then
57                 return origin.send(pubsub_error_reply(stanza, results));
58         end
59
60         local data = st.stanza("items", { node = node });
61         for _, id in ipairs(results) do
62                 data:add_child(results[id]);
63         end
64         local reply;
65         if data then
66                 reply = st.reply(stanza)
67                         :tag("pubsub", { xmlns = xmlns_pubsub })
68                                 :add_child(data);
69         else
70                 reply = pubsub_error_reply(stanza, "item-not-found");
71         end
72         return origin.send(reply);
73 end
74
75 function handlers.get_subscriptions(origin, stanza, subscriptions, service)
76         local node = subscriptions.attr.node;
77         local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
78         if not ok then
79                 return origin.send(pubsub_error_reply(stanza, ret));
80         end
81         local reply = st.reply(stanza)
82                 :tag("pubsub", { xmlns = xmlns_pubsub })
83                         :tag("subscriptions");
84         for _, sub in ipairs(ret) do
85                 reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
86         end
87         return origin.send(reply);
88 end
89
90 function handlers.set_create(origin, stanza, create, service)
91         local node = create.attr.node;
92         local ok, ret, reply;
93         if node then
94                 ok, ret = service:create(node, stanza.attr.from);
95                 if ok then
96                         reply = st.reply(stanza);
97                 else
98                         reply = pubsub_error_reply(stanza, ret);
99                 end
100         else
101                 repeat
102                         node = uuid_generate();
103                         ok, ret = service:create(node, stanza.attr.from);
104                 until ok or ret ~= "conflict";
105                 if ok then
106                         reply = st.reply(stanza)
107                                 :tag("pubsub", { xmlns = xmlns_pubsub })
108                                         :tag("create", { node = node });
109                 else
110                         reply = pubsub_error_reply(stanza, ret);
111                 end
112         end
113         return origin.send(reply);
114 end
115
116 function handlers.set_delete(origin, stanza, delete, service)
117         local node = delete.attr.node;
118
119         local reply, notifier;
120         if not node then
121                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
122         end
123         local ok, ret = service:delete(node, stanza.attr.from);
124         if ok then
125                 reply = st.reply(stanza);
126         else
127                 reply = pubsub_error_reply(stanza, ret);
128         end
129         return origin.send(reply);
130 end
131
132 function handlers.set_subscribe(origin, stanza, subscribe, service)
133         local node, jid = subscribe.attr.node, subscribe.attr.jid;
134         if not (node and jid) then
135                 return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
136         end
137         --[[
138         local options_tag, options = stanza.tags[1]:get_child("options"), nil;
139         if options_tag then
140                 options = options_form:data(options_tag.tags[1]);
141         end
142         --]]
143         local options_tag, options; -- FIXME
144         local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
145         local reply;
146         if ok then
147                 reply = st.reply(stanza)
148                         :tag("pubsub", { xmlns = xmlns_pubsub })
149                                 :tag("subscription", {
150                                         node = node,
151                                         jid = jid,
152                                         subscription = "subscribed"
153                                 }):up();
154                 if options_tag then
155                         reply:add_child(options_tag);
156                 end
157         else
158                 reply = pubsub_error_reply(stanza, ret);
159         end
160         origin.send(reply);
161 end
162
163 function handlers.set_unsubscribe(origin, stanza, unsubscribe, service)
164         local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
165         if not (node and jid) then
166                 return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
167         end
168         local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
169         local reply;
170         if ok then
171                 reply = st.reply(stanza);
172         else
173                 reply = pubsub_error_reply(stanza, ret);
174         end
175         return origin.send(reply);
176 end
177
178 function handlers.set_publish(origin, stanza, publish, service)
179         local node = publish.attr.node;
180         if not node then
181                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
182         end
183         local item = publish:get_child("item");
184         local id = (item and item.attr.id);
185         if not id then
186                 id = uuid_generate();
187                 if item then
188                         item.attr.id = id;
189                 end
190         end
191         local ok, ret = service:publish(node, stanza.attr.from, id, item);
192         local reply;
193         if ok then
194                 reply = st.reply(stanza)
195                         :tag("pubsub", { xmlns = xmlns_pubsub })
196                                 :tag("publish", { node = node })
197                                         :tag("item", { id = id });
198         else
199                 reply = pubsub_error_reply(stanza, ret);
200         end
201         return origin.send(reply);
202 end
203
204 function handlers.set_retract(origin, stanza, retract, service)
205         local node, notify = retract.attr.node, retract.attr.notify;
206         notify = (notify == "1") or (notify == "true");
207         local item = retract:get_child("item");
208         local id = item and item.attr.id
209         if not (node and id) then
210                 return origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required"));
211         end
212         local reply, notifier;
213         if notify then
214                 notifier = st.stanza("retract", { id = id });
215         end
216         local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
217         if ok then
218                 reply = st.reply(stanza);
219         else
220                 reply = pubsub_error_reply(stanza, ret);
221         end
222         return origin.send(reply);
223 end
224
225 function handlers.set_purge(origin, stanza, purge, service)
226         local node, notify = purge.attr.node, purge.attr.notify;
227         notify = (notify == "1") or (notify == "true");
228         local reply;
229         if not node then
230                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
231         end
232         local ok, ret = service:purge(node, stanza.attr.from, notify);
233         if ok then
234                 reply = st.reply(stanza);
235         else
236                 reply = pubsub_error_reply(stanza, ret);
237         end
238         return origin.send(reply);
239 end
240
241 function handlers.get_configure(origin, stanza, config, service)
242         local node = config.attr.node;
243         if not node then
244                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
245         end
246
247         if not service:may(node, stanza.attr.from, "configure") then
248                 return origin.send(pubsub_error_reply(stanza, "forbidden"));
249         end
250
251         local node_obj = service.nodes[node];
252         if not node_obj then
253                 return origin.send(pubsub_error_reply(stanza, "item-not-found"));
254         end
255
256         local reply = st.reply(stanza)
257                 :tag("pubsub", { xmlns = xmlns_pubsub_owner })
258                         :tag("configure", { node = node })
259                                 :add_child(node_config_form:form(node_obj.config));
260         return origin.send(reply);
261 end
262
263 function handlers.set_configure(origin, stanza, config, service)
264         local node = config.attr.node;
265         if not node then
266                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
267         end
268         if not service:may(node, stanza.attr.from, "configure") then
269                 return origin.send(pubsub_error_reply(stanza, "forbidden"));
270         end
271         local new_config, err = node_config_form:data(config.tags[1]);
272         if not new_config then
273                 return origin.send(st.error_reply(stanza, "modify", "bad-request", err));
274         end
275         local ok, err = service:set_node_config(node, stanza.attr.from, new_config);
276         if not ok then
277                 return origin.send(pubsub_error_reply(stanza, err));
278         end
279         return origin.send(st.reply(stanza));
280 end
281
282 function handlers.get_default(origin, stanza, default, service)
283         local reply = st.reply(stanza)
284                 :tag("pubsub", { xmlns = xmlns_pubsub_owner })
285                         :tag("default")
286                                 :add_child(node_config_form:form(service.node_defaults));
287         return origin.send(reply);
288 end
289
290 return _M;