32c75c4d80a72a951918ed142918c09c9b7821fd
[prosody.git] / plugins / mod_pubsub.lua
1 local pubsub = require "util.pubsub";
2 local st = require "util.stanza";
3 local jid_bare = require "util.jid".bare;
4 local uuid_generate = require "util.uuid".generate;
5
6 require "core.modulemanager".load(module.host, "iq");
7
8 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
9 local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
10 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
11
12 local service;
13
14 local handlers = {};
15
16 function handle_pubsub_iq(event)
17         local origin, stanza = event.origin, event.stanza;
18         local pubsub = stanza.tags[1];
19         local action = pubsub.tags[1];
20         local handler = handlers[stanza.attr.type.."_"..action.name];
21         if handler then
22                 handler(origin, stanza, action);
23                 return true;
24         end
25 end
26
27 local pubsub_errors = {
28         ["conflict"] = { "cancel", "conflict" };
29         ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
30         ["item-not-found"] = { "cancel", "item-not-found" };
31         ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
32         ["forbidden"] = { "cancel", "forbidden" };
33 };
34 function pubsub_error_reply(stanza, error)
35         local e = pubsub_errors[error];
36         local reply = st.error_reply(stanza, unpack(e, 1, 3));
37         if e[4] then
38                 reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
39         end
40         return reply;
41 end
42
43 function handlers.get_items(origin, stanza, items)
44         local node = items.attr.node;
45         local item = items:get_child("item");
46         local id = item and item.attr.id;
47         local data = st.stanza("items", { node = node });
48         for _, entry in pairs(service:get(node, stanza.attr.from, id)) do
49                 data:add_child(entry);
50         end
51         if data then
52                 reply = st.reply(stanza)
53                         :tag("pubsub", { xmlns = xmlns_pubsub })
54                                 :add_child(data);
55         else
56                 reply = pubsub_error_reply(stanza, "item-not-found");
57         end
58         return origin.send(reply);
59 end
60
61 function handlers.set_create(origin, stanza, create)
62         local node = create.attr.node;
63         local ok, ret, reply;
64         if node then
65                 ok, ret = service:create(node, stanza.attr.from);
66                 if ok then
67                         reply = st.reply(stanza);
68                 else
69                         reply = pubsub_error_reply(stanza, ret);
70                 end
71         else
72                 repeat
73                         node = uuid_generate();
74                         ok, ret = service:create(node, stanza.attr.from);
75                 until ok;
76                 reply = st.reply(stanza)
77                         :tag("pubsub", { xmlns = xmlns_pubsub })
78                                 :tag("create", { node = node });
79         end
80         return origin.send(reply);
81 end
82
83 function handlers.set_subscribe(origin, stanza, subscribe)
84         local node, jid = subscribe.attr.node, subscribe.attr.jid;
85         if jid_bare(jid) ~= jid_bare(stanza.attr.from) then
86                 return origin.send(pubsub_error_reply(stanza, "invalid-jid"));
87         end
88         local ok, ret = service:add_subscription(node, stanza.attr.from, jid);
89         local reply;
90         if ok then
91                 reply = st.reply(stanza)
92                         :tag("pubsub", { xmlns = xmlns_pubsub })
93                                 :tag("subscription", {
94                                         node = node,
95                                         jid = jid,
96                                         subscription = "subscribed"
97                                 });
98         else
99                 reply = pubsub_error_reply(stanza, ret);
100         end
101         return origin.send(reply);
102 end
103
104 function handlers.set_unsubscribe(origin, stanza, unsubscribe)
105         local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
106         if jid_bare(jid) ~= jid_bare(stanza.attr.from) then
107                 return origin.send(pubsub_error_reply(stanza, "invalid-jid"));
108         end
109         local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
110         local reply;
111         if ok then
112                 reply = st.reply(stanza);
113         else
114                 reply = pubsub_error_reply(stanza, ret);
115         end
116         return origin.send(reply);
117 end
118
119 function handlers.set_publish(origin, stanza, publish)
120         local node = publish.attr.node;
121         local item = publish:get_child("item");
122         local id = (item and item.attr.id) or uuid_generate();
123         local ok, ret = service:publish(node, stanza.attr.from, id, item);
124         local reply;
125         if ok then
126                 reply = st.reply(stanza)
127                         :tag("pubsub", { xmlns = xmlns_pubsub })
128                                 :tag("publish", { node = node })
129                                         :tag("item", { id = id });
130         else
131                 reply = pubsub_error_reply(stanza, ret);
132         end
133         return origin.send(reply);
134 end
135
136 function handlers.set_retract(origin, stanza, retract)
137         local node, notify = retract.attr.node, retract.attr.notify;
138         notify = (notify == "1") or (notify == "true");
139         local item = retract:get_child("item");
140         local id = item and item.attr.id
141         local reply, notifier;
142         if notify then
143                 notifier = st.stanza("retract", { id = id });
144         end
145         local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
146         if ok then
147                 reply = st.reply(stanza);
148         else
149                 reply = pubsub_error_reply(stanza, ret);
150         end
151         return origin.send(reply);
152 end
153
154 function simple_broadcast(node, jids, item)
155         item = st.clone(item);
156         item.attr.xmlns = nil; -- Clear the pubsub namespace
157         local message = st.message({ from = module.host, type = "headline" })
158                 :tag("event", { xmlns = xmlns_pubsub_event })
159                         :tag("items", { node = node })
160                                 :add_child(item);
161         for jid in pairs(jids) do
162                 module:log("debug", "Sending notification to %s", jid);
163                 message.attr.to = jid;
164                 core_post_stanza(hosts[module.host], message);
165         end
166 end
167
168 module:hook("iq/host/http://jabber.org/protocol/pubsub:pubsub", handle_pubsub_iq);
169
170 local disco_info = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" })
171         :tag("identity", { category = "pubsub", type = "service" }):up()
172         :tag("feature", { var = "http://jabber.org/protocol/pubsub" }):up();
173
174 module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function (event)
175         event.origin.send(st.reply(event.stanza):add_child(disco_info));
176         return true;
177 end);
178
179 module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function (event)
180         local ok, ret = service:get_nodes(event.stanza.attr.from);
181         if not ok then
182                 event.origin.send(pubsub_error_reply(stanza, ret));
183         else
184                 local reply = st.reply(event.stanza)
185                         :tag("query", { xmlns = "http://jabber.org/protocol/disco#items" });
186                 for node, node_obj in pairs(ret) do
187                         reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
188                 end
189                 event.origin.send(reply);
190         end
191         return true;
192 end);
193
194 service = pubsub.new({
195         broadcaster = simple_broadcast
196 });
197 module.environment.service = service;
198
199 function module.save()
200         return { service = service };
201 end
202
203 function module.restore(data)
204         service = data.service;
205         module.environment.service = service;
206 end