Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

405 lines (355 sloc) 11.426 kb
local pubsub = require "util.pubsub";
local st = require "util.stanza";
local jid_bare = require "util.jid".bare;
local uuid_generate = require "util.uuid".generate;
local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false);
local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false);
local pubsub_disco_name = module:get_option("name");
if type(pubsub_disco_name) ~= "string" then pubsub_disco_name = "Prosody PubSub Service"; end
local service;
local handlers = {};
function handle_pubsub_iq(event)
local origin, stanza = event.origin, event.stanza;
local pubsub = stanza.tags[1];
local action = pubsub.tags[1];
local handler = handlers[stanza.attr.type.."_"..action.name];
if handler then
handler(origin, stanza, action);
return true;
end
end
local pubsub_errors = {
["conflict"] = { "cancel", "conflict" };
["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
["item-not-found"] = { "cancel", "item-not-found" };
["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
["forbidden"] = { "cancel", "forbidden" };
};
function pubsub_error_reply(stanza, error)
local e = pubsub_errors[error];
local reply = st.error_reply(stanza, unpack(e, 1, 3));
if e[4] then
reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
end
return reply;
end
function handlers.get_items(origin, stanza, items)
local node = items.attr.node;
local item = items:get_child("item");
local id = item and item.attr.id;
local ok, results = service:get_items(node, stanza.attr.from, id);
if not ok then
return origin.send(pubsub_error_reply(stanza, results));
end
local data = st.stanza("items", { node = node });
for _, entry in pairs(results) do
data:add_child(entry);
end
local reply;
if data then
reply = st.reply(stanza)
:tag("pubsub", { xmlns = xmlns_pubsub })
:add_child(data);
else
reply = pubsub_error_reply(stanza, "item-not-found");
end
return origin.send(reply);
end
function handlers.get_subscriptions(origin, stanza, subscriptions)
local node = subscriptions.attr.node;
local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
if not ok then
return origin.send(pubsub_error_reply(stanza, ret));
end
local reply = st.reply(stanza)
:tag("pubsub", { xmlns = xmlns_pubsub })
:tag("subscriptions");
for _, sub in ipairs(ret) do
reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
end
return origin.send(reply);
end
function handlers.set_create(origin, stanza, create)
local node = create.attr.node;
local ok, ret, reply;
if node then
ok, ret = service:create(node, stanza.attr.from);
if ok then
reply = st.reply(stanza);
else
reply = pubsub_error_reply(stanza, ret);
end
else
repeat
node = uuid_generate();
ok, ret = service:create(node, stanza.attr.from);
until ok or ret ~= "conflict";
if ok then
reply = st.reply(stanza)
:tag("pubsub", { xmlns = xmlns_pubsub })
:tag("create", { node = node });
else
reply = pubsub_error_reply(stanza, ret);
end
end
return origin.send(reply);
end
function handlers.set_subscribe(origin, stanza, subscribe)
local node, jid = subscribe.attr.node, subscribe.attr.jid;
local options_tag, options = stanza.tags[1]:get_child("options"), nil;
if options_tag then
options = options_form:data(options_tag.tags[1]);
end
local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
local reply;
if ok then
reply = st.reply(stanza)
:tag("pubsub", { xmlns = xmlns_pubsub })
:tag("subscription", {
node = node,
jid = jid,
subscription = "subscribed"
}):up();
if options_tag then
reply:add_child(options_tag);
end
else
reply = pubsub_error_reply(stanza, ret);
end
origin.send(reply);
if ok then
-- Send all current items
local ok, items = service:get_items(node, stanza.attr.from);
if items then
local jids = { [jid] = options or true };
for id, item in pairs(items) do
service.config.broadcaster(node, jids, item);
end
end
end
end
function handlers.set_unsubscribe(origin, stanza, unsubscribe)
local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
local reply;
if ok then
reply = st.reply(stanza);
else
reply = pubsub_error_reply(stanza, ret);
end
return origin.send(reply);
end
function handlers.set_publish(origin, stanza, publish)
local node = publish.attr.node;
local item = publish:get_child("item");
local id = (item and item.attr.id) or uuid_generate();
local ok, ret = service:publish(node, stanza.attr.from, id, item);
local reply;
if ok then
reply = st.reply(stanza)
:tag("pubsub", { xmlns = xmlns_pubsub })
:tag("publish", { node = node })
:tag("item", { id = id });
else
reply = pubsub_error_reply(stanza, ret);
end
return origin.send(reply);
end
function handlers.set_retract(origin, stanza, retract)
local node, notify = retract.attr.node, retract.attr.notify;
notify = (notify == "1") or (notify == "true");
local item = retract:get_child("item");
local id = item and item.attr.id
local reply, notifier;
if notify then
notifier = st.stanza("retract", { id = id });
end
local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
if ok then
reply = st.reply(stanza);
else
reply = pubsub_error_reply(stanza, ret);
end
return origin.send(reply);
end
function simple_broadcast(node, jids, item)
item = st.clone(item);
item.attr.xmlns = nil; -- Clear the pubsub namespace
local message = st.message({ from = module.host, type = "headline" })
:tag("event", { xmlns = xmlns_pubsub_event })
:tag("items", { node = node })
:add_child(item);
for jid in pairs(jids) do
module:log("debug", "Sending notification to %s", jid);
message.attr.to = jid;
module:send(message);
end
end
module:hook("iq/host/http://jabber.org/protocol/pubsub:pubsub", handle_pubsub_iq);
local disco_info;
local feature_map = {
create = { "create-nodes", autocreate_on_publish and "instant-nodes", "item-ids" };
retract = { "delete-items", "retract-items" };
publish = { "publish" };
get_items = { "retrieve-items" };
add_subscription = { "subscribe" };
get_subscriptions = { "retrieve-subscriptions" };
};
local function add_disco_features_from_service(disco, service)
for method, features in pairs(feature_map) do
if service[method] then
for _, feature in ipairs(features) do
if feature then
disco:tag("feature", { var = xmlns_pubsub.."#"..feature }):up();
end
end
end
end
for affiliation in pairs(service.config.capabilities) do
if affiliation ~= "none" and affiliation ~= "owner" then
disco:tag("feature", { var = xmlns_pubsub.."#"..affiliation.."-affiliation" }):up();
end
end
end
local function build_disco_info(service)
local disco_info = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" })
:tag("identity", { category = "pubsub", type = "service", name = pubsub_disco_name }):up()
:tag("feature", { var = "http://jabber.org/protocol/pubsub" }):up();
add_disco_features_from_service(disco_info, service);
return disco_info;
end
module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function (event)
local origin, stanza = event.origin, event.stanza;
local node = stanza.tags[1].attr.node;
if not node then
return origin.send(st.reply(stanza):add_child(disco_info));
else
local ok, ret = service:get_nodes(stanza.attr.from);
if ok and not ret[node] then
ok, ret = false, "item-not-found";
end
if not ok then
return origin.send(pubsub_error_reply(stanza, ret));
end
local reply = st.reply(stanza)
:tag("query", { xmlns = "http://jabber.org/protocol/disco#info", node = node })
:tag("identity", { category = "pubsub", type = "leaf" });
return origin.send(reply);
end
end);
local function handle_disco_items_on_node(event)
local stanza, origin = event.stanza, event.origin;
local query = stanza.tags[1];
local node = query.attr.node;
local ok, ret = service:get_items(node, stanza.attr.from);
if not ok then
return origin.send(pubsub_error_reply(stanza, ret));
end
local reply = st.reply(stanza)
:tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = node });
for id, item in pairs(ret) do
reply:tag("item", { jid = module.host, name = id }):up();
end
return origin.send(reply);
end
module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function (event)
if event.stanza.tags[1].attr.node then
return handle_disco_items_on_node(event);
end
local ok, ret = service:get_nodes(event.stanza.attr.from);
if not ok then
event.origin.send(pubsub_error_reply(stanza, ret));
else
local reply = st.reply(event.stanza)
:tag("query", { xmlns = "http://jabber.org/protocol/disco#items" });
for node, node_obj in pairs(ret) do
reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
end
event.origin.send(reply);
end
return true;
end);
local admin_aff = module:get_option_string("default_admin_affiliation", "owner");
local function get_affiliation(jid)
local bare_jid = jid_bare(jid);
if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then
return admin_aff;
end
end
function set_service(new_service)
service = new_service;
module.environment.service = service;
disco_info = build_disco_info(service);
end
function module.save()
return { service = service };
end
function module.restore(data)
set_service(data.service);
end
set_service(pubsub.new({
capabilities = {
none = {
create = false;
publish = false;
retract = false;
get_nodes = true;
subscribe = true;
unsubscribe = true;
get_subscription = true;
get_subscriptions = true;
get_items = true;
subscribe_other = false;
unsubscribe_other = false;
get_subscription_other = false;
get_subscriptions_other = false;
be_subscribed = true;
be_unsubscribed = true;
set_affiliation = false;
};
publisher = {
create = false;
publish = true;
retract = true;
get_nodes = true;
subscribe = true;
unsubscribe = true;
get_subscription = true;
get_subscriptions = true;
get_items = true;
subscribe_other = false;
unsubscribe_other = false;
get_subscription_other = false;
get_subscriptions_other = false;
be_subscribed = true;
be_unsubscribed = true;
set_affiliation = false;
};
owner = {
create = true;
publish = true;
retract = true;
get_nodes = true;
subscribe = true;
unsubscribe = true;
get_subscription = true;
get_subscriptions = true;
get_items = true;
subscribe_other = true;
unsubscribe_other = true;
get_subscription_other = true;
get_subscriptions_other = true;
be_subscribed = true;
be_unsubscribed = true;
set_affiliation = true;
};
};
autocreate_on_publish = autocreate_on_publish;
autocreate_on_subscribe = autocreate_on_subscribe;
broadcaster = simple_broadcast;
get_affiliation = get_affiliation;
normalize_jid = jid_bare;
}));
Jump to Line
Something went wrong with that request. Please try again.