comparison psgxs.js @ 23:5fc4ee90c1bc

A lot of refactorization. First attempt to modularize the server.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sun, 31 Oct 2010 23:58:07 +0100
parents 0f42c9c8085a
children c774f2ffb271
comparison
equal deleted inserted replaced
22:0f42c9c8085a 23:5fc4ee90c1bc
22 var xmpp = require('xmpp'); 22 var xmpp = require('xmpp');
23 var sha1 = require('sha1'); 23 var sha1 = require('sha1');
24 require('./iso8601'); 24 require('./iso8601');
25 var storage = require('./storage'); 25 var storage = require('./storage');
26 var errors = require('./errors'); 26 var errors = require('./errors');
27 var makeError = errors.makeError;
27 var utils = require('./util'); 28 var utils = require('./util');
28 var toBareJID = utils.toBareJID; 29 var toBareJID = utils.toBareJID;
29 var config = require('./configuration'); 30 var config = require('./configuration');
30 var forms = require('./forms'); 31 var forms = require('./forms');
31 var conn = new xmpp.Connection(); 32 var conn = new xmpp.Connection();
33
34 var notifs = require('./notifs');
35 notifs.setConnection(conn);
36
37 var modules = require('./modules');
32 38
33 var service_configuration = config.service_configuration; 39 var service_configuration = config.service_configuration;
34 var componentJID = config.jid; 40 var componentJID = config.jid;
35 var componentPassword = config.password; 41 var componentPassword = config.password;
36 42
49 }); 55 });
50 56
51 if (typeof xmpp.StanzaBuilder.cnode != 'function' || typeof xmpp.StanzaBuilder.prototype.cnode != 'function') { 57 if (typeof xmpp.StanzaBuilder.cnode != 'function' || typeof xmpp.StanzaBuilder.prototype.cnode != 'function') {
52 xmpp.StanzaBuilder.prototype.cnode = function (stanza) 58 xmpp.StanzaBuilder.prototype.cnode = function (stanza)
53 { 59 {
54 var parent = this; 60 var parent = this.last_node[this.last_node.length-1];
55 parent.tags.push(stanza); 61 parent.tags.push(stanza);
56 parent.children.push(stanza); 62 parent.children.push(stanza);
63 this.last_node.push(stanza);
57 return this; 64 return this;
58 }; 65 };
59 } 66 }
60 67
61 function onIq(stanza) { 68 function onIq(stanza) {
68 if (id) 75 if (id)
69 response = xmpp.iq({to: to, from: from, type: 'result', id: id}); 76 response = xmpp.iq({to: to, from: from, type: 'result', id: id});
70 else 77 else
71 response = xmpp.iq({to: to, from: from, type: 'result'}); 78 response = xmpp.iq({to: to, from: from, type: 'result'});
72 79
73 if (type == 'get') { 80 var sent = false;
74 81
75 // XEP-0092: Software Version 82 for (var i in modules) {
76 if (stanza.getChild('query', 'jabber:iq:version')) { 83 var module = modules[i];
77 var query = xmpp.stanza('query', {xmlns: 'jabber:iq:version'}) 84 if (module.type && (type != module.type))
78 .s('name').t('PSĜS') 85 continue;
79 .s('version').t(config.version)
80 .s('os').t(config.os);
81 response.cnode(query);
82 86
83 // SECTION 5.1 87 for (var j in stanza.tags) {
84 } else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#info')) { 88 var child = stanza.tags[j];
85 var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#info'); 89 if (module.child && (child.name != module.child))
86 var nodeID = query.getAttribute('node'); 90 continue;
87 91
88 // SECTION 5.3 92 if (module.ns && (child.attr.xmlns != module.ns))
89 if (nodeID && nodeID != '') { 93 continue;
90 if (!storage.existsNode(nodeID))
91 return makeError(response, errors.node_does_not_exist.n);
92 94
93 var conf = storage.getConfiguration(nodeID); 95 if (module.child == 'pubsub') {
94 if (typeof conf == 'number') 96 var child2 = child.getChild(module.pschild, child.attr.xmlns);
95 return makeError(response, conf); 97 if (child2)
98 child = child2;
96 99
97 var type = 'leaf' 100 if (module.pschild && (!child || module.pschild != child2.name))
98 if (conf['pubsub#node_type']) 101 continue;
99 type = conf['pubsub#node_type'];
100
101 var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info', node: nodeID})
102 q.s('identity', {category: 'pubsub', type: type});
103 q.s('feature', {'var': 'http://jabber.org/protocol/pubsub'});
104
105 // SECTION 5.4
106 if (config.enabled('meta-data')) {
107 var x = forms.build('result', 'node_metadata', storage.getMetadata(nodeID), true);
108 if (x)
109 q.cnode(x);
110 }
111 response.cnode(q);
112
113 // SECTION 5.1
114 } else {
115 var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info'})
116 .s('identity', {category: 'pubsub', type: 'service', name: 'PubSub JavaScript Server'})
117 .s('feature', {'var': 'http://jabber.org/protocol/disco#info'})
118 .s('feature', {'var': 'http://jabber.org/protocol/disco#items'})
119 .s('feature', {'var': 'http://jabber.org/protocol/pubsub'})
120 .s('feature', {'var': 'jabber:iq:version'})
121 .s('feature', {'var': 'http://jabber.org/protocol/commands'});
122
123 for (var i in config.activated)
124 if (typeof i == 'string')
125 q.s('feature', {'var': 'http://jabber.org/protocol/pubsub#' + config.activated[i]});
126
127 response.cnode(q);
128 } 102 }
129 103
130 // SECTION 5.2 104 var toSend = module.func(response, stanza, child, to);
131 } else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#items')) { 105 if (toSend) {
132 var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#items'); 106 conn.send(toSend);
133 var q; 107 sent = true;
134 var children; 108 }
135 var nodeID = query.getAttribute('node'); 109 }
136 if (nodeID && nodeID != '') { 110 }
137 if (nodeID == 'http://jabber.org/protocol/commands') {
138 // XEP-0050: Ad-Hoc Commands
139 111
140 q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items', node: nodeID}) 112 if (!sent)
141 .s('item', {jid: componentJID, node: 'ping', name: 'Ping'}) 113 conn.send(makeError(response, errors.feature_not_implemented.n));
142 .s('item', {jid: componentJID, node: 'reload', name: 'Reload'});
143
144 response.cnode(q);
145 return conn.send(response);
146 } else {
147 if (!storage.existsNode(nodeID))
148 return makeError(response, errors.node_does_not_exist.n);
149
150 q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items', node: nodeID});
151
152 children = storage.getChildren(nodeID);
153 if (typeof children == 'number')
154 return makeError(response, children);
155 }
156 } else {
157 q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items'});
158
159 children = storage.getChildren();
160 if (typeof children == 'number')
161 return makeError(response, children);
162 }
163
164 for (var i in children) {
165 var attr = {jid: componentJID};
166 if (children[i] == 'node') {
167 if (config.enabled('meta-data')) {
168 var metadata = storage.getMetadata(i);
169 if (metadata['pubsub#title'])
170 attr.name = metadata['pubsub#title'];
171 }
172 attr.node = i;
173
174 // SECTION 5.5
175 } else
176 attr.name = i;
177
178 q.s('item', attr);
179 }
180 response.cnode(q);
181 } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) {
182 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
183
184 // SECTION 5.6
185 if (pubsub.getChild('subscriptions')) {
186 if (!config.enabled('retrieve-subscriptions'))
187 return makeError(response, errors.subscriptions_retrieval_not_supported.n);
188
189 var subscriptions = pubsub.getChild('subscriptions');
190 var subs;
191
192 var nodeID = subscriptions.getAttribute('node');
193 if (nodeID && nodeID != '') {
194 if (!storage.existsNode(nodeID))
195 return makeError(response, errors.node_does_not_exist.n);
196 subs = storage.getSubscription(toBareJID(to), node);
197 } else
198 subs = storage.getSubscription(toBareJID(to));
199
200 var s = xmpp.stanza('subscriptions');
201 for (i in subs)
202 s.s('subscription', {node: i, jid: to, subscription: subs[i].type, subid: subs[i].subid});
203
204 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
205 p.cnode(s);
206 response.cnode(p);
207
208 // SECTION 5.7
209 } else if (pubsub.getChild('affiliations')) {
210 if (!config.enabled('retrieve-affiliations'))
211 return makeError(response, errors.affiliations_retrieval_not_supported.n);
212
213 var affiliations = pubsub.getChild('affiliations');
214 var nodeID = affiliations.getAttribute('node');
215 var affils;
216 if (nodeID && nodeID != '') {
217 if (!storage.existsNode(nodeID))
218 return makeError(response, errors.node_does_not_exist.n);
219 affils = {};
220 affils[nodeID] = storage.getAffiliation(toBareJID(to), nodeID);
221 } else
222 affils = storage.getAffiliationsFromJID(toBareJID(to));
223
224 var s = xmpp.stanza('affiliations');
225 for (i in affils)
226 s.s('affiliation', {node: i, affiliation: affils[i]});
227
228 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
229 p.cnode(s);
230 response.cnode(p);
231
232 // SECTION 6.3.2
233 } else if (pubsub.getChild('options')) {
234 if (!config.enabled('subscription-options'))
235 return makeError(response, errors.sub.configure.subscription_options_not_supported.n);
236
237 var options = pubsub.getChild('options');
238
239 var nodeID = options.getAttribute('node');
240 if (!nodeID || nodeID == '')
241 return makeError(response, errors.nodeid_required.n);
242 if (!storage.existsNode(nodeID))
243 return makeError(response, errors.node_does_not_exist.n);
244
245 var jid = options.getAttribute('jid');
246 if (!jid)
247 return makeError(response, errors.sub.configure.subscriber_jid_required.n);
248 if (toBareJID(jid) != toBareJID(to))
249 return makeError(response, errors.sub.configure.insufficient_privileges.n);
250
251 var subs = storage.getSubscription(jid, nodeID);
252 if (!subs.subid) // FIXME: better test for empty object.
253 return makeError(response, errors.sub.configure.no_such_subscriber.n);
254
255 var s = xmpp.stanza('options', {node: nodeID, jid: jid});
256 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
257 var form = forms.build('form', 'subscribe_options', subs.options, true);
258 s.cnode(form);
259 p.cnode(s);
260 response.cnode(p);
261
262 // SECTION 6.4
263 } else if (pubsub.getChild('default')) {
264 if (!config.enabled('retrieve-default-sub'))
265 return makeError(response, errors.sub.default_options.default_subscription_configuration_retrieval_not_supported.n);
266
267 var def = pubsub.getChild('default');
268
269 var nodeID = def.getAttribute('node');
270 if (nodeID && !storage.existsNode(nodeID))
271 return makeError(response, errors.node_does_not_exist.n);
272
273 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
274 var s;
275 if (nodeID)
276 s = xmpp.stanza('default', {node: nodeID});
277 else
278 s = xmpp.stanza('default');
279
280 var form = forms.build('form', 'subscribe_options', 'default', false);
281 s.cnode(form);
282 p.cnode(s);
283 response.cnode(p);
284
285 // SECTION 6.5
286 } else if (pubsub.getChild('items')) {
287 if (!config.enabled('retrieve-items'))
288 return makeError(response, errors.sub.default_options.node_configuration_not_supported.n);
289
290 var items = pubsub.getChild('items');
291
292 var nodeID = items.getAttribute('node');
293 if (!nodeID || nodeID == '')
294 return makeError(response, errors.nodeid_required.n);
295 if (!storage.existsNode(nodeID))
296 return makeError(response, errors.node_does_not_exist.n);
297
298 var configuration = storage.getConfiguration(nodeID);
299 if (configuration['pubsub#access_model'] == 'whitelist') {
300 var affil = storage.getAffiliation(toBareJID(to), nodeID);
301 if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member')
302 return makeError(response, errors.pub.publish.insufficient_privileges.n);
303 }
304
305 var item = [];
306 for (var i=0; i<items.children.length; i++) {
307 var j = items.children[i];
308 if (j.name == 'item' && j.attr['id'] && j.attr['id'] != '')
309 item.push(j.attr['id']);
310 }
311
312 var max_items = items.getAttribute('max_items');
313 if (max_items)
314 max_items = Number (max_items);
315
316 if (item.length) {
317 var s = xmpp.stanza('items', {node: nodeID});
318
319 for (var i=0; i<item.length; i++) {
320 var j = storage.getItem(nodeID, item[i]);
321 if (typeof j == 'number')
322 return makeError(response, j);
323 if (j == errors.success)
324 continue;
325
326 var k = xmpp.stanza('item', {id: item[i]})
327 k.cnode(j);
328 s.cnode(k);
329 }
330 } else {
331 var s = xmpp.stanza('items', {node: nodeID});
332
333 var j;
334 if (max_items)
335 j = storage.getLastItem(nodeID, max_items);
336 else
337 j = storage.getItems(nodeID);
338 if (typeof j == 'number')
339 return makeError(response, j);
340
341 var k = 0;
342 for (var i in j) {
343 var contentItem = xmpp.stanza('item', {id: i}).t(j[i].content);
344 s.cnode(contentItem);
345 }
346 }
347 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
348 p.cnode(s);
349 response.cnode(p);
350 } else
351 return makeError(response, errors.feature_not_implemented.n);
352 } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
353 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
354
355 // SECTION 8.2
356 if (pubsub.getChild('configure')) {
357 if (!config.enabled('config-node'))
358 return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
359
360 var nodeID = pubsub.getChild('configure').getAttribute('node');
361 if (!nodeID || nodeID == '')
362 return makeError(response, errors.nodeid_required.n);
363 if (!storage.existsNode(nodeID))
364 return makeError(response, errors.node_does_not_exist.n);
365
366 var affil = storage.getAffiliation(toBareJID(to), nodeID);
367 if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
368 return makeError(response, errors.pub.publish.insufficient_privileges.n);
369
370 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
371 var s = xmpp.stanza('configure', {node: nodeID});
372 var form = forms.build('form', 'node_config', 'default', true);
373 s.cnode(form);
374 p.cnode(s);
375 response.cnode(p);
376
377 // SECTION 8.3
378 } else if (pubsub.getChild('default')) {
379 if (!config.enabled('config-node'))
380 return makeError(response, errors.owner.default_options.node_configuration_not_supported.n);
381
382 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
383 var s = xmpp.stanza('default');
384 var form = forms.build('node_config', service_configuration.node_config, null, true);
385 s.cnode(form);
386 p.cnode(s);
387 response.cnode(p);
388
389 // SECTION 8.8
390 } else if (pubsub.getChild('subscriptions')) {
391 if (!config.enabled('manage-subscriptions'))
392 return makeError(response, errors.owner.manage_subscriptions.not_supported.n);
393
394 var subscriptions = pubsub.getChild('subscriptions');
395
396 var nodeID = subscriptions.getAttribute('node');
397 if (!nodeID || nodeID == '')
398 return makeError(response, errors.nodeid_required.n);
399 if (!storage.existsNode(nodeID))
400 return makeError(response, errors.node_does_not_exist.n);
401
402 var affil = storage.getAffiliation(toBareJID(to), nodeID);
403 if (affil != 'super-owner' && affil != 'owner')
404 return makeError(response, errors.forbidden.n);
405
406 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
407 var s = xmpp.stanza('subscriptions', {node: nodeID});
408
409 var subs = storage.getSubscriptionsFromNodeID(nodeID)
410 for (var jid in subs)
411 s.s('subscription', {jid: jid, subscription: subs[jid].type, subid: subs[jid].subid})
412
413 p.cnode(s);
414 response.cnode(p);
415
416 // SECTION 8.9
417 } else if (pubsub.getChild('affiliations')) {
418 if (!config.enabled('modify-affiliations'))
419 return makeError(response, errors.owner.manage_affiliations.not_supported.n);
420
421 var affiliations = pubsub.getChild('affiliations');
422
423 var nodeID = affiliations.getAttribute('node');
424 if (!nodeID || nodeID == '')
425 return makeError(response, errors.nodeid_required.n);
426 if (!storage.existsNode(nodeID))
427 return makeError(response, errors.node_does_not_exist.n);
428
429 var affils = storage.getAffiliationsFromNodeID(nodeID);
430 var affil = affils[toBareJID(to)];
431 if (affil != 'super-owner' && affil != 'owner')
432 return makeError(response, errors.owner.manage_affiliations.retrieve_list.entity_is_not_an_owner.n);
433
434 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
435 var s = xmpp.stanza('affiliations', {node: nodeID});
436
437 for (var jid in affils)
438 s.s('affiliation', {jid: jid, affiliation: affils[jid]})
439
440 p.cnode(s);
441 response.cnode(p);
442 } else
443 return makeError(response, errors.feature_not_implemented.n);
444 } else
445 return makeError(response, errors.feature_not_implemented.n);
446 } else if (type == 'set') {
447 if (stanza.getChild('command', 'http://jabber.org/protocol/commands')) {
448 // XEP-0050: Ad-Hoc Commands
449 var command = stanza.getChild('command', 'http://jabber.org/protocol/commands');
450
451 var action = command.getAttribute('action');
452 if (action != 'execute')
453 return makeError(response, errors.bad_request.n);
454
455 var node = command.getAttribute('node');
456 if (node == 'ping') {
457 var cmd = xmpp.stanza('command', {xmlns: 'http://jabber.org/protocol/commands',
458 // sessionid: 'list:20020923T213616Z-700',
459 node: node,
460 'status': 'completed'})
461 .c('note', {type: 'info'}).t('pong');
462 response.cnode(cmd);
463 } else if (node == 'reload') {
464 storage.load();
465 response.c('command', {xmlns: 'http://jabber.org/protocol/commands',
466 node: node,
467 'status': 'completed'})
468 .c('note', {type: 'info'}).t('The server has correctly reloaded.');
469 } else
470 return makeError(response, errors.bad_request.n);
471 } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) {
472 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
473
474 // SECTION 6.1
475 if (pubsub.getChild('subscribe')) {
476 if (!config.enabled('subscribe'))
477 return makeError(response, errors.sub.subscribe.not_supported.n);
478
479 var subscribe = pubsub.getChild('subscribe');
480
481 var nodeID = subscribe.getAttribute('node');
482 if (!nodeID || nodeID == '')
483 return makeError(response, errors.nodeid_required.n);
484 if (!storage.existsNode(nodeID))
485 return makeError(response, errors.node_does_not_exist.n);
486
487 var configuration = storage.getConfiguration(nodeID);
488 if (!configuration['pubsub#subscribe'])
489 return makeError(response, errors.sub.subscribe.not_supported.n);
490
491 var affil = storage.getAffiliation(toBareJID(to), nodeID);
492 if (affil == 'publish-only' || affil == 'outcast')
493 return makeError(response, errors.pub.publish.insufficient_privileges.n);
494
495 var jid = subscribe.getAttribute('jid');
496 if (!jid || toBareJID(jid) != toBareJID(to))
497 return makeError(response, errors.sub.subscribe.jids_do_not_match.n);
498
499 // SECTION 6.3.7
500 var options = pubsub.getChild('options');
501 if (options && config.enabled('subscription-options')) {
502 if (options.getAttribute('node') || options.getAttribute('jid'))
503 return makeError(response, errors.bad_request.n);
504
505 var x = options.getChild('x', 'jabber:x:data');
506 if (!x || x.getAttribute('type') != 'submit')
507 return makeError(response, errors.bad_request.n);
508
509 var form = forms.parse(x, true);
510 if (typeof form == 'number')
511 return makeError(response, form);
512
513 var conf = form;
514 }
515
516 var subID;
517 if (configuration['pubsub#access_model'] == 'open') {
518 subID = storage.subscribe(nodeID, jid, 'subscribe', conf);
519 if (typeof subID == 'number')
520 return makeError(response, subID);
521 } else if (configuration['pubsub#access_model'] == 'authorize') {
522 subID = storage.subscribe(nodeID, jid, 'pending', conf);
523 if (typeof subID == 'number')
524 return makeError(response, subID);
525
526 var affiliates = storage.getAffiliationsFromNodeID(nodeID);
527 var form = forms.build('form', 'subscribe_authorization', {allow: false, node: nodeID, subscriber_jid: jid}, true); //168
528
529 for (var i in affiliates) {
530 if (affiliates[i] == 'super-owner' || affiliates[i] == 'owner') {
531 var message = xmpp.message({to: i}).cnode(form);
532 conn.send(message);
533 }
534 }
535 } else if (configuration['pubsub#access_model'] == 'whitelist') {
536 var affil = storage.getAffiliation(jid, nodeID);
537 if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member')
538 return makeError(response, errors.sub.subscribe.not_on_whitelist.n);
539
540 subID = storage.subscribe(nodeID, jid, conf);
541 if (typeof subID == 'number')
542 return makeError(response, subID);
543 }
544
545 response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
546 .c('subscription', {node: nodeID, jid: jid, subid: subID.subid, subscription: subID.type});
547
548 if (conf)
549 response.cnode(options);
550
551 if (config.enabled('last-published')) {
552 var last = storage.getLastItem(nodeID);
553 if (typeof last != 'number') {
554 var item = storage.getItem(nodeID, last);
555 if (typeof item != 'number') {
556 var attr = {};
557 attr[last] = {content: item};
558 sendNotifs(jid, 'items', nodeID, attr);
559 }
560 }
561 }
562
563 // SECTION 6.2
564 } else if (pubsub.getChild('unsubscribe')) {
565 if (!config.enabled('subscribe'))
566 return makeError(response, errors.sub.subscribe.not_supported.n);
567
568 var unsubscribe = pubsub.getChild('unsubscribe');
569 var nodeID = unsubscribe.getAttribute('node');
570 if (!nodeID || nodeID == '')
571 return makeError(response, errors.nodeid_required.n);
572 if (!storage.existsNode(nodeID))
573 return makeError(response, errors.node_does_not_exist.n);
574
575 var jid = unsubscribe.getAttribute('jid');
576 if (!jid || toBareJID(jid) != toBareJID(to))
577 return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n);
578
579 var subID = storage.subscribe(nodeID, jid, 'none');
580 if (typeof subID == 'number')
581 return makeError(response, subID);
582
583 // SECTIONS 6.3.5
584 } else if (pubsub.getChild('options')) {
585 if (!config.enabled('subscription-options'))
586 return makeError(response, errors.sub.subscribe.not_supported.n);
587
588 var options = pubsub.getChild('options');
589
590 var nodeID = unsubscribe.getAttribute('node');
591 if (!nodeID || nodeID == '')
592 return makeError(response, errors.nodeid_required.n);
593 if (!storage.existsNode(nodeID))
594 return makeError(response, errors.node_does_not_exist.n);
595
596 var jid = unsubscribe.getAttribute('jid');
597 if (!jid || toBareJID(jid) != toBareJID(to))
598 return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n);
599
600 var x = options.getChild('x', 'jabber:x:data');
601 if (!x || x.getAttribute(type) != 'submit')
602 return makeError(response, errors.bad_request);
603
604 var form = forms.parse(x, true);
605 if (typeof form == 'number')
606 return makeError(response, form);
607
608 var set = storage.configureSubscription(nodeID, jid, form);
609 if (typeof form == 'number')
610 return makeError(response, form);
611
612 // SECTION 7.1
613 } else if (pubsub.getChild('publish')) {
614 if (!config.enabled('publish'))
615 return makeError(response, errors.pub.publish.item_publication_not_supported.n);
616
617 var publish = pubsub.getChild('publish');
618 var nodeID = publish.getAttribute('node');
619 if (!nodeID || nodeID == '')
620 return makeError(response, errors.nodeid_required.n);
621
622 var autocreate = false;
623 if (!storage.existsNode(nodeID)) {
624 if (config.enabled('auto-create'))
625 autocreate = true;
626 else
627 return makeError(response, errors.node_does_not_exist.n);
628 }
629
630 var affil = storage.getAffiliation(toBareJID(to), nodeID);
631 if (typeof affil == 'number' && affil != errors.node_does_not_exist.n)
632 return makeError(response, affil);
633 if (!autocreate && affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'publish-only')
634 return makeError(response, errors.forbidden.n);
635
636 var item = publish.getChild('item');
637 var itemID = item.getAttribute('id');
638 if (!config.enabled('item-ids') && itemID)
639 return makeError(response, errors.itemid_required.n);
640 itemID = itemID? itemID: utils.makeRandomId();
641
642 if (item.tags.length != 1)
643 return makeError(response, errors.pub.publish.bad_payload.n);
644
645 var conf = storage.getConfiguration(nodeID);
646 var publishOptions = pubsub.getChild('publish-options');
647 if (publishOptions && config.enabled('publish-options')) {
648 var x = publishOptions.getChild('x', 'jabber:x:data');
649 if (!x || x.getAttribute('type') != 'submit')
650 return makeError(response, errors.bad_request.n);
651
652 var form = forms.parse(x, true);
653 if (form.access_model != conf['pubsub#access_model'] && !autocreate)
654 return makeError(response, errors.pub.configuration.precondition.n);
655 }
656
657 if (!config.enabled('persistent-items')) {
658 var notifs = storage.purgeNode(nodeID);
659 if (typeof notifs == 'number')
660 return makeError(response, r);
661 }
662
663 if (autocreate) {
664 if (!form)
665 form = {};
666 form['pubsub#creator'] = toBareJID(to);
667
668 var r = storage.createNode(nodeID, form);
669 if (typeof r == 'number')
670 return makeError(response, r);
671 }
672
673 var content = item.getChild();
674
675 subscribers = storage.setItem(nodeID, itemID, content);
676 if (typeof subscribers == 'number')
677 return makeError(response, subscribers);
678
679 var attrs = {};
680 if (content)
681 attrs[itemID] = {content: content};
682 else
683 attrs[itemID] = {};
684 sendNotifs(subscribers, 'items', nodeID, attrs);
685
686 response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
687 .c('publish', {node: nodeID})
688 .c('item', {id: itemID});
689
690 // SECTION 7.2
691 } else if (pubsub.getChild('retract')) {
692 if (!config.enabled('retract-items'))
693 return makeError(response, errors.pub.retract.item_deletion_not_supported.n);
694
695 var retract = pubsub.getChild('retract');
696
697 var nodeID = retract.getAttribute('node');
698 if (!nodeID || nodeID == '')
699 return makeError(response, errors.nodeid_required.n);
700 if (!storage.existsNode(nodeID))
701 return makeError(response, errors.node_does_not_exist.n);
702
703 var item = retract.getChild('item');
704 if (!item)
705 return makeError(response, errors.pub.retract.item_or_itemid_required.n);
706
707 var itemID = item.getAttribute('id')
708 if (!itemID || itemID == '')
709 return makeError(response, errors.pub.retract.item_or_itemid_required.n);
710
711 var affil = storage.getAffiliation(toBareJID(to), nodeID);
712 if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
713 return makeError(response, errors.forbidden.n);
714
715 var subscribers = storage.deleteItem(nodeID, itemID);
716 if (typeof subscribers == 'number')
717 return makeError(response, subscribers);
718
719 var attrs = {};
720 attrs[itemID] = {};
721 sendNotifs(subscribers, 'items', nodeID, attrs, 'retract')
722
723 // SECTION 8.1
724 } else if (pubsub.getChild('create')) {
725 if (!config.enabled('create-nodes'))
726 return makeError(response, errors.owner.create.node_creation_not_supported.n);
727
728 var instant = false;
729
730 var nodeID = pubsub.getChild('create').getAttribute('node');
731 if (!nodeID || nodeID == '') {
732 if (!config.enabled('instant-nodes'))
733 return makeError(response, errors.owner.create.instant_nodes_not_supported.n);
734 nodeID = utils.makeRandomId();
735 instant = true;
736 }
737 if (storage.existsNode(nodeID))
738 return makeError(response, errors.owner.create.nodeid_already_exists.n);
739
740 var bare = toBareJID(to);
741 var right = false;
742
743 // Check for super-owner
744 for (var i in config.owner)
745 if (config.owner[i] == bare)
746 right = true;
747
748 // Check for authorized user
749 for (var i in config.allowCreateNode)
750 if (config.allowCreateNode[i].exec(bare))
751 right = true;
752
753 if (!right)
754 return makeError(response, errors.forbidden.n);
755
756 var configure = pubsub.getChild('configure');
757 if (configure && config.enabled('create-and-configure')) {
758 if (!config.enabled('config-node'))
759 return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
760
761 if (configure.getAttribute('node'))
762 return makeError(response, errors.bad_request.n);
763
764 var x = configure.getChild('x', 'jabber:x:data');
765 if (!x || x.getAttribute('type') != 'submit')
766 return makeError(response, errors.bad_request.n);
767
768 var form = forms.parse(x, true);
769 if (typeof form == 'number')
770 return makeError(response, form);
771
772 var conf = form;
773 }
774
775 if (!conf)
776 conf = {};
777 conf['pubsub#creator'] = bare;
778
779 var r = storage.createNode(nodeID, conf);
780 if (typeof r == 'number')
781 return makeError(response, r);
782
783 if (instant)
784 response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
785 .c('create', {node: nodeID});
786 } else
787 return makeError(response, errors.feature_not_implemented.n);
788 } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
789 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
790
791 // SECTION 8.2.4
792 if (pubsub.getChild('configure')) {
793 if (!config.enabled('config-node'))
794 return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
795
796 var configure = pubsub.getChild('configure');
797
798 var nodeID = configure.getAttribute('node');
799 if (!nodeID)
800 return makeError(response, errors.nodeid_required.n);
801 if (!storage.existsNode(nodeID))
802 return makeError(response, errors.node_does_not_exist.n);
803
804 var affil = storage.getAffiliation(toBareJID(to), nodeID);
805 if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
806 return makeError(response, errors.forbidden.n);
807
808 var x = configure.getChild('x', 'jabber:x:data');
809 if (!x)
810 return makeError(response, errors.bad_request.n);
811
812 var type = x.getAttribute('type');
813 if (type == 'cancel') {
814 conn.send(response);
815 return;
816 }
817 if (type != 'submit')
818 return makeError(response, errors.bad_request.n);
819
820 var form = forms.parse(x, true);
821 if (typeof form == 'number')
822 return makeError(response, form);
823
824 var conf = form;
825
826 var set = storage.configure(nodeID, conf);
827 if (typeof set == 'number')
828 return makeError(response, set);
829
830 // SECTION 8.4
831 } else if (pubsub.getChild('delete')) {
832 if (!config.enabled('delete-nodes'))
833 return makeError(response, errors.feature_not_implemented.n); //XXX
834
835 var del = pubsub.getChild('delete');
836
837 var nodeID = del.getAttribute('node');
838 if (!nodeID)
839 return makeError(response, errors.nodeid_required.n);
840 if (!storage.existsNode(nodeID))
841 return makeError(response, errors.node_does_not_exist.n);
842
843 var affil = storage.getAffiliation(toBareJID(to), nodeID);
844 if (affil != 'super-owner' && affil != 'owner')
845 return makeError(response, errors.forbidden.n);
846
847 var notifs = storage.deleteNode(nodeID);
848 if (typeof notifs == 'number')
849 return makeError(response, r);
850
851 sendNotifs(notifs, 'delete', nodeID);
852
853 // SECTION 8.5
854 } else if (pubsub.getChild('purge')) {
855 if (!config.enabled('purge-nodes'))
856 return makeError(response, errors.owner.purge.node_purging_not_supported.n); //XXX
857
858 var purge = pubsub.getChild('purge');
859
860 var nodeID = purge.getAttribute('node');
861 if (!nodeID)
862 return makeError(response, errors.nodeid_required.n);
863 if (!storage.existsNode(nodeID))
864 return makeError(response, errors.node_does_not_exist.n);
865
866 var affil = storage.getAffiliation(toBareJID(to), nodeID);
867 if (affil != 'super-owner' && affil != 'owner')
868 return makeError(response, errors.forbidden.n);
869
870 if (!config.enabled('persistent-items')) //FIXME: autre condition, supporté par le node
871 return makeError(response, errors.owner.purge.node_does_not_persist_items.n);
872
873 var notifs = storage.purgeNode(nodeID);
874 if (typeof notifs == 'number')
875 return makeError(response, r);
876
877 sendNotifs(notifs, 'purge', nodeID);
878
879 // SECTION 8.8.2
880 } else if (pubsub.getChild('subscriptions')) {
881 if (!config.enabled('manage-subscriptions'))
882 return makeError(response, errors.owner.manage_subscriptions.not_supported.n); //XXX
883
884 var subscriptions = pubsub.getChild('subscriptions');
885
886 var nodeID = subscriptions.getAttribute('node');
887 if (!nodeID)
888 return makeError(response, errors.nodeid_required.n);
889 if (!storage.existsNode(nodeID))
890 return makeError(response, errors.node_does_not_exist.n);
891
892 var affil = storage.getAffiliation(toBareJID(to), nodeID);
893 if (affil != 'super-owner' && affil != 'owner')
894 return makeError(response, errors.forbidden.n);
895
896 var e = false;
897 var tags2 = [];
898 for (i in subscriptions.tags) {
899 var tag = subscriptions.tags[i];
900 var jid = tag.getAttribute('jid');
901 var sub = tag.getAttribute('subscription');
902
903 if (sub == 'none' || sub == 'pending' || sub == 'subscribed' || sub == 'unconfigured') {
904 var set = storage.subscribe(nodeID, jid, sub);
905
906 if (typeof set == 'number') {
907 e = true;
908 tags2.push(tag);
909 } else {
910 // SECTION 8.8.4
911 sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: sub});
912 }
913 } else {
914 e = true;
915 tags2.push(tag);
916 }
917 }
918
919 subscriptions.tags = tags2;
920
921 if (e)
922 return makeError(response, errors.owner.manage_subscriptions.modify.multiple_simultaneous_modifications.n, pubsub);
923
924 // SECTION 8.9.2
925 } else if (pubsub.getChild('affiliations')) {
926 if (!config.enabled('modify-affiliations'))
927 return makeError(response, errors.owner.manage_affiliations.not_supported.n); //XXX
928
929 var affiliations = pubsub.getChild('affiliations');
930
931 var nodeID = affiliations.getAttribute('node');
932 if (!nodeID)
933 return makeError(response, errors.nodeid_required.n);
934 if (!storage.existsNode(nodeID))
935 return makeError(response, errors.node_does_not_exist.n);
936
937 var affil = storage.getAffiliation(toBareJID(to), nodeID);
938 if (affil != 'super-owner' && affil != 'owner')
939 return makeError(response, errors.forbidden.n);
940
941 var e = false;
942 for (i in affiliations.children) {
943 var jid = affiliations.children[i].getAttribute('jid');
944 var affiliation = affiliations.children[i].getAttribute('affiliation');
945
946 var set = storage.setAffiliation(nodeID, jid, affiliation);
947 if (typeof set == 'number')
948 e = true;
949 else {
950 // SECTION 8.9.4
951 sendNotifs(jid, 'affiliations', nodeID, {jid: jid, affiliation: affiliation});
952 affiliations.children.splice(i, 1);
953 }
954 }
955
956 if (e)
957 return makeError(response, errors.owner.manage_affiliations.modify.multiple_simultaneous_modifications.n, pubsub);
958 } else
959 return makeError(response, errors.feature_not_implemented.n);
960 } else
961 return makeError(response, errors.feature_not_implemented.n);
962 } else
963 return makeError(response, errors.feature_not_implemented.n);
964 conn.send(response);
965 } 114 }
966 115
967 function onMessage(stanza) { 116 function onMessage(stanza) {
968 var from = stanza.getAttribute('to'); 117 var from = stanza.getAttribute('to');
969 var to = stanza.getAttribute('from'); 118 var to = stanza.getAttribute('from');
989 var allow = form.fields.allow.value; 138 var allow = form.fields.allow.value;
990 139
991 var type = allow? 'subscribed': 'none'; 140 var type = allow? 'subscribed': 'none';
992 var set = storage.subscribe(nodeID, jid, type) 141 var set = storage.subscribe(nodeID, jid, type)
993 //if (set.subid != subID) //TODO: support the multi-subscribe feature 142 //if (set.subid != subID) //TODO: support the multi-subscribe feature
994 sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: type}); 143 notifs.send(jid, 'subscription', nodeID, {jid: jid, subscription: type});
995 } else 144 } else
996 return makeError(response, errors.feature_not_implemented.n); 145 return makeError(response, errors.feature_not_implemented.n);
997 } else 146 } else
998 return makeError(response, errors.feature_not_implemented.n); 147 return makeError(response, errors.feature_not_implemented.n);
999 conn.send(response) 148 conn.send(response)
1011 response = xmpp.presence({to: to, from: from}); 160 response = xmpp.presence({to: to, from: from});
1012 161
1013 makeError(response, errors.feature_not_implemented.n); 162 makeError(response, errors.feature_not_implemented.n);
1014 } 163 }
1015 164
1016 function makeError(response, errorNumber, payload) {
1017 response.attr.type = 'error';
1018 if (payload)
1019 response.cnode(payload);
1020
1021 var e = errors.reverse[errorNumber];
1022 var error = xmpp.stanza('error', {type: e.type});
1023
1024 error.s(e.error, {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'})
1025
1026 if (e.reason) {
1027 if (e.feature)
1028 error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors', feature: e.feature});
1029 else
1030 error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors'});
1031 }
1032
1033 response.cnode(error);
1034 conn.send(response);
1035 }
1036
1037 function sendNotifs(notifs, type, nodeID, a1, a2) {
1038 var ev = xmpp.stanza('event', {xmlns: 'http://jabber.org/protocol/pubsub#event'});
1039
1040 if (type == 'affiliations') {
1041 ev.attr.xmlns = 'http://jabber.org/protocol/pubsub';
1042
1043 var args = {};
1044 for (i in a1) {
1045 var attr = a1[i];
1046 if (i == 'affiliation')
1047 args.affiliation = attr;
1048 else if (i == 'jid')
1049 args.jid = attr;
1050 }
1051 var affiliations = xmpp.stanza('affiliations', {node: nodeID})
1052 .c('affiliation', args);
1053 ev.cnode(affiliations);
1054 } else if (type == 'collection') {
1055 var collection = xmpp.stanza('collection', {node: nodeID});
1056 if (a1 == 'associate')
1057 collection.cnode('associate', {node: nodeID});
1058 else
1059 collection.cnode('disassociate', {node: nodeID});
1060 ev.cnode(collection);
1061 } else if (type == 'configuration') {
1062 if (!config.enabled('config-node')) {
1063 _('Error #4', 41)
1064 return;
1065 }
1066
1067 var configuration = xmpp.stanza('configuration', {node: nodeID});
1068 if (a1) {
1069 var x = forms.build('node_config', service_configuration.node_config, storage.getConfiguration(nodeID));
1070 if (x)
1071 configuration.cnode(x); //TODO: voir exemple 150
1072 }
1073 ev.cnode(configuration);
1074 } else if (type == 'delete') {
1075 var del = xmpp.stanza('delete', {node: nodeID});
1076 if (a1)
1077 del.c('redirect', {uri: a1});
1078 ev.cnode(del);
1079 } else if (type == 'items') {
1080 var items = xmpp.stanza(type, {node: nodeID});
1081 if (a2 == 'retract')
1082 for (var i in a1)
1083 items.s('retract', {id: i});
1084 else {
1085 for (var i in a1) {
1086 var item = a1[i];
1087 var args = {};
1088 if (i != '')
1089 args.id = i;
1090 if (item.node)
1091 args.node = item.node;
1092 if (item.publisher)
1093 args.publisher = item.publisher;
1094 var it = xmpp.stanza('item', args);
1095 if (item.content)
1096 it.cnode(item.content);
1097 items.cnode(it);
1098 }
1099 }
1100 ev.cnode(items);
1101 } else if (type == 'purge') {
1102 ev.c('purge', {node: nodeID});
1103 } else if (type == 'subscription') {
1104 if (!config.enabled('subscription-notifications'))
1105 return;
1106
1107 var args = {node: nodeID};
1108 for (i in a1) {
1109 var attr = a1[i];
1110 if (i == 'subscription') {
1111 if (attr == 'none' || attr == 'pending' || attr == 'subscribed' || attr == 'unconfigured')
1112 args[i] = attr;
1113 else {
1114 _('Error #3', 41)
1115 return;
1116 }
1117 } else if (i == 'jid' || i == 'subid')
1118 args[i] = attr;
1119 else if (i == 'expiry')
1120 args[i] = attr.toString();
1121 }
1122 if (!args.jid || args.jid == '') {
1123 _('Error #2', 41)
1124 return;
1125 }
1126 var sub = xmpp.stanza('subscription', args);
1127 ev.cnode(sub);
1128 } else {
1129 _('Error #1', 41)
1130 return;
1131 }
1132
1133 var subs;
1134 if (typeof notifs == 'string') {
1135 subs = {};
1136 subs[notifs] = storage.getSubscription(notifs, nodeID);
1137 } else
1138 subs = notifs;
1139
1140 for (var i in subs) {
1141 var sub = subs[i];
1142
1143 if (sub.options) {
1144 if (typeof sub.options['pubsub#deliver'] != 'undefined' && !sub.options['pubsub#deliver'])
1145 continue;
1146
1147 if (typeof sub.options['pubsub#digest'] != 'undefined' && sub.options['pubsub#digest']) {
1148 if (!sub.digest)
1149 sub.digest = [];
1150 sub.digest.push(ev)
1151
1152 if (sub.digestTimeout)
1153 continue;
1154
1155 var freq;
1156 if (typeof sub.options['pubsub#digest_frequency'] == 'undefined')
1157 freq = 0;
1158 else
1159 freq = parseInt(sub.options['pubsub#digest_frequency']);
1160
1161 if (freq == 0)
1162 freq = 24*60*60*1000;
1163
1164 setTimeout(sendDigest, freq, notifs[i], nodeID);
1165 sub.digestTimeout = true;
1166 continue;
1167 }
1168 }
1169
1170 var message = xmpp.message({to: i, from: componentJID, id: conn.getUniqueId()});
1171 message.cnode(ev);
1172 conn.send(message);
1173 }
1174 }
1175
1176 function sendDigest(jid, nodeID) {
1177 var sub = storage.getSubscription(jid, nodeID);
1178 if (sub.digestTimeout)
1179 sub.digestTimeout = false;
1180
1181 var message = xmpp.message({to: jid, from: componentJID, id: conn.getUniqueId()});
1182 for (var i in sub.digest)
1183 message.cnode(sub.digest[i]);
1184 conn.send(message);
1185 }
1186
1187 conn.connect(componentJID, componentPassword, function (status, condition) { 165 conn.connect(componentJID, componentPassword, function (status, condition) {
1188 if (status == xmpp.Status.CONNECTED) { 166 if (status == xmpp.Status.CONNECTED) {
1189 conn.addHandler(onMessage, null, 'message', null, null, null); 167 conn.addHandler(onMessage, null, 'message', null, null, null);
1190 conn.addHandler(onIq, null, 'iq', null, null, null); 168 conn.addHandler(onIq, null, 'iq', null, null, null);
1191 conn.addHandler(onPresence, null, 'presence', null, null, null); 169 conn.addHandler(onPresence, null, 'presence', null, null, null);