mod_pubsub: Transplant pieces of cdcfd93e2f43 from trunk
[prosody.git] / plugins / mod_pubsub / pubsub.lib.lua
1 local st = require "util.stanza";
2 local uuid_generate = require "util.uuid".generate;
3
4 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
5 local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
6
7 local _M = {};
8
9 local handlers = {};
10 _M.handlers = handlers;
11
12 local pubsub_errors = {
13         ["conflict"] = { "cancel", "conflict" };
14         ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
15         ["jid-required"] = { "modify", "bad-request", nil, "jid-required" };
16         ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" };
17         ["item-not-found"] = { "cancel", "item-not-found" };
18         ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
19         ["forbidden"] = { "cancel", "forbidden" };
20 };
21 local function pubsub_error_reply(stanza, error)
22         local e = pubsub_errors[error];
23         local reply = st.error_reply(stanza, unpack(e, 1, 3));
24         if e[4] then
25                 reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
26         end
27         return reply;
28 end
29 _M.pubsub_error_reply = pubsub_error_reply;
30
31 function handlers.get_items(origin, stanza, items, service)
32         local node = items.attr.node;
33         local item = items:get_child("item");
34         local id = item and item.attr.id;
35
36         if not node then
37                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
38         end
39         local ok, results = service:get_items(node, stanza.attr.from, id);
40         if not ok then
41                 return origin.send(pubsub_error_reply(stanza, results));
42         end
43
44         local data = st.stanza("items", { node = node });
45         for _, id in ipairs(results) do
46                 data:add_child(results[id]);
47         end
48         local reply;
49         if data then
50                 reply = st.reply(stanza)
51                         :tag("pubsub", { xmlns = xmlns_pubsub })
52                                 :add_child(data);
53         else
54                 reply = pubsub_error_reply(stanza, "item-not-found");
55         end
56         return origin.send(reply);
57 end
58
59 function handlers.get_subscriptions(origin, stanza, subscriptions, service)
60         local node = subscriptions.attr.node;
61         local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
62         if not ok then
63                 return origin.send(pubsub_error_reply(stanza, ret));
64         end
65         local reply = st.reply(stanza)
66                 :tag("pubsub", { xmlns = xmlns_pubsub })
67                         :tag("subscriptions");
68         for _, sub in ipairs(ret) do
69                 reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
70         end
71         return origin.send(reply);
72 end
73
74 function handlers.set_create(origin, stanza, create, service)
75         local node = create.attr.node;
76         local ok, ret, reply;
77         if node then
78                 ok, ret = service:create(node, stanza.attr.from);
79                 if ok then
80                         reply = st.reply(stanza);
81                 else
82                         reply = pubsub_error_reply(stanza, ret);
83                 end
84         else
85                 repeat
86                         node = uuid_generate();
87                         ok, ret = service:create(node, stanza.attr.from);
88                 until ok or ret ~= "conflict";
89                 if ok then
90                         reply = st.reply(stanza)
91                                 :tag("pubsub", { xmlns = xmlns_pubsub })
92                                         :tag("create", { node = node });
93                 else
94                         reply = pubsub_error_reply(stanza, ret);
95                 end
96         end
97         return origin.send(reply);
98 end
99
100 function handlers.set_delete(origin, stanza, delete, service)
101         local node = delete.attr.node;
102
103         local reply, notifier;
104         if not node then
105                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
106         end
107         local ok, ret = service:delete(node, stanza.attr.from);
108         if ok then
109                 reply = st.reply(stanza);
110         else
111                 reply = pubsub_error_reply(stanza, ret);
112         end
113         return origin.send(reply);
114 end
115
116 function handlers.set_subscribe(origin, stanza, subscribe, service)
117         local node, jid = subscribe.attr.node, subscribe.attr.jid;
118         if not (node and jid) then
119                 return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
120         end
121         --[[
122         local options_tag, options = stanza.tags[1]:get_child("options"), nil;
123         if options_tag then
124                 options = options_form:data(options_tag.tags[1]);
125         end
126         --]]
127         local options_tag, options; -- FIXME
128         local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
129         local reply;
130         if ok then
131                 reply = st.reply(stanza)
132                         :tag("pubsub", { xmlns = xmlns_pubsub })
133                                 :tag("subscription", {
134                                         node = node,
135                                         jid = jid,
136                                         subscription = "subscribed"
137                                 }):up();
138                 if options_tag then
139                         reply:add_child(options_tag);
140                 end
141         else
142                 reply = pubsub_error_reply(stanza, ret);
143         end
144         origin.send(reply);
145 end
146
147 function handlers.set_unsubscribe(origin, stanza, unsubscribe, service)
148         local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
149         if not (node and jid) then
150                 return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
151         end
152         local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
153         local reply;
154         if ok then
155                 reply = st.reply(stanza);
156         else
157                 reply = pubsub_error_reply(stanza, ret);
158         end
159         return origin.send(reply);
160 end
161
162 function handlers.set_publish(origin, stanza, publish, service)
163         local node = publish.attr.node;
164         if not node then
165                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
166         end
167         local item = publish:get_child("item");
168         local id = (item and item.attr.id);
169         if not id then
170                 id = uuid_generate();
171                 if item then
172                         item.attr.id = id;
173                 end
174         end
175         local ok, ret = service:publish(node, stanza.attr.from, id, item);
176         local reply;
177         if ok then
178                 reply = st.reply(stanza)
179                         :tag("pubsub", { xmlns = xmlns_pubsub })
180                                 :tag("publish", { node = node })
181                                         :tag("item", { id = id });
182         else
183                 reply = pubsub_error_reply(stanza, ret);
184         end
185         return origin.send(reply);
186 end
187
188 function handlers.set_retract(origin, stanza, retract, service)
189         local node, notify = retract.attr.node, retract.attr.notify;
190         notify = (notify == "1") or (notify == "true");
191         local item = retract:get_child("item");
192         local id = item and item.attr.id
193         if not (node and id) then
194                 return origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required"));
195         end
196         local reply, notifier;
197         if notify then
198                 notifier = st.stanza("retract", { id = id });
199         end
200         local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
201         if ok then
202                 reply = st.reply(stanza);
203         else
204                 reply = pubsub_error_reply(stanza, ret);
205         end
206         return origin.send(reply);
207 end
208
209 function handlers.set_purge(origin, stanza, purge, service)
210         local node, notify = purge.attr.node, purge.attr.notify;
211         notify = (notify == "1") or (notify == "true");
212         local reply;
213         if not node then
214                 return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
215         end
216         local ok, ret = service:purge(node, stanza.attr.from, notify);
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 return _M;