Mercurial > psgxs
changeset 24:b80ab94da447
Add new modules files.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Mon, 01 Nov 2010 00:02:27 +0100 |
parents | 5fc4ee90c1bc |
children | c774f2ffb271 |
files | modules.js modules/mod_adhoc.js modules/mod_configure.js modules/mod_disco.js modules/mod_manage.js modules/mod_options.js modules/mod_owner.js modules/mod_publish.js modules/mod_retrieve.js modules/mod_subscribe.js modules/mod_version.js namespaces.js notifs.js |
diffstat | 13 files changed, 1248 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
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]; +}
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; + } +}
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; + } +}
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; + } +}
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; + } +}
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; + } +}
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; + } +}
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; + } +}
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<request.children.length; i++) { + var j = request.children[i]; + if (j.name == 'item' && j.attr['id'] && j.attr['id'] != '') + item.push(j.attr['id']); + } + + response.c('pubsub', {xmlns: NS.PUBSUB}); + + var max_items = request.getAttribute('max_items'); + if (max_items) + max_items = Number (max_items); + + if (item.length) { + response.c('items', {node: nodeID}); + + for (var i=0; i<item.length; i++) { + var j = storage.getItem(nodeID, item[i]); + if (typeof j == 'number') + return makeError(response, j); + if (j == errors.success) + continue; + + response.c('item', {id: item[i]}) + response.cnode(j).up().up(); + } + } else { + response.c('items', {node: nodeID}); + + var j; + if (max_items) + j = storage.getLastItem(nodeID, max_items); + else + j = storage.getItems(nodeID); + if (typeof j == 'number') + return makeError(response, j); + + var k = 0; + for (var i in j) + response.c('item', {id: i}).t(j[i].content).up(); + } + + return response; + } +}
new file mode 100644 --- /dev/null +++ b/modules/mod_subscribe.js @@ -0,0 +1,128 @@ +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 6.1: Subscribe to a Node +exports.subscribe = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'subscribe', + func: function(response, stanza, request, to) { + if (!config.enabled('subscribe')) + return makeError(response, errors.sub.subscribe.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#subscribe']) + return makeError(response, errors.sub.subscribe.not_supported.n); + + var affil = storage.getAffiliation(toBareJID(to), nodeID); + if (affil == 'publish-only' || affil == 'outcast') + return makeError(response, errors.pub.publish.insufficient_privileges.n); + + var jid = request.getAttribute('jid'); + if (!jid || toBareJID(jid) != toBareJID(to)) + return makeError(response, errors.sub.subscribe.jids_do_not_match.n); + + /* + // SECTION 6.3.7 + var options = pubsub.getChild('options'); + if (options && config.enabled('subscription-options')) { + if (options.getAttribute('node') || options.getAttribute('jid')) + return makeError(response, errors.bad_request.n); + + var x = options.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 (typeof form == 'number') + return makeError(response, form); + + var conf = form; + }*/ + + var subID; + if (configuration['pubsub#access_model'] == 'open') { + subID = storage.subscribe(nodeID, jid, 'subscribe'); + if (typeof subID == 'number') + return makeError(response, subID); + } else if (configuration['pubsub#access_model'] == 'authorize') { + subID = storage.subscribe(nodeID, jid, 'pending'); + if (typeof subID == 'number') + return makeError(response, subID); + + var affiliates = storage.getAffiliationsFromNodeID(nodeID); + var form = forms.build('form', 'subscribe_authorization', {allow: false, node: nodeID, subscriber_jid: jid}, true); //168 + + for (var i in affiliates) { + if (affiliates[i] == 'super-owner' || affiliates[i] == 'owner') { + var message = xmpp.message({to: i}).cnode(form); + conn.send(message); // FIXME: impossible à faire d’ici + } + } + } else if (configuration['pubsub#access_model'] == 'whitelist') { + var affil = storage.getAffiliation(jid, nodeID); + if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member') + return makeError(response, errors.sub.subscribe.not_on_whitelist.n); + + subID = storage.subscribe(nodeID, jid); + if (typeof subID == 'number') + return makeError(response, subID); + } + + response.c('pubsub', {xmlns: NS.PUBSUB}) + .c('subscription', {node: nodeID, jid: jid, subid: subID.subid, subscription: subID.type}); + + if (config.enabled('last-published')) { + var last = storage.getLastItem(nodeID); + if (typeof last != 'number') { + var item = storage.getItem(nodeID, last); + if (typeof item != 'number') { + var attr = {}; + attr[last] = {content: item}; + notifs.send(jid, 'items', nodeID, attr); + } + } + } + + return response; + } +} + +// SECTION 6.2: Unsubscribe from a Node +exports.unsubscribe = { + type: 'set', + child: 'pubsub', + ns: NS.PUBSUB, + pschild: 'unsubscribe', + func: function(response, stanza, request) { + if (!config.enabled('subscribe')) + return makeError(response, errors.sub.subscribe.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 || toBareJID(jid) != toBareJID(to)) + return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n); + + var subID = storage.subscribe(nodeID, jid, 'none'); + if (typeof subID == 'number') + return makeError(response, subID); + + return response; + } +}
new file mode 100644 --- /dev/null +++ b/modules/mod_version.js @@ -0,0 +1,17 @@ +// XEP-0092: Software Version + +var config = require('../configuration'); +var NS = require('../namespaces'); + +exports.version = { + type: 'get', + child: 'query', + ns: NS.VERSION, + func: function(response) { + response.c('query', {xmlns: NS.VERSION}) + .s('name').t('PSĜS') + .s('version').t(config.version) + .s('os').t(config.os); + return response; + } +}
new file mode 100644 --- /dev/null +++ b/namespaces.js @@ -0,0 +1,17 @@ +var NS = exports; + +NS.DISCO_INFO = 'http://jabber.org/protocol/disco#info'; +NS.DISCO_ITEMS = 'http://jabber.org/protocol/disco#items'; + +NS.PUBSUB = 'http://jabber.org/protocol/pubsub'; +NS.PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner'; +NS.PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event'; +NS.PUBSUB_ERROR = 'http://jabber.org/protocol/pubsub#error'; +NS.PUBSUB_METADATA = 'http://jabber.org/protocol/pubsub#meta-data'; +NS.PUBSUB_NODE_CONFIG = 'http://jabber.org/protocol/pubsub#node_config'; +NS.PUBSUB_PUBLISH_OPTIONS = 'http://jabber.org/protocol/pubsub#publish-options'; + +NS.DATAFORMS = 'jabber:x:data'; +NS.DELAY = 'urn:xmpp:delay'; +NS.VERSION = 'jabber:iq:version'; +NS.COMMANDS = 'http://jabber.org/protocol/commands';
new file mode 100644 --- /dev/null +++ b/notifs.js @@ -0,0 +1,166 @@ +var xmpp = require('xmpp'); +var storage = require('./storage'); +var config = require('./configuration'); +var conn; + +function _(obj, color) { + var str = require('sys').inspect(obj, false, null); + if (color) + console.log('\033['+c+';1m' + str + '\033[0m'); + else + console.log(str); +}; + +exports.send = function(notifs, type, nodeID, a1, a2) { + var ev = xmpp.stanza('event', {xmlns: 'http://jabber.org/protocol/pubsub#event'}); + + if (type == 'affiliations') { + ev.attr.xmlns = 'http://jabber.org/protocol/pubsub'; + + var args = {}; + for (i in a1) { + var attr = a1[i]; + if (i == 'affiliation') + args.affiliation = attr; + else if (i == 'jid') + args.jid = attr; + } + var affiliations = xmpp.stanza('affiliations', {node: nodeID}) + .c('affiliation', args); + ev.cnode(affiliations); + } else if (type == 'collection') { + var collection = xmpp.stanza('collection', {node: nodeID}); + if (a1 == 'associate') + collection.cnode('associate', {node: nodeID}); + else + collection.cnode('disassociate', {node: nodeID}); + ev.cnode(collection); + } else if (type == 'configuration') { + if (!config.enabled('config-node')) { + _('Error #4', 41) + return; + } + + var configuration = xmpp.stanza('configuration', {node: nodeID}); + if (a1) { + var x = forms.build('node_config', service_configuration.node_config, storage.getConfiguration(nodeID)); + if (x) + configuration.cnode(x); //TODO: voir exemple 150 + } + ev.cnode(configuration); + } else if (type == 'delete') { + var del = xmpp.stanza('delete', {node: nodeID}); + if (a1) + del.c('redirect', {uri: a1}); + ev.cnode(del); + } else if (type == 'items') { + var items = xmpp.stanza(type, {node: nodeID}); + if (a2 == 'retract') + for (var i in a1) + items.s('retract', {id: i}); + else { + for (var i in a1) { + var item = a1[i]; + var args = {}; + if (i != '') + args.id = i; + if (item.node) + args.node = item.node; + if (item.publisher) + args.publisher = item.publisher; + var it = xmpp.stanza('item', args); + if (item.content) + it.cnode(item.content); + items.cnode(it); + } + } + ev.cnode(items); + } else if (type == 'purge') { + ev.c('purge', {node: nodeID}); + } else if (type == 'subscription') { + if (!config.enabled('subscription-notifications')) + return; + + var args = {node: nodeID}; + for (i in a1) { + var attr = a1[i]; + if (i == 'subscription') { + if (attr == 'none' || attr == 'pending' || attr == 'subscribed' || attr == 'unconfigured') + args[i] = attr; + else { + _('Error #3', 41) + return; + } + } else if (i == 'jid' || i == 'subid') + args[i] = attr; + else if (i == 'expiry') + args[i] = attr.toString(); + } + if (!args.jid || args.jid == '') { + _('Error #2', 41) + return; + } + var sub = xmpp.stanza('subscription', args); + ev.cnode(sub); + } else { + _('Error #1', 41) + return; + } + + var subs; + if (typeof notifs == 'string') { + subs = {}; + subs[notifs] = storage.getSubscription(notifs, nodeID); + } else + subs = notifs; + + for (var i in subs) { + var sub = subs[i]; + + if (sub.options) { + if (typeof sub.options['pubsub#deliver'] != 'undefined' && !sub.options['pubsub#deliver']) + continue; + + if (typeof sub.options['pubsub#digest'] != 'undefined' && sub.options['pubsub#digest']) { + if (!sub.digest) + sub.digest = []; + sub.digest.push(ev) + + if (sub.digestTimeout) + continue; + + var freq; + if (typeof sub.options['pubsub#digest_frequency'] == 'undefined') + freq = 0; + else + freq = parseInt(sub.options['pubsub#digest_frequency']); + + if (freq == 0) + freq = 24*60*60*1000; + + setTimeout(sendDigest, freq, notifs[i], nodeID); + sub.digestTimeout = true; + continue; + } + } + + var message = xmpp.message({to: i, from: config.jid, id: conn.getUniqueId()}); + message.cnode(ev); + conn.send(message); + } +} + +exports.sendDigest = function(jid, nodeID) { + var sub = storage.getSubscription(jid, nodeID); + if (sub.digestTimeout) + sub.digestTimeout = false; + + var message = xmpp.message({to: jid, from: config.jid, id: conn.getUniqueId()}); + for (var i in sub.digest) + message.cnode(sub.digest[i]); + conn.send(message); +} + +exports.setConnection = function(c) { + conn = c; +}