# HG changeset patch # User Emmanuel Gil Peyrot # Date 1288566147 -3600 # Node ID b80ab94da4478a3a9eb30a982a3456f291f24306 # Parent 5fc4ee90c1bcd9c21f6f207632db1e75417ba21f Add new modules files. diff --git a/modules.js b/modules.js new file mode 100644 --- /dev/null +++ b/modules.js @@ -0,0 +1,14 @@ +var modules = exports; + +var files = require('fs').readdirSync('modules'); +var regex = /^mod_.*\.js/; + +for (var i in files) { + var file = files[i]; + if (!regex.test(file)) + continue; + + var module = require('./modules/' + files[i]); + for (var j in module) + modules[j] = module[j]; +} diff --git a/modules/mod_adhoc.js b/modules/mod_adhoc.js new file mode 100644 --- /dev/null +++ b/modules/mod_adhoc.js @@ -0,0 +1,38 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var forms = require('../forms'); +var errors = require('../errors'); +var makeError = errors.makeError; +var toBareJID = require('../util').toBareJID; +var NS = require('../namespaces'); + +// XEP-0050: Ad-Hoc Commands +exports.configureSub = { + type: 'set', + child: 'command', + ns: NS.COMMANDS, + func: function(response, stanza, request, to) { + var action = request.getAttribute('action'); + if (action != 'execute') + return makeError(response, errors.bad_request.n); + + var node = request.getAttribute('node'); + if (node == 'ping') { + var cmd = xmpp.stanza('command', {xmlns: NS.COMMANDS, + // sessionid: 'list:20020923T213616Z-700', + node: node, + 'status': 'completed'}) + .c('note', {type: 'info'}).t('pong'); + response.cnode(cmd); + } else if (node == 'reload') { + storage.load(); + response.c('command', {xmlns: NS.COMMANDS, + node: node, + 'status': 'completed'}) + .c('note', {type: 'info'}).t('The server has correctly reloaded.'); + } else + return makeError(response, errors.bad_request.n); + + return response; + } +} diff --git a/modules/mod_configure.js b/modules/mod_configure.js new file mode 100644 --- /dev/null +++ b/modules/mod_configure.js @@ -0,0 +1,103 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var forms = require('../forms'); +var errors = require('../errors'); +var makeError = errors.makeError; +var toBareJID = require('../util').toBareJID; +var NS = require('../namespaces'); + +// SECTION 8.2: Configure a Node +exports.getConfigure = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'configure', + func: function(response, stanza, request, to) { + if (!config.enabled('config-node')) + return makeError(response, errors.owner.configure.node_configuration_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only') + return makeError(response, errors.pub.publish.insufficient_privileges.n); + + response.c('pubsub', {xmlns: NS.PUBSUB_OWNER}); + response.c('configure', {node: nodeID}); + + var form = forms.build('form', 'node_config', 'default', true); + response.cnode(form); + + return response; + } +} + +// SECTION 8.3: Request Default Node Configuration Options +exports.default = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'default', + func: function(response) { + if (!config.enabled('config-node')) + return makeError(response, errors.owner.default_options.node_configuration_not_supported.n); + + response.c('pubsub', {xmlns: NS.PUBSUB_OWNER}); + response.c('default'); + + var form = forms.build('node_config', service_configuration.node_config, null, true); + response.cnode(form); + + return response; + } +} + +// SECTION 8.2.4: Form Submission +exports.setConfigure = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'configure', + func: function(response, stanza, request, to) { + if (!config.enabled('config-node')) + return makeError(response, errors.owner.configure.node_configuration_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID) + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only') + return makeError(response, errors.forbidden.n); + + var x = request.getChild('x', 'jabber:x:data'); + if (!x) + return makeError(response, errors.bad_request.n); + + var type = x.getAttribute('type'); + if (type == 'cancel') { + conn.send(response); + return; + } + if (type != 'submit') + return makeError(response, errors.bad_request.n); + + var form = forms.parse(x, true); + if (typeof form == 'number') + return makeError(response, form); + + var conf = form; + + var set = storage.configure(nodeID, conf); + if (typeof set == 'number') + return makeError(response, set); + + return response; + } +} diff --git a/modules/mod_disco.js b/modules/mod_disco.js new file mode 100644 --- /dev/null +++ b/modules/mod_disco.js @@ -0,0 +1,113 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var forms = require('../forms'); +var errors = require('../errors'); +var makeError = errors.makeError; +var NS = require('../namespaces'); + +// SECTION 5.1: Discover Features +exports.disco_info = { + type: 'get', + child: 'query', + ns: NS.DISCO_INFO, + func: function(response, stanza, request) { + var nodeID = request.getAttribute('node'); + + // SECTION 5.3: Discover Node Information + if (nodeID && nodeID != '') { + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var conf = storage.getConfiguration(nodeID); + if (typeof conf == 'number') + return makeError(response, conf); + + var type = 'leaf' + if (conf['pubsub#node_type']) + type = conf['pubsub#node_type']; + + response.c('query', {xmlns: NS.DISCO_INFO, node: nodeID}) + .c('identity', {category: 'pubsub', type: type}).up() + .c('feature', {'var': NS.PUBSUB}).up(); + + // SECTION 5.4 + if (config.enabled('meta-data')) { + var x = forms.build('result', 'node_metadata', storage.getMetadata(nodeID), true); + if (x) + response.cnode(x); + } + + // SECTION 5.1: Discover Features + } else { + response.c('query', {xmlns: NS.DISCO_INFO}) + .c('identity', {category: 'pubsub', type: 'service', name: 'PubSub JavaScript Server'}).up() + .c('feature', {'var': NS.DISCO_INFO}).up() + .c('feature', {'var': NS.DISCO_ITEMS}).up() + .c('feature', {'var': NS.PUBSUB}).up() + .c('feature', {'var': 'jabber:iq:version'}).up() + .c('feature', {'var': NS.COMMANDS}).up(); + + for (var i in config.activated) + if (typeof i == 'string') + response.c('feature', {'var': 'http://jabber.org/protocol/pubsub#' + config.activated[i]}).up(); + } + + return response; + } +} + +// SECTION 5.2: Discover Nodes +exports.disco_items = { + type: 'get', + child: 'query', + ns: NS.DISCO_ITEMS, + func: function(response, stanza, request) { + var children; + var nodeID = request.getAttribute('node'); + if (nodeID && nodeID != '') { + if (nodeID == NS.COMMANDS) { + // XEP-0050: Ad-Hoc Commands + + response.c('query', {xmlns: NS.DISCO_ITEMS, node: nodeID}) + .c('item', {jid: config.jid, node: 'ping', name: 'Ping'}).up() + .c('item', {jid: config.jid, node: 'reload', name: 'Reload'}).up(); + + return response; + } else { + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + response.c('query', {xmlns: NS.DISCO_ITEMS, node: nodeID}); + + children = storage.getChildren(nodeID); + if (typeof children == 'number') + return makeError(response, children); + } + } else { + response.c('query', {xmlns: NS.DISCO_ITEMS}); + + children = storage.getChildren(); + if (typeof children == 'number') + return makeError(response, children); + } + + for (var i in children) { + var attr = {jid: config.jid}; + if (children[i] == 'node') { + if (config.enabled('meta-data')) { + var metadata = storage.getMetadata(i); + if (metadata['pubsub#title']) + attr.name = metadata['pubsub#title']; + } + attr.node = i; + + // SECTION 5.5: Discover Items for a Node + } else + attr.name = i; + + response.c('item', attr).up(); + } + + return response; + } +} diff --git a/modules/mod_manage.js b/modules/mod_manage.js new file mode 100644 --- /dev/null +++ b/modules/mod_manage.js @@ -0,0 +1,162 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var errors = require('../errors'); +var makeError = errors.makeError; +var toBareJID = require('../util').toBareJID; +var NS = require('../namespaces'); + +// SECTION 8.8.1: Retrieve Subscriptions List +exports.manageRetrieveSub = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'subscriptions', + func: function(response, stanza, request, to) { + if (!config.enabled('manage-subscriptions')) + return makeError(response, errors.owner.manage_subscriptions.not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner') + return makeError(response, errors.forbidden.n); + + response.c('pubsub', {xmlns: NS.PUBSUB_OWNER}); + response.c('subscriptions', {node: nodeID}); + + var subs = storage.getSubscriptionsFromNodeID(nodeID) + for (var jid in subs) + response.c('subscription', {jid: jid, subscription: subs[jid].type, subid: subs[jid].subid}).up(); + + return response; + } +} + +// SECTION 8.9.1: Retrieve Affiliations List +exports.manageRetrieveAff = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'affiliations', + func: function(response, stanza, request, to) { + if (!config.enabled('modify-affiliations')) + return makeError(response, errors.owner.manage_affiliations.not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affils = storage.getAffiliationsFromNodeID(nodeID); + var affil = affils[toBareJID(to)]; + if (affil != 'super-owner' && affil != 'owner') + return makeError(response, errors.owner.manage_affiliations.retrieve_list.entity_is_not_an_owner.n); + + response.c('pubsub', {xmlns: NS.PUBSUB_OWNER}); + response.c('affiliations', {node: nodeID}); + + for (var jid in affils) + response.c('affiliation', {jid: jid, affiliation: affils[jid]}).up(); + + return response; + } +} + +// SECTION 8.8.2: Modify Subscriptions +exports.modifySub = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'subscriptions', + func: function(response, stanza, request, to) { + if (!config.enabled('manage-subscriptions')) + return makeError(response, errors.owner.manage_subscriptions.not_supported.n); //XXX + + var nodeID = request.getAttribute('node'); + if (!nodeID) + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner') + return makeError(response, errors.forbidden.n); + + var e = false; + var tags2 = []; + for (i in request.tags) { + var tag = request.tags[i]; + var jid = tag.getAttribute('jid'); + var sub = tag.getAttribute('subscription'); + + if (sub == 'none' || sub == 'pending' || sub == 'subscribed' || sub == 'unconfigured') { + var set = storage.subscribe(nodeID, jid, sub); + + if (typeof set == 'number') { + e = true; + tags2.push(tag); + } else { + // SECTION 8.8.4 + notifs.send(jid, 'subscription', nodeID, {jid: jid, subscription: sub}); + } + } else { + e = true; + tags2.push(tag); + } + } + + request.tags = tags2; + + if (e) + return makeError(response, errors.owner.manage_subscriptions.modify.multiple_simultaneous_modifications.n, pubsub); + + return response; + } +} + +// SECTION 8.9.2: Modify Affiliation +exports.modifyAff = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'affiliations', + func: function(response, stanza, request, to) { + if (!config.enabled('modify-affiliations')) + return makeError(response, errors.owner.manage_affiliations.not_supported.n); //XXX + + var nodeID = request.getAttribute('node'); + if (!nodeID) + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner') + return makeError(response, errors.forbidden.n); + + var e = false; + for (i in request.children) { + var jid = request.children[i].getAttribute('jid'); + var affiliation = request.children[i].getAttribute('affiliation'); + + var set = storage.setAffiliation(nodeID, jid, affiliation); + if (typeof set == 'number') + e = true; + else { + // SECTION 8.9.4 + notifs.send(jid, 'affiliations', nodeID, {jid: jid, affiliation: affiliation}); + request.children.splice(i, 1); + } + } + + if (e) + return makeError(response, errors.owner.manage_affiliations.modify.multiple_simultaneous_modifications.n, pubsub); + + return response; + } +} diff --git a/modules/mod_options.js b/modules/mod_options.js new file mode 100644 --- /dev/null +++ b/modules/mod_options.js @@ -0,0 +1,107 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var forms = require('../forms'); +var errors = require('../errors'); +var makeError = errors.makeError; +var toBareJID = require('../util').toBareJID; +var NS = require('../namespaces'); + +// SECTION 6.3.2: Configure Subscription Options (Request) +exports.configureSub = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'options', + func: function(response, stanza, request, to) { + if (!config.enabled('subscription-options')) + return makeError(response, errors.sub.configure.subscription_options_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var jid = request.getAttribute('jid'); + if (!jid) + return makeError(response, errors.sub.configure.subscriber_jid_required.n); + if (toBareJID(jid) != toBareJID(to)) + return makeError(response, errors.sub.configure.insufficient_privileges.n); + + var subs = storage.getSubscription(jid, nodeID); + if (!subs.subid) // FIXME: better test for empty object. + return makeError(response, errors.sub.configure.no_such_subscriber.n); + + response.c('pubsub', {xmlns: NS.PUBSUB}); + response.c('options', {node: nodeID, jid: jid}); + + var form = forms.build('form', 'subscribe_options', subs.options, true); + response.cnode(form); + + return response; + } +} + +// SECTIONS 6.3.5: Form Submission +exports.configureSub = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'options', + func: function(response, stanza, request, to) { + if (!config.enabled('subscription-options')) + return makeError(response, errors.sub.subscribe.not_supported.n); + + var nodeID = unsubscribe.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var jid = unsubscribe.getAttribute('jid'); + if (!jid || toBareJID(jid) != toBareJID(to)) + return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n); + + var x = request.getChild('x', 'jabber:x:data'); + if (!x || x.getAttribute(type) != 'submit') + return makeError(response, errors.bad_request); + + var form = forms.parse(x, true); + if (typeof form == 'number') + return makeError(response, form); + + var set = storage.configureSubscription(nodeID, jid, form); + if (typeof form == 'number') + return makeError(response, form); + + return response; + } +} + +// SECTION 6.4: Request Default Subscription Configuration Options +exports.defaultSub = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'default', + func: function(response, stanza, request) { + if (!config.enabled('retrieve-default-sub')) + return makeError(response, errors.sub.default_options.default_subscription_configuration_retrieval_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (nodeID && !storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + response.c('pubsub', {xmlns: NS.PUBSUB}); + var s; + if (nodeID) + response.c('default', {node: nodeID}); + else + response.c('default'); + + var form = forms.build('form', 'subscribe_options', 'default', false); + response.cnode(form); + + return response; + } +} diff --git a/modules/mod_owner.js b/modules/mod_owner.js new file mode 100644 --- /dev/null +++ b/modules/mod_owner.js @@ -0,0 +1,121 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var forms = require('../forms'); +var notifs = require('../notifs'); +var errors = require('../errors'); +var makeError = errors.makeError; +var toBareJID = require('../util').toBareJID; +var NS = require('../namespaces'); + +// SECTION 8.1: Create a Node +exports.getConfigure = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'create', + func: function(response, stanza, request, to) { + if (!config.enabled('create-nodes')) + return makeError(response, errors.owner.create.node_creation_not_supported.n); + + var instant = false; + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') { + if (!config.enabled('instant-nodes')) + return makeError(response, errors.owner.create.instant_nodes_not_supported.n); + nodeID = utils.makeRandomId(); + instant = true; + } + if (storage.existsNode(nodeID)) + return makeError(response, errors.owner.create.nodeid_already_exists.n); + + var bare = toBareJID(to); + var right = false; + + // Check for super-owner + for (var i in config.owner) + if (config.owner[i] == bare) + right = true; + + // Check for authorized user + for (var i in config.allowCreateNode) + if (config.allowCreateNode[i].exec(bare)) + right = true; + + if (!right) + return makeError(response, errors.forbidden.n); + + var r = storage.createNode(nodeID); + if (typeof r == 'number') + return makeError(response, r); + + if (instant) + response.c('pubsub', {xmlns: NS.PUBSUB}) + .c('create', {node: nodeID}); + + return response; + } +} + +// SECTION 8.4: Delete a Node +exports['delete'] = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'delete', + func: function(response, stanza, request, to) { + if (!config.enabled('delete-nodes')) + return makeError(response, errors.feature_not_implemented.n); //XXX + + var nodeID = request.getAttribute('node'); + if (!nodeID) + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner') + return makeError(response, errors.forbidden.n); + + var notifs = storage.deleteNode(nodeID); + if (typeof notifs == 'number') + return makeError(response, r); + + notifs.send(notifs, 'delete', nodeID); + + return response; + } +} + +// SECTION 8.5: Purge All Node Items +exports.purge = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB_OWNER, + pschild: 'purge', + func: function(response, stanza, request, to) { + if (!config.enabled('purge-nodes')) + return makeError(response, errors.owner.purge.node_purging_not_supported.n); //XXX + + var nodeID = request.getAttribute('node'); + if (!nodeID) + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner') + return makeError(response, errors.forbidden.n); + + if (!config.enabled('persistent-items')) //FIXME: autre condition, supporté par le node + return makeError(response, errors.owner.purge.node_does_not_persist_items.n); + + var notifs = storage.purgeNode(nodeID); + if (typeof notifs == 'number') + return makeError(response, r); + + notifs.send(notifs, 'purge', nodeID); + + return response; + } +} diff --git a/modules/mod_publish.js b/modules/mod_publish.js new file mode 100644 --- /dev/null +++ b/modules/mod_publish.js @@ -0,0 +1,130 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var toBareJID = require('../util').toBareJID; +var NS = require('../namespaces'); + +// SECTION 7.1: Publish an Item to a Node +exports.retrieveSub = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'publish', + func: function(response, stanza, request, to) { + if (!config.enabled('publish')) + return makeError(response, errors.pub.publish.item_publication_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + + var autocreate = false; + if (!storage.existsNode(nodeID)) { + if (config.enabled('auto-create')) + autocreate = true; + else + return makeError(response, errors.node_does_not_exist.n); + } + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (typeof affil == 'number' && affil != errors.node_does_not_exist.n) + return makeError(response, affil); + if (!autocreate && affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'publish-only') + return makeError(response, errors.forbidden.n); + + var item = request.getChild('item'); + var itemID = item.getAttribute('id'); + if (!config.enabled('item-ids') && itemID) + return makeError(response, errors.itemid_required.n); + itemID = itemID? itemID: utils.makeRandomId(); + + if (item.tags.length != 1) + return makeError(response, errors.pub.publish.bad_payload.n); + + var conf = storage.getConfiguration(nodeID); + var publishOptions = pubsub.getChild('publish-options'); + if (publishOptions && config.enabled('publish-options')) { + var x = publishOptions.getChild('x', 'jabber:x:data'); + if (!x || x.getAttribute('type') != 'submit') + return makeError(response, errors.bad_request.n); + + var form = forms.parse(x, true); + if (form.access_model != conf['pubsub#access_model'] && !autocreate) + return makeError(response, errors.pub.configuration.precondition.n); + } + + if (!config.enabled('persistent-items')) { + var notifs = storage.purgeNode(nodeID); + if (typeof notifs == 'number') + return makeError(response, r); + } + + if (autocreate) { + if (!form) + form = {}; + form['pubsub#creator'] = toBareJID(to); + + var r = storage.createNode(nodeID, form); + if (typeof r == 'number') + return makeError(response, r); + } + + var content = item.getChild(); + + subscribers = storage.setItem(nodeID, itemID, content); + if (typeof subscribers == 'number') + return makeError(response, subscribers); + + var attrs = {}; + if (content) + attrs[itemID] = {content: content}; + else + attrs[itemID] = {}; + notifs.send(subscribers, 'items', nodeID, attrs); + + response.c('pubsub', {xmlns: NS.PUBSUB}) + .c('publish', {node: nodeID}) + .c('item', {id: itemID}); + + return response; + } +} + +// SECTION 7.2: Delete an Item from a Node +exports.retrieveSub = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'retract', + func: function(response, stanza, request, to) { + if (!config.enabled('retract-items')) + return makeError(response, errors.pub.retract.item_deletion_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var item = request.getChild('item'); + if (!item) + return makeError(response, errors.pub.retract.item_or_itemid_required.n); + + var itemID = item.getAttribute('id') + if (!itemID || itemID == '') + return makeError(response, errors.pub.retract.item_or_itemid_required.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only') + return makeError(response, errors.forbidden.n); + + var subscribers = storage.deleteItem(nodeID, itemID); + if (typeof subscribers == 'number') + return makeError(response, subscribers); + + var attrs = {}; + attrs[itemID] = {}; + notifs.send(subscribers, 'items', nodeID, attrs, 'retract') + + return response; + } +} diff --git a/modules/mod_retrieve.js b/modules/mod_retrieve.js new file mode 100644 --- /dev/null +++ b/modules/mod_retrieve.js @@ -0,0 +1,132 @@ +var config = require('../configuration'); +var storage = require('../storage'); +var errors = require('../errors'); +var makeError = errors.makeError; +var toBareJID = require('../util').toBareJID; +var NS = require('../namespaces'); + +// SECTION 5.6: Retrieve Subscriptions +exports.retrieveSubscriptions = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'subscriptions', + func: function(response, stanza, request) { + if (!config.enabled('retrieve-subscriptions')) + return makeError(response, errors.subscriptions_retrieval_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (nodeID && nodeID != '') { + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + var subs = storage.getSubscription(toBareJID(to), node); + } else + var subs = storage.getSubscription(toBareJID(to)); + + response.c('pubsub', {xmlns: NS.PUBSUB}); + response.c('subscriptions'); + + for (i in subs) + response.c('subscription', {node: i, jid: to, subscription: subs[i].type, subid: subs[i].subid}).up(); + + return response; + } +} + +// SECTION 5.7: Retrieve Affiliations +exports.retrieveAffiliations = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'affiliations', + func: function(response, stanza, request) { + if (!config.enabled('retrieve-affiliations')) + return makeError(response, errors.affiliations_retrieval_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (nodeID && nodeID != '') { + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + var affils = {}; + affils[nodeID] = storage.getAffiliation(toBareJID(to), nodeID); + } else + var affils = storage.getAffiliationsFromJID(toBareJID(to)); + + response.c('pubsub', {xmlns: NS.PUBSUB}); + response.c('affiliations'); + + for (i in affils) + response.c('affiliation', {node: i, affiliation: affils[i]}).up(); + + return response; + } +} + +// SECTION 6.5: Retrieve Items from a Node +exports.retrieveItems = { + type: 'get', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'items', + func: function(response, stanza, request) { + if (!config.enabled('retrieve-items')) + return makeError(response, errors.sub.default_options.node_configuration_not_supported.n); + + var nodeID = request.getAttribute('node'); + if (!nodeID || nodeID == '') + return makeError(response, errors.nodeid_required.n); + if (!storage.existsNode(nodeID)) + return makeError(response, errors.node_does_not_exist.n); + + var configuration = storage.getConfiguration(nodeID); + if (configuration['pubsub#access_model'] == 'whitelist') { + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member') + return makeError(response, errors.pub.publish.insufficient_privileges.n); + } + + var item = []; + for (var i=0; i