comparison psgxs.js @ 0:9ee956af41e3

Initial commit
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sun, 27 Jun 2010 22:05:12 +0200
parents
children c2954a9e5665
comparison
equal deleted inserted replaced
-1:000000000000 0:9ee956af41e3
1 #!/usr/bin/env node
2
3 /*
4 * Copyright (C) 2010 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
5 *
6 * This file is part of PSĜS, a PubSub server written in JavaScript.
7 *
8 * PSĜS is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as
10 * published by the Free Software Foundation, either version 3 of the
11 * License.
12 *
13 * PSĜS is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with PSĜS. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 var sys = require('sys');
23 var xmpp = require('xmpp');
24 var sha1 = require('sha1');
25 require('./iso8601');
26 var storage = require('./storage');
27 var errors = require('./errors');
28 var utils = require('./util');
29 var toBareJID = utils.toBareJID;
30 var config = require('./configuration');
31 var forms = require('./forms');
32 var conn = new xmpp.Connection();
33
34 var service_configuration = config.service_configuration;
35 var componentJID = config.jid;
36 var componentPassword = config.password;
37
38 conn.log = function (_, m) { sys.puts(m); };
39
40 function _(m, c) {
41 if (c)
42 sys.print('\033[1;'+c+'m');
43 sys.print(sys.inspect(m, false, null));
44 if (c)
45 sys.print('\033[0m');
46 sys.puts('');
47 };
48
49 function onIq(stanza) {
50 var type = stanza.getAttribute('type');
51 var from = stanza.getAttribute('to');
52 var to = stanza.getAttribute('from');
53 var id = stanza.getAttribute('id');
54
55 var response;
56 if (id)
57 response = xmpp.iq({to: to, from: from, type: 'result', id: id});
58 else
59 response = xmpp.iq({to: to, from: from, type: 'result'});
60
61 if (type == 'get') {
62 if (stanza.getChild('query', 'jabber:iq:version')) {
63 var os = 'GNU/Linux';
64 var query = xmpp.stanza('query', {xmlns: 'jabber:iq:version'})
65 .s('name').t('PubSubJS')
66 .s('version').t('Pas releasé')
67 .s('os').t(os);
68 response.cx(query);
69
70 // SECTION 5.1
71 } else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#info')) {
72 var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#info');
73 var nodeID = query.getAttribute('node');
74
75 // SECTION 5.3
76 if (nodeID && nodeID != '') {
77 if (!storage.existsNode(nodeID))
78 return makeError(response, errors.node_does_not_exist.n);
79
80 var conf = storage.getConfiguration(nodeID);
81 if (typeof conf == 'number')
82 return makeError(response, conf);
83
84 var type = 'leaf'
85 if (conf['pubsub#node_type'])
86 type = conf['pubsub#node_type'];
87
88 var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info'})
89 q.s('identity', {category: 'pubsub', type: type});
90 q.s('feature', {'var': 'http://jabber.org/protocol/pubsub'});
91
92 // SECTION 5.4
93 if (config.enabled('meta-data')) {
94 var x = forms.build('result', 'meta-data', storage.getMetadata(nodeID), true);
95 if (x)
96 q.cx(x);
97 }
98 response.cx(q);
99
100 // SECTION 5.1
101 } else {
102 var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info'})
103 .s('identity', {category: 'pubsub', type: 'service', name: 'PubSub JavaScript Server'})
104 .s('feature', {'var': 'http://jabber.org/protocol/disco#info'})
105 .s('feature', {'var': 'http://jabber.org/protocol/disco#items'})
106 .s('feature', {'var': 'http://jabber.org/protocol/pubsub'})
107 // .s('feature', {'var': 'http://jabber.org/protocol/commands'})
108 for (var i in config.activated)
109 if (typeof i == 'string')
110 q.s('feature', {'var': 'http://jabber.org/protocol/pubsub#' + config.activated[i]});
111 response.cx(q);
112 }
113
114 // SECTION 5.2
115 } else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#items')) {
116 var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#items');
117 var q;
118 var children;
119 var nodeID = query.getAttribute('node');
120 if (nodeID && nodeID != '') {
121 if (!storage.existsNode(nodeID))
122 return makeError(response, errors.node_does_not_exist.n);
123
124 q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items', node: nodeID});
125
126 children = storage.getChildren(nodeID);
127 if (typeof children == 'number')
128 return makeError(response, children);
129 } else {
130 q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items'});
131
132 children = storage.getChildren();
133 if (typeof children == 'number')
134 return makeError(response, children);
135 }
136
137 for (var i in children) {
138 var attr = {jid: componentJID};
139 if (children[i] == 'node') {
140 if (config.enabled('meta-data')) {
141 var metadata = storage.getMetadata(i);
142 if (metadata['pubsub#title'])
143 attr.name = metadata['pubsub#title'];
144 }
145 attr.node = i;
146
147 // SECTION 5.5
148 } else
149 attr.name = i;
150
151 q.s('item', attr);
152 }
153 response.cx(q);
154 } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) {
155 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
156
157 // SECTION 5.6
158 if (pubsub.getChild('subscriptions')) {
159 if (!config.enabled('retrieve-subscriptions'))
160 return makeError(response, errors.subscriptions_retrieval_not_supported.n);
161
162 var subscriptions = pubsub.getChild('subscriptions');
163 var subs;
164
165 var nodeID = subscriptions.getAttribute('node');
166 if (nodeID && nodeID != '') {
167 if (!storage.existsNode(nodeID))
168 return makeError(response, errors.node_does_not_exist.n);
169 subs = storage.getSubscription(toBareJID(to), node);
170 } else
171 subs = storage.getSubscription(toBareJID(to));
172
173 var s = xmpp.stanza('subscriptions');
174 for (i in subs)
175 s.s('subscription', {node: i, jid: to, subscription: subs[i].type, subid: subs[i].subid});
176
177 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
178 p.cx(s);
179 response.cx(p);
180
181 // SECTION 5.7
182 } else if (pubsub.getChild('affiliations')) {
183 if (!config.enabled('retrieve-affiliations'))
184 return makeError(response, errors.affiliations_retrieval_not_supported.n);
185
186 var affiliations = pubsub.getChild('affiliations');
187 var nodeID = affiliations.getAttribute('node');
188 var affils;
189 if (nodeID && nodeID != '') {
190 if (!storage.existsNode(nodeID))
191 return makeError(response, errors.node_does_not_exist.n);
192 affils = storage.getAffiliation(toBareJID(to), nodeID);
193 } else
194 affils = storage.getAffiliationsFromJID(toBareJID(to));
195 var s = xmpp.stanza('affiliations');
196 for (i in affils)
197 s.s('affiliation', {node: i, affiliation: affils[i]});
198 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
199 p.cx(s);
200 response.cx(p);
201
202 // SECTION 6.3.2
203 } else if (pubsub.getChild('options')) {
204 if (!config.enabled('subscription-options'))
205 return makeError(response, errors.sub.configure.subscription_options_not_supported.n);
206
207 var options = pubsub.getChild('options');
208
209 var nodeID = options.getAttribute('node');
210 if (!nodeID || nodeID == '')
211 return makeError(response, errors.nodeid_required.n);
212 if (!storage.existsNode(nodeID))
213 return makeError(response, errors.node_does_not_exist.n);
214
215 var jid = options.getAttribute('jid');
216 if (!jid)
217 return makeError(response, errors.sub.configure.subscriber_jid_required.n);
218 if (toBareJID(jid) != toBareJID(to))
219 return makeError(response, errors.sub.configure.insufficient_privileges.n);
220
221 var subs = storage.getSubscription(jid, nodeID);
222 if (subs == {})
223 return makeError(response, errors.sub.configure.no_such_subscriber.n);
224
225 var s = xmpp.stanza('options', {node: nodeID, jid: jid});
226 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
227 var form = forms.build('form', 'subscribe_options', subs.options, true);
228 s.cx(form);
229 p.cx(s);
230 response.cx(p);
231
232 // SECTION 6.4
233 } else if (pubsub.getChild('default')) {
234 if (!config.enabled('retrieve-default-sub'))
235 return makeError(response, errors.sub.default_options.default_subscription_configuration_retrieval_not_supported.n);
236
237 var def = pubsub.getChild('default');
238
239 var nodeID = def.getAttribute('node');
240 if (nodeID && !storage.existsNode(nodeID))
241 return makeError(response, errors.node_does_not_exist.n);
242
243 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
244 var s;
245 if (nodeID)
246 s = xmpp.stanza('default', {node: nodeID});
247 else
248 s = xmpp.stanza('default');
249
250 var form = forms.build('form', 'subscribe_options', 'default', false);
251 s.cx(form);
252 p.cx(s);
253 response.cx(p);
254
255 // SECTION 6.5
256 } else if (pubsub.getChild('items')) {
257 if (!config.enabled('retrieve-items'))
258 return makeError(response, errors.sub.default_options.node_configuration_not_supported.n);
259
260 var items = pubsub.getChild('items');
261
262 var nodeID = items.getAttribute('node');
263 if (!nodeID || nodeID == '')
264 return makeError(response, errors.nodeid_required.n);
265 if (!storage.existsNode(nodeID))
266 return makeError(response, errors.node_does_not_exist.n);
267
268 var affil = storage.getAffiliation(toBareJID(to), nodeID);
269 if (affil != 'owner' && affil != 'publisher' && affil != 'member')
270 return makeError(response, errors.pub.publish.insufficient_privileges.n);
271
272 var item = [];
273 for (var i=0; i<items.children.length; i++) {
274 var j = items.children[i];
275 if (j.name == 'item' && j.attr['id'] && j.attr['id'] != '')
276 item.push(j.attr['id']);
277 }
278
279 var max_items = items.getAttribute('max_items');
280 if (max_items)
281 max_items = Number (max_items);
282
283 if (item.length) {
284 var s = xmpp.stanza('items', {node: nodeID});
285
286 for (var i=0; i<item.length; i++) {
287 var j = storage.getItem(nodeID, item[i]);
288 if (typeof j == 'number')
289 return makeError(response, j);
290
291 var k = xmpp.stanza('item', {id: item[i]})
292 k.cx(j);
293 s.cx(k);
294 }
295 } else {
296 var s = xmpp.stanza('items', {node: nodeID});
297
298 var j;
299 if (max_items)
300 j = storage.getLastItem(nodeID, max_items);
301 else
302 j = storage.getItems(nodeID);
303 if (typeof j == 'number')
304 return makeError(response, j);
305
306 var k = 0;
307 for (var i in j) {
308 var contentItem = xmpp.stanza('item', {id: i}).t(j[i].content);
309 s.cx(contentItem);
310 }
311 }
312 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
313 p.cx(s);
314 response.cx(p);
315 } else
316 return makeError(response, errors.feature_not_implemented.n);
317 } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
318 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
319
320 // SECTION
321 if (pubsub.getChild('configure')) {
322 if (!config.enabled('config-node'))
323 return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
324
325 var nodeID = pubsub.getChild('configure').getAttribute('node');
326 if (!nodeID || nodeID == '')
327 return makeError(response, errors.nodeid_required.n);
328 if (!storage.existsNode(nodeID))
329 return makeError(response, errors.node_does_not_exist.n);
330
331 var affil = storage.getAffiliation(toBareJID(to), nodeID);
332 if (affil != 'owner' && affil != 'publish-only')
333 return makeError(response, errors.pub.publish.insufficient_privileges.n);
334
335 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
336 var s = xmpp.stanza('configure', {node: nodeID});
337 var form = forms.build('form', 'node_config', 'default', true);
338 s.cx(form);
339 p.cx(s);
340 response.cx(p);
341
342 // SECTION
343 } else if (pubsub.getChild('default')) {
344 if (!config.enabled('config-node'))
345 return makeError(response, errors.owner.default_options.node_configuration_not_supported.n);
346
347 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
348 var s = xmpp.stanza('default');
349 var form = forms.build('node_config', service_configuration.node_config, null, true);
350 s.cx(form);
351 p.cx(s);
352 response.cx(p);
353
354 // SECTION
355 } else if (pubsub.getChild('subscriptions')) {
356 if (!config.enabled('manage-subscriptions'))
357 return makeError(response, errors.owner.manage_subscriptions.not_supported.n);
358
359 var subscriptions = pubsub.getChild('subscriptions');
360
361 var nodeID = subscriptions.getAttribute('node');
362 if (!nodeID || nodeID == '')
363 return makeError(response, errors.nodeid_required.n);
364 if (!storage.existsNode(nodeID))
365 return makeError(response, errors.node_does_not_exist.n);
366
367 if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
368 return makeError(response, errors.forbidden.n);
369
370 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
371 var s = xmpp.stanza('subscriptions', {node: nodeID});
372
373 var subs = storage.getSubscriptionsFromNodeID(nodeID)
374 for (var jid in subs)
375 s.s('subscription', {jid: jid, subscription: subs[jid].type, subid: subs[jid].subid})
376
377 p.cx(s);
378 response.cx(p);
379
380 // SECTION
381 } else if (pubsub.getChild('affiliations')) {
382 if (!config.enabled('modify-affiliations'))
383 return makeError(response, errors.owner.manage_affiliations.not_supported.n);
384
385 var affiliations = pubsub.getChild('affiliations');
386
387 var nodeID = affiliations.getAttribute('node');
388 if (!nodeID || nodeID == '')
389 return makeError(response, errors.nodeid_required.n);
390 if (!storage.existsNode(nodeID))
391 return makeError(response, errors.node_does_not_exist.n);
392
393 var affil = storage.getAffiliationsFromNodeID(nodeID);
394 if (affil[toBareJID(to)] != 'owner')
395 return makeError(response, errors.owner.manage_affiliations.retrieve_list.entity_is_not_an_owner.n);
396
397 var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
398 var s = xmpp.stanza('affiliations', {node: nodeID});
399
400 for (var jid in affil)
401 s.s('affiliation', {jid: jid, affiliation: affil[jid]})
402
403 p.cx(s);
404 response.cx(p);
405 } else
406 return makeError(response, errors.feature_not_implemented.n);
407 } else
408 return makeError(response, errors.feature_not_implemented.n);
409 } else if (type == 'set') {
410 if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) {
411 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
412
413 // SECTION 6.1
414 if (pubsub.getChild('subscribe')) {
415 if (!config.enabled('subscribe'))
416 return makeError(response, errors.sub.subscribe.not_supported.n);
417
418 var subscribe = pubsub.getChild('subscribe');
419
420 var nodeID = subscribe.getAttribute('node');
421 if (!nodeID || nodeID == '')
422 return makeError(response, errors.nodeid_required.n);
423 if (!storage.existsNode(nodeID))
424 return makeError(response, errors.node_does_not_exist.n);
425
426 var configuration = storage.getConfiguration(nodeID);
427 if (!configuration['pubsub#subscribe'])
428 return makeError(response, errors.sub.subscribe.not_supported.n);
429
430 var affil = storage.getAffiliation(toBareJID(to), nodeID);
431 if (affil == 'publish-only' || affil == 'outcast')
432 return makeError(response, errors.pub.publish.insufficient_privileges.n);
433
434 var jid = subscribe.getAttribute('jid');
435 if (!jid || toBareJID(jid) != toBareJID(to))
436 return makeError(response, errors.sub.subscribe.jids_do_not_match.n);
437
438 // SECTION 6.3.7
439 var options = pubsub.getChild('options');
440 if (options && config.enabled('subscription-options')) {
441 if (options.getAttribute('node') || options.getAttribute('jid'))
442 return makeError(response, errors.bad_request.n);
443
444 var x = options.getChild('x', 'jabber:x:data');
445 if (!x || x.getAttribute('type') != 'submit')
446 return makeError(response, errors.bad_request.n);
447
448 var form = forms.parse(x, true);
449 if (typeof form == 'number')
450 return makeError(response, form);
451
452 var conf = form;
453 }
454
455 var subID;
456 if (configuration['pubsub#access_model'] == 'open') {
457 subID = storage.subscribe(nodeID, jid, 'subscribe', conf);
458 if (typeof subID == 'number')
459 return makeError(response, subID);
460 } else if (configuration['pubsub#access_model'] == 'authorize') {
461 subID = storage.subscribe(nodeID, jid, 'pending', conf);
462 if (typeof subID == 'number')
463 return makeError(response, subID);
464 } else if (configuration['pubsub#access_model'] == 'whitelist') {
465 var affil = storage.getAffiliation(jid, nodeID);
466 if (affil != 'owner' && affil != 'publisher' && affil != 'member')
467 return makeError(response, errors.sub.subscribe.not_on_whitelist.n);
468
469 subID = storage.subscribe(nodeID, jid, conf);
470 if (typeof subID == 'number')
471 return makeError(response, subID);
472 }
473
474 response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
475 .c('subscription', {node: nodeID, jid: jid, subid: subID.subid, subscription: subID.type});
476
477 if (conf)
478 response.cx(options);
479
480 if (config.enabled('get-pending')) {
481 var affiliates = storage.getAffiliationsFromNodeID(nodeID);
482 var form = forms.build('form', 'subscribe_authorization', {allow: false, node: nodeID, subscriber_jid: jid}, true); //168
483 for (var i in affiliates) {
484 if (affiliates[i] == 'owner') {
485 var message = xmpp.message({to: i}).cx(form);
486 conn.send(message);
487 }
488 }
489 }
490
491 if (config.enabled('last-published')) {
492 var last = storage.getLastItem(nodeID);
493 if (typeof last != 'number') {
494 var item = storage.getItem(nodeID, last);
495 if (typeof item != 'number') {
496 var attr = {};
497 attr[last] = {content: item};
498 sendNotifs(jid, 'items', nodeID, attr);
499 }
500 }
501 }
502
503 // SECTION 6.2
504 } else if (pubsub.getChild('unsubscribe')) {
505 if (!config.enabled('subscribe'))
506 return makeError(response, errors.sub.subscribe.not_supported.n);
507
508 var unsubscribe = pubsub.getChild('unsubscribe');
509 var nodeID = unsubscribe.getAttribute('node');
510 if (!nodeID || nodeID == '')
511 return makeError(response, errors.nodeid_required.n);
512 if (!storage.existsNode(nodeID))
513 return makeError(response, errors.node_does_not_exist.n);
514
515 var jid = unsubscribe.getAttribute('jid');
516 if (!jid || toBareJID(jid) != toBareJID(to))
517 return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n);
518
519 var subID = storage.subscribe(nodeID, jid, 'none');
520 if (typeof subID == 'number')
521 return makeError(response, subID);
522
523 // SECTIONS 6.3.5
524 } else if (pubsub.getChild('options')) {
525 if (!config.enabled('subscription-options'))
526 return makeError(response, errors.sub.subscribe.not_supported.n);
527
528 var options = pubsub.getChild('options');
529
530 var nodeID = unsubscribe.getAttribute('node');
531 if (!nodeID || nodeID == '')
532 return makeError(response, errors.nodeid_required.n);
533 if (!storage.existsNode(nodeID))
534 return makeError(response, errors.node_does_not_exist.n);
535
536 var jid = unsubscribe.getAttribute('jid');
537 if (!jid || toBareJID(jid) != toBareJID(to))
538 return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n);
539
540 var x = options.getChild('x', 'jabber:x:data');
541 if (!x || x.getAttribute(type) != 'submit')
542 return makeError(response, errors.bad_request);
543
544 var form = forms.parse(x, true);
545 if (typeof form == 'number')
546 return makeError(response, form);
547
548 var set = storage.configureSubscription(nodeID, jid, form);
549 if (typeof form == 'number')
550 return makeError(response, form);
551
552 // SECTION
553 } else if (pubsub.getChild('publish')) {
554 if (!config.enabled('publish'))
555 return makeError(response, errors.pub.publish.item_publication_not_supported.n);
556
557 var publish = pubsub.getChild('publish');
558 var nodeID = publish.getAttribute('node');
559 if (!nodeID || nodeID == '')
560 return makeError(response, errors.nodeid_required.n);
561
562 var affil = storage.getAffiliation(toBareJID(to), nodeID);
563 if (typeof affil == 'number')
564 return makeError(response, affil);
565 if (affil != 'owner' && affil != 'publisher' && affil != 'publish-only')
566 return makeError(response, errors.forbidden.n);
567
568 var item = publish.getChild('item');
569 var itemID = item.getAttribute('id');
570 if (!config.enabled('item-ids') && itemID)
571 return makeError(response, errors.itemid_required.n);
572 itemID = itemID? itemID: utils.makeRandomId();
573
574 if (item.tags.length != 1)
575 return makeError(response, errors.pub.publish.bad_payload.n);
576
577 var autocreate = false;
578 if (!storage.existsNode(nodeID)) {
579 if (config.enabled('auto-create'))
580 autocreate = true;
581 else
582 return makeError(response, errors.node_does_not_exist.n);
583 }
584
585 var conf = storage.getConfiguration(nodeID);
586 var publishOptions = pubsub.getChild('publish-options');
587 if (publishOptions && config.enabled('publish-options')) {
588 var x = publishOptions.getChild('x', 'jabber:x:data');
589 if (!x || x.getAttribute('type') != 'submit')
590 return makeError(response, errors.bad_request.n);
591
592 var form = forms.parse(x, true);
593 if (form.access_model != conf['pubsub#access_model'] && !autocreate)
594 return makeError(response, errors.pub.configuration.precondition.n);
595 }
596
597 if (!config.enabled('persistent-items')) {
598 var notifs = storage.purgeNode(nodeID);
599 if (typeof notifs == 'number')
600 return makeError(response, r);
601 }
602
603 if (autocreate)
604 storage.createNode(nodeID, form);
605
606 var content = item.getChild();
607 subscribers = storage.setItem(nodeID, itemID, content);
608
609 if (typeof subscribers == 'number')
610 return makeError(response, subscribers);
611
612 var attrs = {};
613 if (content)
614 attrs[itemID] = {content: content};
615 else
616 attrs[itemID] = {};
617 sendNotifs(subscribers, 'items', nodeID, attrs);
618
619 response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
620 .c('publish', {node: nodeID})
621 .c('item', {id: itemID});
622
623 // SECTION
624 } else if (pubsub.getChild('retract')) {
625 if (!config.enabled('retract-items'))
626 return makeError(response, errors.pub.retract.item_deletion_not_supported.n);
627
628 var retract = pubsub.getChild('retract');
629
630 var nodeID = retract.getAttribute('node');
631 if (!nodeID || nodeID == '')
632 return makeError(response, errors.nodeid_required.n);
633 if (!storage.existsNode(nodeID))
634 return makeError(response, errors.node_does_not_exist.n);
635
636 var item = retract.getChild('item');
637 if (!item)
638 return makeError(response, errors.pub.retract.item_or_itemid_required.n);
639
640 var itemID = item.getAttribute('id')
641 if (!itemID || itemID == '')
642 return makeError(response, errors.pub.retract.item_or_itemid_required.n);
643
644 var subscribers = storage.deleteItem(nodeID, itemID);
645 if (typeof subscribers == 'number')
646 return makeError(response, subscribers);
647
648 var attrs = {};
649 attrs[itemID] = {};
650 sendNotifs(subscribers, 'items', nodeID, attrs, 'retract')
651
652 // SECTION
653 } else if (pubsub.getChild('create')) {
654 if (!config.enabled('create-nodes'))
655 return makeError(response, errors.owner.create.node_creation_not_supported.n);
656
657 var instant = false;
658
659 var nodeID = pubsub.getChild('create').getAttribute('node');
660 if (!nodeID || nodeID == '') {
661 if (config.enabled('instant-nodes'))
662 return makeError(response, errors.owner.create.instant_nodes_not_supported.n);
663 nodeID = utils.makeRandomId();
664 instant = true;
665 }
666 if (storage.existsNode(nodeID))
667 return makeError(response, errors.nodeid_already_exists.n);
668
669 var affil = storage.getAffiliation(toBareJID(to), nodeID);
670 if (affil != 'owner' && affil != 'publish-only')
671 return makeError(response, errors.forbidden.n);
672
673 var configure = pubsub.getChild('configure');
674 if (configure && config.enabled('create-and-configure')) {
675 if (!config.enabled('config-node'))
676 return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
677
678 if (configure.getAttribute('node'))
679 return makeError(response, errors.bad_request.n);
680
681 var x = configure.getChild('x', 'jabber:x:data');
682 if (!x || x.getAttribute('type') != 'submit')
683 return makeError(response, errors.bad_request.n);
684
685 var form = forms.parse(x, true);
686 if (typeof form == 'number')
687 return makeError(response, form);
688
689 var conf = form;
690 }
691
692 var r = storage.createNode(nodeID, conf);
693 if (typeof r == 'number')
694 return makeError(response, r);
695
696 if (instant)
697 response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
698 .c('create', {node: nodeID});
699 } else
700 return makeError(response, errors.feature_not_implemented.n);
701 } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
702 var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
703
704 // SECTION
705 if (pubsub.getChild('configure')) {
706 if (!config.enabled('config-node'))
707 return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
708
709 var nodeID = configure.getAttribute('node');
710 if (!nodeID)
711 return makeError(response, errors.nodeid_required.n);
712 if (!storage.existsNode(nodeID))
713 return makeError(response, errors.node_does_not_exist.n);
714
715 var affil = storage.getAffiliation(toBareJID(to), nodeID);
716 if (affil != 'owner' && affil != 'publish-only')
717 return makeError(response, errors.forbidden.n);
718
719 var x = configure.getChild('x', 'jabber:x:data');
720 if (!x || x.getAttribute(type) != 'submit')
721 return makeError(response, errors.bad_request.n);
722
723 var form = forms.parse(x, true);
724 if (typeof form == 'number')
725 return makeError(response, form);
726
727 var conf = form;
728
729 var set = storage.configure(nodeID, conf);
730 if (typeof set == 'number')
731 return makeError(response, set);
732
733 // SECTION
734 } else if (pubsub.getChild('delete')) {
735 if (!config.enabled('delete-nodes'))
736 return makeError(response, errors.feature_not_implemented.n); //XXX
737
738 var del = pubsub.getChild('delete');
739
740 var nodeID = del.getAttribute('node');
741 if (!nodeID)
742 return makeError(response, errors.nodeid_required.n);
743 if (!storage.existsNode(nodeID))
744 return makeError(response, errors.node_does_not_exist.n);
745
746 if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
747 return makeError(response, errors.forbidden.n);
748
749 var notifs = storage.deleteNode(nodeID);
750 if (typeof notifs == 'number')
751 return makeError(response, r);
752
753 sendNotifs(notifs, 'delete', nodeID);
754
755 // SECTION
756 } else if (pubsub.getChild('purge')) {
757 if (!config.enabled('purge-nodes'))
758 return makeError(response, errors.owner.purge.node_purging_not_supported.n); //XXX
759
760 var purge = pubsub.getChild('purge');
761
762 var nodeID = purge.getAttribute('node');
763 if (!nodeID)
764 return makeError(response, errors.nodeid_required.n);
765 if (!storage.existsNode(nodeID))
766 return makeError(response, errors.node_does_not_exist.n);
767
768 if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
769 return makeError(response, errors.forbidden.n);
770
771 if (!config.enabled('persistent-items')) //FIXME: autre condition, supporté par le node
772 return makeError(response, errors.owner.purge.node_does_not_persist_items.n);
773
774 var notifs = storage.purgeNode(nodeID);
775 if (typeof notifs == 'number')
776 return makeError(response, r);
777
778 sendNotifs(notifs, 'purge', nodeID);
779
780 // SECTION
781 } else if (pubsub.getChild('subscriptions')) {
782 if (!config.enabled('manage-subscriptions'))
783 return makeError(response, errors.owner.manage_subscriptions.not_supported.n); //XXX
784
785 var subscriptions = pubsub.getChild('subscriptions');
786
787 var nodeID = subscriptions.getAttribute('node');
788 if (!nodeID)
789 return makeError(response, errors.nodeid_required.n);
790 if (!storage.existsNode(nodeID))
791 return makeError(response, errors.node_does_not_exist.n);
792
793 if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
794 return makeError(response, errors.forbidden.n);
795
796 var e = false;
797 for (i in subscriptions.tags) {
798 var jid = subscriptions.tags[i].getAttribute('jid');
799 var subscription = subscriptions.tags[i].getAttribute('subscription');
800
801 var set = storage.subscribe(nodeID, jid, subscription);
802 if (typeof set == 'number')
803 e = true;
804 else {
805 sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: subscription});
806 subscriptions.tags.splice(i, 1);
807 }
808 }
809
810 if (e)
811 return makeError(response, errors.owner.manage_subscriptions.modify.multiple_simultaneous_modifications.n, pubsub);
812
813 // SECTION
814 } else if (pubsub.getChild('affiliations')) {
815 if (!config.enabled('modify-affiliations'))
816 return makeError(response, errors.owner.manage_affiliations.not_supported.n); //XXX
817
818 var affiliations = pubsub.getChild('affiliations');
819
820 var nodeID = affiliations.getAttribute('node');
821 if (!nodeID)
822 return makeError(response, errors.nodeid_required.n);
823 if (!storage.existsNode(nodeID))
824 return makeError(response, errors.node_does_not_exist.n);
825
826 if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
827 return makeError(response, errors.forbidden.n);
828
829 var e = false;
830 for (i in affiliations.children) {
831 var jid = affiliations.children[i].getAttribute('jid');
832 var affiliation = affiliations.children[i].getAttribute('affiliation');
833
834 var set = storage.setAffiliation(nodeID, jid, affiliation);
835 if (typeof set == 'number')
836 e = true;
837 else
838 affiliations.children.splice(i, 1);
839 }
840
841 if (e)
842 return makeError(response, errors.owner.manage_affiliations.modify.multiple_simultaneous_modifications.n, pubsub);
843 } else
844 return makeError(response, errors.feature_not_implemented.n);
845 } else
846 return makeError(response, errors.feature_not_implemented.n);
847 } else
848 return makeError(response, errors.feature_not_implemented.n);
849 conn.send(response);
850 }
851
852 function onMessage(stanza) {
853 var from = stanza.getAttribute('to');
854 var to = stanza.getAttribute('from');
855 var id = stanza.getAttribute('id');
856
857 var response;
858 if (id)
859 response = xmpp.message({to: to, from: from, id: id});
860 else
861 response = xmpp.message({to: to, from: from});
862
863 var x = stanza.getChild('x', 'jabber:x:data');
864 if (x) {
865 var form = forms.parse(x);
866 if (form.type == 'submit' && form.fields.FORM_TYPE.value == 'subscribe_authorization') {
867 if (form.fields.subid && form.fields.subid.value)
868 var subID = form.fields.subid.value;
869 if (form.fields.node && form.fields.node.value)
870 var nodeID = form.fields.node.value;
871 if (form.fields.subscriber_jid && form.fields.subscriber_jid.value)
872 var jid = form.fields.subscriber_jid.value;
873 if (form.fields.allow && form.fields.allow.value)
874 var allow = form.fields.allow.value;
875
876 var type = allow? 'subscribed': 'none';
877 var set = storage.subscribe(nodeID, jid, type)
878 //if (set.subid != subID) //TODO: support the multi-subscribe feature
879 sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: type});
880 } else
881 return makeError(response, errors.feature_not_implemented.n);
882 } else
883 return makeError(response, errors.feature_not_implemented.n);
884 conn.send(response)
885 }
886
887 function onPresence(stanza) {
888 var from = stanza.getAttribute('to');
889 var to = stanza.getAttribute('from');
890 var id = stanza.getAttribute('id');
891
892 var response;
893 if (id)
894 response = xmpp.presence({to: to, from: from, id: id});
895 else
896 response = xmpp.presence({to: to, from: from});
897
898 makeError(response, errors.feature_not_implemented.n);
899 }
900
901 function makeError(response, errorNumber, payload) {
902 response.attr.type = 'error';
903 if (payload)
904 response.cx(payload);
905
906 var e = errors.reverse[errorNumber];
907 var error = xmpp.stanza('error', {type: e.type});
908
909 error.s(e.error, {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'})
910
911 if (e.reason) {
912 if (e.feature)
913 error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors', feature: e.feature});
914 else
915 error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors'});
916 }
917
918 response.cx(error);
919 conn.send(response);
920 }
921
922 function sendNotifs(notifs, type, nodeID, a1, a2) {
923 var ev = xmpp.stanza('event', {xmlns: 'http://jabber.org/protocol/pubsub#event'});
924
925 if (type == 'collection') {
926 var collection = xmpp.stanza('collection', {node: nodeID});
927 if (a1 == 'associate')
928 collection.cx('associate', {node: nodeID});
929 else
930 collection.cx('disassociate', {node: nodeID});
931 ev.cx(collection);
932 } else if (type == 'configuration') {
933 if (!config.enabled('config-node')) {
934 _('Error #4', 41)
935 return;
936 }
937
938 var configuration = xmpp.stanza('configuration', {node: nodeID});
939 if (a1) {
940 var x = forms.build('node_config', service_configuration.node_config, storage.getConfiguration(nodeID));
941 if (x)
942 configuration.cx(x); //TODO: voir exemple 150
943 }
944 ev.cx(configuration);
945 } else if (type == 'delete') {
946 var del = xmpp.stanza('delete', {node: nodeID});
947 if (a1)
948 del.c('redirect', {uri: a1});
949 ev.cx(del);
950 } else if (type == 'items') {
951 var items = xmpp.stanza(type, {node: nodeID});
952 if (a2 == 'retract')
953 for (var i in a1)
954 items.s('retract', {id: i});
955 else {
956 for (var i in a1) {
957 var item = a1[i];
958 var args = {};
959 if (i != '')
960 args.id = i;
961 if (item.node)
962 args.node = item.node;
963 if (item.publisher)
964 args.publisher = item.publisher;
965 var it = xmpp.stanza('item', args);
966 if (item.content)
967 it.cx(item.content);
968 items.cx(it);
969 }
970 }
971 ev.cx(items);
972 } else if (type == 'purge') {
973 ev.c('purge', {node: nodeID});
974 } else if (type == 'subscription') {
975 if (!config.enabled('subscription-notifications'))
976 return;
977
978 var args = {node: nodeID};
979 for (i in a1) {
980 var attr = a1[i];
981 if (i == 'subscription') {
982 if (attr == 'none' || attr == 'pending' || attr == 'subscribed' || attr == 'unconfigured')
983 args[i] = attr;
984 else {
985 _('Error #3', 41)
986 return;
987 }
988 } else if (i == 'jid' || i == 'subid')
989 args[i] = attr;
990 else if (i == 'expiry')
991 args[i] = attr.toString();
992 }
993 if (!args.jid || args.jid == '') {
994 _('Error #2', 41)
995 return;
996 }
997 var sub = xmpp.stanza('subscription', args);
998 ev.cx(sub);
999 } else {
1000 _('Error #1', 41)
1001 return;
1002 }
1003
1004 var subs;
1005 if (typeof notifs == 'string') {
1006 subs = {};
1007 subs[notifs] = storage.getSubscription(notifs, nodeID);
1008 } else
1009 subs = notifs;
1010
1011 for (var i in subs) {
1012 var sub = subs[i];
1013
1014 if (sub.options) {
1015 if (typeof sub.options['pubsub#deliver'] != 'undefined' && !sub.options['pubsub#deliver'])
1016 continue;
1017
1018 if (typeof sub.options['pubsub#digest'] != 'undefined' && sub.options['pubsub#digest']) {
1019 if (!sub.digest)
1020 sub.digest = [];
1021 sub.digest.push(ev)
1022
1023 if (sub.digestTimeout)
1024 continue;
1025
1026 var freq;
1027 if (typeof sub.options['pubsub#digest_frequency'] == 'undefined')
1028 freq = 0;
1029 else
1030 freq = parseInt(sub.options['pubsub#digest_frequency']);
1031
1032 if (freq == 0)
1033 freq = 24*60*60*1000;
1034
1035 setTimeout(sendDigest, freq, notifs[i], nodeID);
1036 sub.digestTimeout = true;
1037 continue;
1038 }
1039 }
1040
1041 var message = xmpp.message({to: i, from: componentJID, id: conn.getUniqueId()});
1042 message.cx(ev);
1043 conn.send(message);
1044 }
1045 }
1046
1047 function sendDigest(jid, nodeID) {
1048 var sub = storage.getSubscription(jid, nodeID);
1049 if (sub.digestTimeout)
1050 sub.digestTimeout = false;
1051
1052 var message = xmpp.message({to: jid, from: componentJID, id: conn.getUniqueId()});
1053 for (var i in sub.digest)
1054 message.cx(sub.digest[i]);
1055 conn.send(message);
1056 }
1057
1058 conn.connect(componentJID, componentPassword, function (status, condition) {
1059 if (status == xmpp.Status.CONNECTED) {
1060 conn.addHandler(onMessage, null, 'message', null, null, null);
1061 conn.addHandler(onIq, null, 'iq', null, null, null);
1062 conn.addHandler(onPresence, null, 'presence', null, null, null);
1063
1064 if (process.argv.length >= 3)
1065 storage.load(process.argv[2]);
1066 else
1067 storage.load();
1068
1069 var stdin = process.openStdin();
1070 stdin.setEncoding('utf8');
1071 stdin.addListener('data', storage.debug);
1072 } else
1073 conn.log(xmpp.LogLevel.DEBUG, 'New connection status: ' + status + (condition? (' ('+condition+')'): ''));
1074 });