Mercurial > psgxs
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 }); |