3 Jappix - An open social platform
4 This is the JSJaC library for Jappix (from trunk)
6 -------------------------------------------------
8 Licenses: Mozilla Public License version 1.1, GNU GPL, AGPL
9 Authors: Stefan Strigler, Vanaryon, Zash
10 Last revision: 25/08/11
15 * @fileoverview Magic dependency loading. Taken from script.aculo.us
16 * and modified to break it.
17 * @author Stefan Strigler steve@zeank.in-berlin.de
23 bind: function(fn, obj, optArg) {
24 return function(arg) {
25 return fn.apply(obj, [arg, optArg]);
30 if (typeof JSJaCConnection == 'undefined')
35 /* Copyright 2006 Erik Arvidsson
37 * Licensed under the Apache License, Version 2.0 (the "License"); you
38 * may not use this file except in compliance with the License. You
39 * may obtain a copy of the License at
41 * http://www.apache.org/licenses/LICENSE-2.0
43 * Unless required by applicable law or agreed to in writing, software
44 * distributed under the License is distributed on an "AS IS" BASIS,
45 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
46 * implied. See the License for the specific language governing
47 * permissions and limitations under the License.
51 * @fileoverview Wrapper to make working with XmlHttpRequest and the
52 * DOM more convenient (cross browser compliance).
53 * this code is taken from
54 * http://webfx.eae.net/dhtml/xmlextras/xmlextras.html
55 * @author Stefan Strigler steve@zeank.in-berlin.de
66 * creates a cross browser compliant XmlHttpRequest object
68 XmlHttp.create = function () {
70 // Are we cross-domain?
71 if(((BOSH_PROXY == 'on') || (HOST_BOSH_MINI)) && (typeof jXHR == "function")) {
73 if (window.XMLHttpRequest) {
74 var req = new XMLHttpRequest();
76 if (req.withCredentials !== undefined)
82 // Might be local-domain?
83 if (window.XMLHttpRequest) {
84 var req = new XMLHttpRequest();
86 // some versions of Moz do not support the readyState property
87 // and the onreadystate event so we patch it!
88 if (req.readyState == null) {
90 req.addEventListener("load", function () {
92 if (typeof req.onreadystatechange == "function")
93 req.onreadystatechange();
99 if (window.ActiveXObject) {
100 return new ActiveXObject(XmlHttp.getPrefix() + ".XmlHttp");
105 throw new Error("Your browser does not support XmlHttp objects");
109 * used to find the Automation server name
112 XmlHttp.getPrefix = function() {
113 if (XmlHttp.prefix) // I know what you did last summer
114 return XmlHttp.prefix;
116 var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
118 for (var i = 0; i < prefixes.length; i++) {
120 // try to create the objects
121 o = new ActiveXObject(prefixes[i] + ".XmlHttp");
122 return XmlHttp.prefix = prefixes[i];
127 throw new Error("Could not find an installed XML parser");
132 * XmlDocument factory
135 function XmlDocument() {}
137 XmlDocument.create = function (name,ns) {
138 name = name || 'foo';
144 if (document.implementation && document.implementation.createDocument) {
145 doc = document.implementation.createDocument(ns, name, null);
146 // some versions of Moz do not support the readyState property
147 // and the onreadystate event so we patch it!
148 if (doc.readyState == null) {
150 doc.addEventListener("load", function () {
152 if (typeof doc.onreadystatechange == "function")
153 doc.onreadystatechange();
156 } else if (window.ActiveXObject) {
157 doc = new ActiveXObject(XmlDocument.getPrefix() + ".DomDocument");
160 if (!doc.documentElement || doc.documentElement.tagName != name ||
161 (doc.documentElement.namespaceURI &&
162 doc.documentElement.namespaceURI != ns)) {
165 doc.appendChild(doc.createElement(name)).
166 setAttribute('xmlns',ns);
168 doc.appendChild(doc.createElement(name));
170 doc = document.implementation.createDocument(ns,name,null);
172 if (doc.documentElement == null)
173 doc.appendChild(doc.createElement(name));
175 // fix buggy opera 8.5x
177 doc.documentElement.getAttribute('xmlns') != ns) {
178 doc.documentElement.setAttribute('xmlns',ns);
186 throw new Error("Your browser does not support XmlDocument objects");
190 * used to find the Automation server name
193 XmlDocument.getPrefix = function() {
194 if (XmlDocument.prefix)
195 return XmlDocument.prefix;
197 var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
199 for (var i = 0; i < prefixes.length; i++) {
201 // try to create the objects
202 o = new ActiveXObject(prefixes[i] + ".DomDocument");
203 return XmlDocument.prefix = prefixes[i];
208 throw new Error("Could not find an installed XML parser");
212 // Create the loadXML method
213 if (typeof(Document) != 'undefined' && window.DOMParser) {
216 * XMLDocument did not extend the Document interface in some
217 * versions of Mozilla.
220 Document.prototype.loadXML = function (s) {
222 // parse the string to a new doc
223 var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
225 // remove all initial children
226 while (this.hasChildNodes())
227 this.removeChild(this.lastChild);
229 // insert and import nodes
230 for (var i = 0; i < doc2.childNodes.length; i++) {
231 this.appendChild(this.importNode(doc2.childNodes[i], true));
236 // Create xml getter for Mozilla
237 if (window.XMLSerializer &&
238 window.Node && Node.prototype && Node.prototype.__defineGetter__) {
243 * This serializes the DOM tree to an XML String
245 * Usage: var sXml = oNode.xml
249 // XMLDocument did not extend the Document interface in some versions
250 // of Mozilla. Extend both!
251 XMLDocument.prototype.__defineGetter__("xml", function () {
252 return (new XMLSerializer()).serializeToString(this);
257 * This serializes the DOM tree to an XML String
259 * Usage: var sXml = oNode.xml
263 Document.prototype.__defineGetter__("xml", function () {
264 return (new XMLSerializer()).serializeToString(this);
270 * This serializes the DOM tree to an XML String
272 * Usage: var sXml = oNode.xml
276 Node.prototype.__defineGetter__("xml", function () {
277 return (new XMLSerializer()).serializeToString(this);
283 * @fileoverview Collection of functions to make live easier
284 * @author Stefan Strigler
285 * @version $Revision$
289 * Convert special chars to HTML entities
291 * @return The string with chars encoded for HTML
294 String.prototype.htmlEnc = function() {
298 var str = this.replace(/&/g,"&");
299 str = str.replace(/</g,"<");
300 str = str.replace(/>/g,">");
301 str = str.replace(/\"/g,""");
302 str = str.replace(/\n/g,"<br />");
307 * Convert HTML entities to special chars
309 * @return The normal string
312 String.prototype.revertHtmlEnc = function() {
316 var str = this.replace(/&/gi,'&');
317 str = str.replace(/</gi,'<');
318 str = str.replace(/>/gi,'>');
319 str = str.replace(/"/gi,'\"');
320 str = str.replace(/<br( )?(\/)?>/gi,'\n');
325 * Converts from jabber timestamps to JavaScript Date objects
327 * @param {String} ts A string representing a jabber datetime timestamp as
328 * defined by {@link http://www.xmpp.org/extensions/xep-0082.html XEP-0082}
329 * @return A javascript Date object corresponding to the jabber DateTime given
332 Date.jab2date = function(ts) {
334 var date = new Date(Date.UTC(ts.substr(0,4),ts.substr(5,2)-1,ts.substr(8,2),ts.substr(11,2),ts.substr(14,2),ts.substr(17,2)));
336 if (ts.substr(ts.length-6,1) != 'Z') { // there's an offset
337 var date_offset = date.getTimezoneOffset() * 60 * 1000;
338 var offset = new Date();
340 offset.setUTCHours(ts.substr(ts.length-5,2));
341 offset.setUTCMinutes(ts.substr(ts.length-2,2));
342 if (ts.substr(ts.length-6,1) == '+')
343 date.setTime(date.getTime() + offset.getTime() + date_offset);
344 else if (ts.substr(ts.length-6,1) == '-')
345 date.setTime(date.getTime() - offset.getTime() + date_offset);
351 * Takes a timestamp in the form of 2004-08-13T12:07:04+02:00 as argument
352 * and converts it to some sort of humane readable format
355 Date.hrTime = function(ts) {
356 return Date.jab2date(ts).toLocaleString();
360 * somewhat opposit to {@link #hrTime}
361 * expects a javascript Date object as parameter and returns a jabber
362 * date string conforming to
363 * {@link http://www.xmpp.org/extensions/xep-0082.html XEP-0082}
365 * @return The corresponding jabber DateTime string
368 Date.prototype.jabberDate = function() {
369 var padZero = function(i) {
370 if (i < 10) return "0" + i;
374 var jDate = this.getUTCFullYear() + "-";
375 jDate += padZero(this.getUTCMonth()+1) + "-";
376 jDate += padZero(this.getUTCDate()) + "T";
377 jDate += padZero(this.getUTCHours()) + ":";
378 jDate += padZero(this.getUTCMinutes()) + ":";
379 jDate += padZero(this.getUTCSeconds()) + "Z";
385 * Determines the maximum of two given numbers
387 * @param {Number} A a number
388 * @param {Number} B another number
389 * @return the maximum of A and B
392 Number.max = function(A, B) {
393 return (A > B)? A : B;
396 Number.min = function(A, B) {
397 return (A < B)? A : B;
401 /* Copyright (c) 1998 - 2007, Paul Johnston & Contributors
402 * All rights reserved.
404 * Redistribution and use in source and binary forms, with or without
405 * modification, are permitted provided that the following conditions
408 * Redistributions of source code must retain the above copyright
409 * notice, this list of conditions and the following
410 * disclaimer. Redistributions in binary form must reproduce the above
411 * copyright notice, this list of conditions and the following
412 * disclaimer in the documentation and/or other materials provided
413 * with the distribution.
415 * Neither the name of the author nor the names of its contributors
416 * may be used to endorse or promote products derived from this
417 * software without specific prior written permission.
420 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
421 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
422 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
423 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
424 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
425 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
426 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
427 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
428 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
429 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
430 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
431 * OF THE POSSIBILITY OF SUCH DAMAGE.
436 * @fileoverview Collection of MD5 and SHA1 hashing and encoding
438 * @author Stefan Strigler steve@zeank.in-berlin.de
439 * @version $Revision$
443 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
445 * Version 2.1a Copyright Paul Johnston 2000 - 2002.
446 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
447 * Distributed under the BSD License
448 * See http://pajhome.org.uk/crypt/md5 for details.
452 * Configurable variables. You may need to tweak these to be compatible with
453 * the server-side, but the defaults work in most cases.
455 var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
456 var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
457 var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
460 * These are the functions you'll usually want to call
461 * They take string arguments and return either hex or base-64 encoded strings
463 function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
464 function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
465 function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
466 function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
467 function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
468 function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
471 * Perform a simple self-test to see if the VM is working
473 function sha1_vm_test()
475 return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
479 * Calculate the SHA-1 of an array of big-endian words, and a bit length
481 function core_sha1(x, len)
484 x[len >> 5] |= 0x80 << (24 - len % 32);
485 x[((len + 64 >> 9) << 4) + 15] = len;
494 for(var i = 0; i < x.length; i += 16)
502 for(var j = 0; j < 80; j++)
504 if(j < 16) w[j] = x[i + j];
505 else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
506 var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
507 safe_add(safe_add(e, w[j]), sha1_kt(j)));
515 a = safe_add(a, olda);
516 b = safe_add(b, oldb);
517 c = safe_add(c, oldc);
518 d = safe_add(d, oldd);
519 e = safe_add(e, olde);
521 return Array(a, b, c, d, e);
526 * Perform the appropriate triplet combination function for the current
529 function sha1_ft(t, b, c, d)
531 if(t < 20) return (b & c) | ((~b) & d);
532 if(t < 40) return b ^ c ^ d;
533 if(t < 60) return (b & c) | (b & d) | (c & d);
538 * Determine the appropriate additive constant for the current iteration
542 return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
543 (t < 60) ? -1894007588 : -899497514;
547 * Calculate the HMAC-SHA1 of a key and some data
549 function core_hmac_sha1(key, data)
551 var bkey = str2binb(key);
552 if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
554 var ipad = Array(16), opad = Array(16);
555 for(var i = 0; i < 16; i++)
557 ipad[i] = bkey[i] ^ 0x36363636;
558 opad[i] = bkey[i] ^ 0x5C5C5C5C;
561 var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
562 return core_sha1(opad.concat(hash), 512 + 160);
566 * Bitwise rotate a 32-bit number to the left.
568 function rol(num, cnt)
570 return (num << cnt) | (num >>> (32 - cnt));
574 * Convert an 8-bit or 16-bit string to an array of big-endian words
575 * In 8-bit function, characters >255 have their hi-byte silently ignored.
577 function str2binb(str)
580 var mask = (1 << chrsz) - 1;
581 for(var i = 0; i < str.length * chrsz; i += chrsz)
582 bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
587 * Convert an array of big-endian words to a string
589 function binb2str(bin)
592 var mask = (1 << chrsz) - 1;
593 for(var i = 0; i < bin.length * 32; i += chrsz)
594 str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
599 * Convert an array of big-endian words to a hex string.
601 function binb2hex(binarray)
603 var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
605 for(var i = 0; i < binarray.length * 4; i++)
607 str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
608 hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
614 * Convert an array of big-endian words to a base-64 string
616 function binb2b64(binarray)
618 var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
620 for(var i = 0; i < binarray.length * 4; i += 3)
622 var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
623 | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
624 | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
625 for(var j = 0; j < 4; j++)
627 if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
628 else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
631 return str.replace(/AAA\=(\=*?)$/,'$1'); // cleans garbage chars at end of string
635 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
636 * Digest Algorithm, as defined in RFC 1321.
637 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
638 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
639 * Distributed under the BSD License
640 * See http://pajhome.org.uk/crypt/md5 for more info.
644 * Configurable variables. You may need to tweak these to be compatible with
645 * the server-side, but the defaults work in most cases.
647 // var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
648 // var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
649 // var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
652 * These are the functions you'll usually want to call
653 * They take string arguments and return either hex or base-64 encoded strings
655 function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
656 function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
657 function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
658 function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
659 function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
660 function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
663 * Perform a simple self-test to see if the VM is working
665 function md5_vm_test()
667 return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
671 * Calculate the MD5 of an array of little-endian words, and a bit length
673 function core_md5(x, len)
676 x[len >> 5] |= 0x80 << ((len) % 32);
677 x[(((len + 64) >>> 9) << 4) + 14] = len;
684 for(var i = 0; i < x.length; i += 16)
691 a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
692 d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
693 c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
694 b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
695 a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
696 d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
697 c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
698 b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
699 a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
700 d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
701 c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
702 b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
703 a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
704 d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
705 c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
706 b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
708 a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
709 d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
710 c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
711 b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
712 a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
713 d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
714 c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
715 b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
716 a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
717 d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
718 c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
719 b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
720 a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
721 d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
722 c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
723 b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
725 a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
726 d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
727 c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
728 b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
729 a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
730 d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
731 c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
732 b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
733 a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
734 d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
735 c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
736 b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
737 a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
738 d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
739 c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
740 b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
742 a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
743 d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
744 c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
745 b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
746 a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
747 d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
748 c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
749 b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
750 a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
751 d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
752 c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
753 b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
754 a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
755 d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
756 c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
757 b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
759 a = safe_add(a, olda);
760 b = safe_add(b, oldb);
761 c = safe_add(c, oldc);
762 d = safe_add(d, oldd);
764 return Array(a, b, c, d);
769 * These functions implement the four basic operations the algorithm uses.
771 function md5_cmn(q, a, b, x, s, t)
773 return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
775 function md5_ff(a, b, c, d, x, s, t)
777 return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
779 function md5_gg(a, b, c, d, x, s, t)
781 return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
783 function md5_hh(a, b, c, d, x, s, t)
785 return md5_cmn(b ^ c ^ d, a, b, x, s, t);
787 function md5_ii(a, b, c, d, x, s, t)
789 return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
793 * Calculate the HMAC-MD5, of a key and some data
795 function core_hmac_md5(key, data)
797 var bkey = str2binl(key);
798 if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
800 var ipad = Array(16), opad = Array(16);
801 for(var i = 0; i < 16; i++)
803 ipad[i] = bkey[i] ^ 0x36363636;
804 opad[i] = bkey[i] ^ 0x5C5C5C5C;
807 var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
808 return core_md5(opad.concat(hash), 512 + 128);
812 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
813 * to work around bugs in some JS interpreters.
815 function safe_add(x, y)
817 var lsw = (x & 0xFFFF) + (y & 0xFFFF);
818 var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
819 return (msw << 16) | (lsw & 0xFFFF);
823 * Bitwise rotate a 32-bit number to the left.
825 function bit_rol(num, cnt)
827 return (num << cnt) | (num >>> (32 - cnt));
831 * Convert a string to an array of little-endian words
832 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
834 function str2binl(str)
837 var mask = (1 << chrsz) - 1;
838 for(var i = 0; i < str.length * chrsz; i += chrsz)
839 bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
844 * Convert an array of little-endian words to a string
846 function binl2str(bin)
849 var mask = (1 << chrsz) - 1;
850 for(var i = 0; i < bin.length * 32; i += chrsz)
851 str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
856 * Convert an array of little-endian words to a hex string.
858 function binl2hex(binarray)
860 var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
862 for(var i = 0; i < binarray.length * 4; i++)
864 str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
865 hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
871 * Convert an array of little-endian words to a base-64 string
873 function binl2b64(binarray)
875 var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
877 for(var i = 0; i < binarray.length * 4; i += 3)
879 var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16)
880 | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
881 | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
882 for(var j = 0; j < 4; j++)
884 if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
885 else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
891 /* #############################################################################
892 UTF-8 Decoder and Encoder
893 base64 Encoder and Decoder
894 written by Tobias Kieslich, justdreams
895 Contact: tobias@justdreams.de http://www.justdreams.de/
896 ############################################################################# */
898 // returns an array of byterepresenting dezimal numbers which represent the
899 // plaintext in an UTF-8 encoded version. Expects a string.
900 // This function includes an exception management for those nasty browsers like
901 // NN401, which returns negative decimal numbers for chars>128. I hate it!!
902 // This handling is unfortunately limited to the user's charset. Anyway, it works
903 // in most of the cases! Special signs with an unicode>256 return numbers, which
904 // can not be converted to the actual unicode and so not to the valid utf-8
905 // representation. Anyway, this function does always return values which can not
906 // misinterpretd by RC4 or base64 en- or decoding, because every value is >0 and
908 // Arrays are faster and easier to handle in b64 encoding or encrypting....
911 t = t.replace(/\r\n/g,"\n");
912 var d=new Array; var test=String.fromCharCode(237);
913 if (test.charCodeAt(0) < 0)
914 for(var n=0; n<t.length; n++)
916 var c=t.charCodeAt(n);
920 d[d.length]= (((256+c)>>6)|192);
921 d[d.length]= (((256+c)&63)|128);}
924 for(var n=0; n<t.length; n++)
926 var c=t.charCodeAt(n);
927 // all the signs of asci => 1byte
930 // all the signs between 127 and 2047 => 2byte
931 else if((c>127) && (c<2048)) {
932 d[d.length]= ((c>>6)|192);
933 d[d.length]= ((c&63)|128);}
934 // all the signs between 2048 and 66536 => 3byte
936 d[d.length]= ((c>>12)|224);
937 d[d.length]= (((c>>6)&63)|128);
938 d[d.length]= ((c&63)|128);}
943 // returns plaintext from an array of bytesrepresenting dezimal numbers, which
944 // represent an UTF-8 encoded text; browser which does not understand unicode
945 // like NN401 will show "?"-signs instead
946 // expects an array of byterepresenting decimals; returns a string
949 var r=new Array; var i=0;
953 r[r.length]= String.fromCharCode(d[i]); i++;}
954 else if((d[i]>191) && (d[i]<224)) {
955 r[r.length]= String.fromCharCode(((d[i]&31)<<6) | (d[i+1]&63)); i+=2;}
957 r[r.length]= String.fromCharCode(((d[i]&15)<<12) | ((d[i+1]&63)<<6) | (d[i+2]&63)); i+=3;}
962 // included in <body onload="b64arrays"> it creates two arrays which makes base64
963 // en- and decoding faster
964 // this speed is noticeable especially when coding larger texts (>5k or so)
965 function b64arrays() {
966 var b64s='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
967 b64 = new Array();f64 =new Array();
968 for (var i=0; i<b64s.length ;i++) {
969 b64[i] = b64s.charAt(i);
970 f64[b64s.charAt(i)] = i;
974 // creates a base64 encoded text out of an array of byerepresenting dezimals
975 // it is really base64 :) this makes serversided handling easier
976 // expects an array; returns a string
978 var r=new Array; var i=0; var dl=d.length;
979 // this is for the padding
981 d[d.length] = 0; d[d.length] = 0;}
984 // from here conversion
987 r[r.length] = b64[d[i]>>2];
988 r[r.length] = b64[((d[i]&3)<<4) | (d[i+1]>>4)];
989 r[r.length] = b64[((d[i+1]&15)<<2) | (d[i+2]>>6)];
990 r[r.length] = b64[d[i+2]&63];
993 // this is again for the padding
995 r[r.length-1] = r[r.length-2] = "=";
998 // we join the array to return a textstring
1003 // returns array of byterepresenting numbers created of an base64 encoded text
1004 // it is still the slowest function in this modul; I hope I can make it faster
1005 // expects string; returns an array
1006 function b64t2d(t) {
1007 var d=new Array; var i=0;
1008 // here we fix this CRLF sequenz created by MS-OS; arrrgh!!!
1009 t=t.replace(/\n|\r/g,""); t=t.replace(/=/g,"");
1012 d[d.length] = (f64[t.charAt(i)]<<2) | (f64[t.charAt(i+1)]>>4);
1013 d[d.length] = (((f64[t.charAt(i+1)]&15)<<4) | (f64[t.charAt(i+2)]>>2));
1014 d[d.length] = (((f64[t.charAt(i+2)]&3)<<6) | (f64[t.charAt(i+3)]));
1017 if (t.length%4 == 2)
1018 d = d.slice(0, d.length-2);
1019 if (t.length%4 == 3)
1020 d = d.slice(0, d.length-1);
1026 b64decode = function(s) {
1027 return utf8d2t(b64t2d(s));
1030 b64encode = function(s) {
1031 return b64d2t(utf8t2d(s));
1034 function cnonce(size) {
1035 var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1037 for (var i=0; i<size; i++) {
1038 cnonce += tab.charAt(Math.round(Math.random(new Date().getTime())*(tab.length-1)));
1045 JSJAC_HAVEKEYS = true; // whether to use keys
1046 JSJAC_NKEYS = 16; // number of keys to generate
1047 JSJAC_INACTIVITY = 300; // qnd hack to make suspend/resume
1048 // work more smoothly with polling
1049 JSJAC_ERR_COUNT = 10; // number of retries in case of connection
1052 JSJAC_ALLOW_PLAIN = true; // whether to allow plaintext logins
1054 JSJAC_CHECKQUEUEINTERVAL = 100; // msecs to poll send queue
1055 JSJAC_CHECKINQUEUEINTERVAL = 100; // msecs to poll incoming queue
1056 JSJAC_TIMERVAL = 2000; // default polling interval
1058 // Options specific to HTTP Binding (BOSH)
1059 JSJACHBC_MAX_HOLD = 1; // default for number of connections held by
1060 // connection manager
1061 JSJACHBC_MAX_WAIT = 20; // default 'wait' param - how long an idle connection
1062 // should be held by connection manager
1064 JSJACHBC_BOSH_VERSION = "1.6";
1065 JSJACHBC_USE_BOSH_VER = true;
1067 JSJACHBC_MAXPAUSE = 20; // how long a suspend/resume cycle may take
1069 /*** END CONFIG ***/
1072 /* Copyright (c) 2005-2007 Sam Stephenson
1074 * Permission is hereby granted, free of charge, to any person
1075 * obtaining a copy of this software and associated documentation
1076 * files (the "Software"), to deal in the Software without
1077 * restriction, including without limitation the rights to use, copy,
1078 * modify, merge, publish, distribute, sublicense, and/or sell copies
1079 * of the Software, and to permit persons to whom the Software is
1080 * furnished to do so, subject to the following conditions:
1082 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1083 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1084 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1085 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
1086 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1087 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1088 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1094 taken from prototype.js, made static
1096 function JSJaCJSON() {}
1097 JSJaCJSON.toString = function (obj) {
1108 array: function (x) {
1109 var a = ['['], b, f, i, l = x.length, v;
1110 for (i = 0; i < l; i += 1) {
1116 if (typeof v == 'string') {
1130 'boolean': function (x) {
1133 'null': function (x) {
1136 number: function (x) {
1137 return isFinite(x) ? String(x) : 'null';
1139 object: function (x) {
1141 if (x instanceof Array) {
1144 var a = ['{'], b, f, i, v;
1146 if (x.hasOwnProperty(i)) {
1152 if (typeof v == 'string') {
1156 a.push(s.string(i), ':', v);
1170 string: function (x) {
1171 if (/["\\\x00-\x1f]/.test(x)) {
1172 x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
1179 Math.floor(c / 16).toString(16) +
1180 (c % 16).toString(16);
1183 return '"' + x + '"';
1187 switch (typeof(obj)) {
1189 return s.object(obj);
1191 return s.array(obj);
1196 JSJaCJSON.parse = function (str) {
1198 return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
1199 str.replace(/"(\\.|[^"\\])*"/g, ''))) &&
1200 eval('(' + str + ')');
1208 * @fileoverview This file contains all things that make life easier when
1210 * @author Stefan Strigler
1211 * @version $Revision$
1215 * list of forbidden chars for nodenames
1218 var JSJACJID_FORBIDDEN = ['"',' ','&','\'','/',':','<','>','@'];
1221 * Creates a new JSJaCJID object
1222 * @class JSJaCJID models xmpp jid objects
1224 * @param {Object} jid jid may be either of type String or a JID represented
1225 * by JSON with fields 'node', 'domain' and 'resource'
1226 * @throws JSJaCJIDInvalidException Thrown if jid is not valid
1227 * @return a new JSJaCJID object
1229 function JSJaCJID(jid) {
1241 this._resource = '';
1243 if (typeof(jid) == 'string') {
1244 if (jid.indexOf('@') != -1) {
1245 this.setNode(jid.substring(0,jid.indexOf('@')));
1246 jid = jid.substring(jid.indexOf('@')+1);
1248 if (jid.indexOf('/') != -1) {
1249 this.setResource(jid.substring(jid.indexOf('/')+1));
1250 jid = jid.substring(0,jid.indexOf('/'));
1252 this.setDomain(jid);
1254 this.setNode(jid.node);
1255 this.setDomain(jid.domain);
1256 this.setResource(jid.resource);
1262 * Gets the node part of the jid
1263 * @return A string representing the node name
1266 JSJaCJID.prototype.getNode = function() { return this._node; };
1269 * Gets the domain part of the jid
1270 * @return A string representing the domain name
1273 JSJaCJID.prototype.getDomain = function() { return this._domain; };
1276 * Gets the resource part of the jid
1277 * @return A string representing the resource
1280 JSJaCJID.prototype.getResource = function() { return this._resource; };
1284 * Sets the node part of the jid
1285 * @param {String} node Name of the node
1286 * @throws JSJaCJIDInvalidException Thrown if node name contains invalid chars
1287 * @return This object
1290 JSJaCJID.prototype.setNode = function(node) {
1291 JSJaCJID._checkNodeName(node);
1292 this._node = node || '';
1297 * Sets the domain part of the jid
1298 * @param {String} domain Name of the domain
1299 * @throws JSJaCJIDInvalidException Thrown if domain name contains invalid
1301 * @return This object
1304 JSJaCJID.prototype.setDomain = function(domain) {
1305 if (!domain || domain == '')
1306 throw new JSJaCJIDInvalidException("domain name missing");
1307 // chars forbidden for a node are not allowed in domain names
1308 // anyway, so let's check
1309 JSJaCJID._checkNodeName(domain);
1310 this._domain = domain;
1315 * Sets the resource part of the jid
1316 * @param {String} resource Name of the resource
1317 * @return This object
1320 JSJaCJID.prototype.setResource = function(resource) {
1321 this._resource = resource || '';
1326 * The string representation of the full jid
1327 * @return A string representing the jid
1330 JSJaCJID.prototype.toString = function() {
1332 if (this.getNode() && this.getNode() != '')
1333 jid = this.getNode() + '@';
1334 jid += this.getDomain(); // we always have a domain
1335 if (this.getResource() && this.getResource() != "")
1336 jid += '/' + this.getResource();
1341 * Removes the resource part of the jid
1342 * @return This object
1345 JSJaCJID.prototype.removeResource = function() {
1346 return this.setResource();
1350 * creates a copy of this JSJaCJID object
1351 * @return A copy of this
1354 JSJaCJID.prototype.clone = function() {
1355 return new JSJaCJID(this.toString());
1359 * Compares two jids if they belong to the same entity (i.e. w/o resource)
1360 * @param {String} jid a jid as string or JSJaCJID object
1361 * @return 'true' if jid is same entity as this
1364 JSJaCJID.prototype.isEntity = function(jid) {
1365 if (typeof jid == 'string')
1366 jid = (new JSJaCJID(jid));
1367 jid.removeResource();
1368 return (this.clone().removeResource().toString() === jid.toString());
1372 * Check if node name is valid
1374 * @param {String} node A name for a node
1375 * @throws JSJaCJIDInvalidException Thrown if name for node is not allowed
1377 JSJaCJID._checkNodeName = function(nodeprep) {
1378 if (!nodeprep || nodeprep == '')
1380 for (var i=0; i< JSJACJID_FORBIDDEN.length; i++) {
1381 if (nodeprep.indexOf(JSJACJID_FORBIDDEN[i]) != -1) {
1382 throw new JSJaCJIDInvalidException("forbidden char in nodename: "+JSJACJID_FORBIDDEN[i]);
1388 * Creates a new Exception of type JSJaCJIDInvalidException
1389 * @class Exception to indicate invalid values for a jid
1391 * @param {String} message The message associated with this Exception
1393 function JSJaCJIDInvalidException(message) {
1395 * The exceptions associated message
1398 this.message = message;
1400 * The name of the exception
1403 this.name = "JSJaCJIDInvalidException";
1407 /* Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
1409 * Permission is hereby granted, free of charge, to any person
1410 * obtaining a copy of this software and associated documentation
1411 * files (the "Software"), to deal in the Software without
1412 * restriction, including without limitation the rights to use, copy,
1413 * modify, merge, publish, distribute, sublicense, and/or sell copies
1414 * of the Software, and to permit persons to whom the Software is
1415 * furnished to do so, subject to the following conditions:
1417 * The above copyright notice and this permission notice shall be
1418 * included in all copies or substantial portions of the Software.
1420 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1421 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1422 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1423 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
1424 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1425 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1426 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1432 * This code is taken from {@link
1433 * http://wiki.script.aculo.us/scriptaculous/show/Builder
1434 * script.aculo.us' Dom Builder} and has been modified to suit our
1436 * The original parts of the code do have the following
1437 * copyright and license notice:<br/>
1438 * Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us,
1439 * http://mir.acu lo.us) <br/>
1440 * script.aculo.us is freely distributable under the terms of an
1441 * MIT-style license.<br>
1442 * For details, see the script.aculo.us web site:
1443 * http://script.aculo.us/<br>
1445 var JSJaCBuilder = {
1449 buildNode: function(doc, elementName) {
1451 var element, ns = arguments[4];
1453 // attributes (or text)
1455 if(JSJaCBuilder._isStringOrNumber(arguments[2]) ||
1456 (arguments[2] instanceof Array)) {
1457 element = this._createElement(doc, elementName, ns);
1458 JSJaCBuilder._children(doc, element, arguments[2]);
1460 ns = arguments[2]['xmlns'] || ns;
1461 element = this._createElement(doc, elementName, ns);
1462 for(attr in arguments[2]) {
1463 if (arguments[2].hasOwnProperty(attr) && attr != 'xmlns')
1464 element.setAttribute(attr, arguments[2][attr]);
1468 element = this._createElement(doc, elementName, ns);
1469 // text, or array of children
1471 JSJaCBuilder._children(doc, element, arguments[3], ns);
1476 _createElement: function(doc, elementName, ns) {
1479 return doc.createElementNS(ns, elementName);
1482 var el = doc.createElement(elementName);
1485 el.setAttribute("xmlns", ns);
1493 _text: function(doc, text) {
1494 return doc.createTextNode(text);
1500 _children: function(doc, element, children, ns) {
1501 if(typeof children=='object') { // array can hold nodes and text
1502 for (var i in children) {
1503 if (children.hasOwnProperty(i)) {
1504 var e = children[i];
1505 if (typeof e=='object') {
1506 if (e instanceof Array) {
1507 var node = JSJaCBuilder.buildNode(doc, e[0], e[1], e[2], ns);
1508 element.appendChild(node);
1510 element.appendChild(e);
1513 if(JSJaCBuilder._isStringOrNumber(e)) {
1514 element.appendChild(JSJaCBuilder._text(doc, e));
1520 if(JSJaCBuilder._isStringOrNumber(children)) {
1521 element.appendChild(JSJaCBuilder._text(doc, children));
1526 _attributes: function(attributes) {
1528 for(attribute in attributes)
1529 if (attributes.hasOwnProperty(attribute))
1530 attrs.push(attribute +
1531 '="' + attributes[attribute].toString().htmlEnc() + '"');
1532 return attrs.join(" ");
1535 _isStringOrNumber: function(param) {
1536 return(typeof param=='string' || typeof param=='number');
1542 * @fileoverview Contains all Jabber/XMPP packet related classes.
1543 * @author Stefan Strigler steve@zeank.in-berlin.de
1544 * @version $Revision$
1547 var JSJACPACKET_USE_XMLNS = true;
1550 * Creates a new packet with given root tag name (for internal use)
1551 * @class Somewhat abstract base class for all kinds of specialised packets
1552 * @param {String} name The root tag name of the packet
1553 * (i.e. one of 'message', 'iq' or 'presence')
1555 function JSJaCPacket(name) {
1561 if (typeof(JSJACPACKET_USE_XMLNS) != 'undefined' && JSJACPACKET_USE_XMLNS)
1565 this.doc = XmlDocument.create(name,'jabber:client');
1570 this.doc = XmlDocument.create(name,'');
1574 * Gets the type (name of root element) of this packet, i.e. one of
1575 * 'presence', 'message' or 'iq'
1576 * @return the top level tag name
1579 JSJaCPacket.prototype.pType = function() { return this.name; };
1582 * Gets the associated Document for this packet.
1583 * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document Document}
1585 JSJaCPacket.prototype.getDoc = function() {
1589 * Gets the root node of this packet
1590 * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
1592 JSJaCPacket.prototype.getNode = function() {
1593 if (this.getDoc() && this.getDoc().documentElement)
1594 return this.getDoc().documentElement;
1600 * Sets the 'to' attribute of the root node of this packet
1601 * @param {String} to
1604 JSJaCPacket.prototype.setTo = function(to) {
1605 if (!to || to == '')
1606 this.getNode().removeAttribute('to');
1607 else if (typeof(to) == 'string')
1608 this.getNode().setAttribute('to',to);
1610 this.getNode().setAttribute('to',to.toString());
1614 * Sets the 'from' attribute of the root node of this
1615 * packet. Usually this is not needed as the server will take care
1616 * of this automatically.
1619 JSJaCPacket.prototype.setFrom = function(from) {
1620 if (!from || from == '')
1621 this.getNode().removeAttribute('from');
1622 else if (typeof(from) == 'string')
1623 this.getNode().setAttribute('from',from);
1625 this.getNode().setAttribute('from',from.toString());
1629 * Sets 'id' attribute of the root node of this packet.
1630 * @param {String} id The id of the packet.
1633 JSJaCPacket.prototype.setID = function(id) {
1634 if (!id || id == '')
1635 this.getNode().removeAttribute('id');
1637 this.getNode().setAttribute('id',id);
1641 * Sets the 'type' attribute of the root node of this packet.
1642 * @param {String} type The type of the packet.
1645 JSJaCPacket.prototype.setType = function(type) {
1646 if (!type || type == '')
1647 this.getNode().removeAttribute('type');
1649 this.getNode().setAttribute('type',type);
1653 * Sets 'xml:lang' for this packet
1654 * @param {String} xmllang The xml:lang of the packet.
1657 JSJaCPacket.prototype.setXMLLang = function(xmllang) {
1658 // Fix IE9+ bug with xml:lang attribute
1659 if (BrowserDetect && (BrowserDetect.browser == 'Explorer') && (BrowserDetect.version >= 9))
1661 if (!xmllang || xmllang == '')
1662 this.getNode().removeAttribute('xml:lang');
1664 this.getNode().setAttribute('xml:lang',xmllang);
1669 * Gets the 'to' attribute of this packet
1672 JSJaCPacket.prototype.getTo = function() {
1673 return this.getNode().getAttribute('to');
1676 * Gets the 'from' attribute of this packet.
1679 JSJaCPacket.prototype.getFrom = function() {
1680 return this.getNode().getAttribute('from');
1683 * Gets the 'to' attribute of this packet as a JSJaCJID object
1686 JSJaCPacket.prototype.getToJID = function() {
1687 return new JSJaCJID(this.getTo());
1690 * Gets the 'from' attribute of this packet as a JSJaCJID object
1693 JSJaCPacket.prototype.getFromJID = function() {
1694 return new JSJaCJID(this.getFrom());
1697 * Gets the 'id' of this packet
1700 JSJaCPacket.prototype.getID = function() {
1701 return this.getNode().getAttribute('id');
1704 * Gets the 'type' of this packet
1707 JSJaCPacket.prototype.getType = function() {
1708 return this.getNode().getAttribute('type');
1711 * Gets the 'xml:lang' of this packet
1714 JSJaCPacket.prototype.getXMLLang = function() {
1715 return this.getNode().getAttribute('xml:lang');
1718 * Gets the 'xmlns' (xml namespace) of the root node of this packet
1721 JSJaCPacket.prototype.getXMLNS = function() {
1722 return this.getNode().namespaceURI || this.getNode().getAttribute('xmlns');
1726 * Gets a child element of this packet. If no params given returns first child.
1727 * @param {String} name Tagname of child to retrieve. Use '*' to match any tag. [optional]
1728 * @param {String} ns Namespace of child. Use '*' to match any ns.[optional]
1729 * @return The child node, null if none found
1730 * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
1732 JSJaCPacket.prototype.getChild = function(name, ns) {
1733 if (!this.getNode()) {
1740 if (this.getNode().getElementsByTagNameNS) {
1741 return this.getNode().getElementsByTagNameNS(ns, name).item(0);
1745 var nodes = this.getNode().getElementsByTagName(name);
1747 for (var i=0; i<nodes.length; i++) {
1748 if (nodes.item(i).namespaceURI == ns || nodes.item(i).getAttribute('xmlns') == ns) {
1749 return nodes.item(i);
1753 return nodes.item(0);
1755 return null; // nothing found
1759 * Gets the node value of a child element of this packet.
1760 * @param {String} name Tagname of child to retrieve.
1761 * @param {String} ns Namespace of child
1762 * @return The value of the child node, empty string if none found
1765 JSJaCPacket.prototype.getChildVal = function(name, ns) {
1766 var node = this.getChild(name, ns);
1768 if (node && node.hasChildNodes()) {
1769 // concatenate all values from childNodes
1770 for (var i=0; i<node.childNodes.length; i++)
1771 if (node.childNodes.item(i).nodeValue)
1772 ret += node.childNodes.item(i).nodeValue;
1778 * Returns a copy of this node
1779 * @return a copy of this node
1782 JSJaCPacket.prototype.clone = function() {
1783 return JSJaCPacket.wrapNode(this.getNode());
1787 * Checks if packet is of type 'error'
1788 * @return 'true' if this packet is of type 'error', 'false' otherwise
1791 JSJaCPacket.prototype.isError = function() {
1792 return (this.getType() == 'error');
1796 * Returns an error condition reply according to {@link http://www.xmpp.org/extensions/xep-0086.html XEP-0086}. Creates a clone of the calling packet with senders and recipient exchanged and error stanza appended.
1797 * @param {STANZA_ERROR} stanza_error an error stanza containing error cody, type and condition of the error to be indicated
1798 * @return an error reply packet
1801 JSJaCPacket.prototype.errorReply = function(stanza_error) {
1802 var rPacket = this.clone();
1803 rPacket.setTo(this.getFrom());
1805 rPacket.setType('error');
1807 rPacket.appendNode('error',
1808 {code: stanza_error.code, type: stanza_error.type},
1809 [[stanza_error.cond]]);
1815 * Returns a string representation of the raw xml content of this packet.
1818 JSJaCPacket.prototype.xml = typeof XMLSerializer != 'undefined' ?
1820 var r = (new XMLSerializer()).serializeToString(this.getNode());
1821 if (typeof(r) == 'undefined')
1822 r = (new XMLSerializer()).serializeToString(this.doc); // oldschool
1826 return this.getDoc().xml
1830 // PRIVATE METHODS DOWN HERE
1833 * Gets an attribute of the root element
1836 JSJaCPacket.prototype._getAttribute = function(attr) {
1837 return this.getNode().getAttribute(attr);
1841 if (document.ELEMENT_NODE == null) {
1842 document.ELEMENT_NODE = 1;
1843 document.ATTRIBUTE_NODE = 2;
1844 document.TEXT_NODE = 3;
1845 document.CDATA_SECTION_NODE = 4;
1846 document.ENTITY_REFERENCE_NODE = 5;
1847 document.ENTITY_NODE = 6;
1848 document.PROCESSING_INSTRUCTION_NODE = 7;
1849 document.COMMENT_NODE = 8;
1850 document.DOCUMENT_NODE = 9;
1851 document.DOCUMENT_TYPE_NODE = 10;
1852 document.DOCUMENT_FRAGMENT_NODE = 11;
1853 document.NOTATION_NODE = 12;
1857 * import node into this packets document
1860 JSJaCPacket.prototype._importNode = function(node, allChildren) {
1861 switch (node.nodeType) {
1862 case document.ELEMENT_NODE:
1864 if (this.getDoc().createElementNS) {
1865 var newNode = this.getDoc().createElementNS(node.namespaceURI, node.nodeName);
1867 var newNode = this.getDoc().createElement(node.nodeName);
1870 /* does the node have any attributes to add? */
1871 if (node.attributes && node.attributes.length > 0)
1872 for (var i = 0, il = node.attributes.length;i < il; i++) {
1873 var attr = node.attributes.item(i);
1874 if (attr.nodeName == 'xmlns' && newNode.getAttribute('xmlns') != null ) continue;
1875 if (newNode.setAttributeNS && attr.namespaceURI) {
1876 newNode.setAttributeNS(attr.namespaceURI,
1880 newNode.setAttribute(attr.nodeName,
1884 /* are we going after children too, and does the node have any? */
1885 if (allChildren && node.childNodes && node.childNodes.length > 0) {
1886 for (var i = 0, il = node.childNodes.length; i < il; i++) {
1887 newNode.appendChild(this._importNode(node.childNodes.item(i), allChildren));
1892 case document.TEXT_NODE:
1893 case document.CDATA_SECTION_NODE:
1894 case document.COMMENT_NODE:
1895 return this.getDoc().createTextNode(node.nodeValue);
1901 * Set node value of a child node
1904 JSJaCPacket.prototype._setChildNode = function(nodeName, nodeValue) {
1905 var aNode = this.getChild(nodeName);
1906 var tNode = this.getDoc().createTextNode(nodeValue);
1909 aNode.replaceChild(tNode,aNode.firstChild);
1913 aNode = this.getDoc().createElementNS(this.getNode().namespaceURI,
1916 aNode = this.getDoc().createElement(nodeName)
1918 this.getNode().appendChild(aNode);
1919 aNode.appendChild(tNode);
1925 * Builds a node using {@link
1926 * http://wiki.script.aculo.us/scriptaculous/show/Builder
1927 * script.aculo.us' Dom Builder} notation.
1928 * This code is taken from {@link
1929 * http://wiki.script.aculo.us/scriptaculous/show/Builder
1930 * script.aculo.us' Dom Builder} and has been modified to suit our
1932 * The original parts of the code do have the following copyright
1933 * and license notice:<br/>
1934 * Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us,
1935 * http://mir.acu lo.us) <br/>
1936 * script.aculo.us is freely distributable under the terms of an
1937 * MIT-style licen se. // For details, see the script.aculo.us web
1938 * site: http://script.aculo.us/<br>
1939 * @author Thomas Fuchs
1940 * @author Stefan Strigler
1941 * @return The newly created node
1942 * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
1944 JSJaCPacket.prototype.buildNode = function(elementName) {
1945 return JSJaCBuilder.buildNode(this.getDoc(),
1952 * Appends node created by buildNode to this packets parent node.
1953 * @param {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node} element The node to append or
1954 * @param {String} element A name plus an object hash with attributes (optional) plus an array of childnodes (optional)
1956 * @return This packet
1959 JSJaCPacket.prototype.appendNode = function(element) {
1960 if (typeof element=='object') { // seems to be a prebuilt node
1961 return this.getNode().appendChild(element)
1962 } else { // build node
1963 return this.getNode().appendChild(this.buildNode(element,
1967 this.getNode().namespaceURI));
1973 * A jabber/XMPP presence packet
1974 * @class Models the XMPP notion of a 'presence' packet
1975 * @extends JSJaCPacket
1977 function JSJaCPresence() {
1981 this.base = JSJaCPacket;
1982 this.base('presence');
1984 JSJaCPresence.prototype = new JSJaCPacket;
1987 * Sets the status message for current status. Usually this is set
1988 * to some human readable string indicating what the user is
1989 * doing/feel like currently.
1990 * @param {String} status A status message
1994 JSJaCPresence.prototype.setStatus = function(status) {
1995 this._setChildNode("status", status);
1999 * Sets the online status for this presence packet.
2000 * @param {String} show An XMPP complient status indicator. Must
2001 * be one of 'chat', 'away', 'xa', 'dnd'
2005 JSJaCPresence.prototype.setShow = function(show) {
2006 if (show == 'chat' || show == 'away' || show == 'xa' || show == 'dnd')
2007 this._setChildNode("show",show);
2011 * Sets the priority of the resource bind to with this connection
2012 * @param {int} prio The priority to set this resource to
2016 JSJaCPresence.prototype.setPriority = function(prio) {
2017 this._setChildNode("priority", prio);
2021 * Some combined method that allowes for setting show, status and
2023 * @param {String} show A status message
2024 * @param {String} status A status indicator as defined by XMPP
2025 * @param {int} prio A priority for this resource
2029 JSJaCPresence.prototype.setPresence = function(show,status,prio) {
2033 this.setStatus(status);
2035 this.setPriority(prio);
2040 * Gets the status message of this presence
2041 * @return The (human readable) status message
2044 JSJaCPresence.prototype.getStatus = function() {
2045 return this.getChildVal('status');
2048 * Gets the status of this presence.
2049 * Either one of 'chat', 'away', 'xa' or 'dnd' or null.
2050 * @return The status indicator as defined by XMPP
2053 JSJaCPresence.prototype.getShow = function() {
2054 return this.getChildVal('show');
2057 * Gets the priority of this status message
2058 * @return A resource priority
2061 JSJaCPresence.prototype.getPriority = function() {
2062 return this.getChildVal('priority');
2067 * A jabber/XMPP iq packet
2068 * @class Models the XMPP notion of an 'iq' packet
2069 * @extends JSJaCPacket
2071 function JSJaCIQ() {
2075 this.base = JSJaCPacket;
2078 JSJaCIQ.prototype = new JSJaCPacket;
2081 * Some combined method to set 'to', 'type' and 'id' at once
2082 * @param {String} to the recepients JID
2083 * @param {String} type A XMPP compliant iq type (one of 'set', 'get', 'result' and 'error'
2084 * @param {String} id A packet ID
2088 JSJaCIQ.prototype.setIQ = function(to,type,id) {
2098 * Creates a 'query' child node with given XMLNS
2099 * @param {String} xmlns The namespace for the 'query' node
2100 * @return The query node
2101 * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
2103 JSJaCIQ.prototype.setQuery = function(xmlns) {
2106 query = this.getDoc().createElementNS(xmlns,'query');
2108 query = this.getDoc().createElement('query');
2109 query.setAttribute('xmlns',xmlns);
2111 this.getNode().appendChild(query);
2116 * Gets the 'query' node of this packet
2117 * @return The query node
2118 * @type {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 Node}
2120 JSJaCIQ.prototype.getQuery = function() {
2121 return this.getNode().getElementsByTagName('query').item(0);
2124 * Gets the XMLNS of the query node contained within this packet
2125 * @return The namespace of the query node
2128 JSJaCIQ.prototype.getQueryXMLNS = function() {
2129 if (this.getQuery()) {
2130 return this.getQuery().namespaceURI || this.getQuery().getAttribute('xmlns');
2137 * Creates an IQ reply with type set to 'result'. If given appends payload to first child if IQ. Payload maybe XML as string or a DOM element (or an array of such elements as well).
2138 * @param {Element} payload A payload to be appended [optional]
2139 * @return An IQ reply packet
2142 JSJaCIQ.prototype.reply = function(payload) {
2143 var rIQ = this.clone();
2144 rIQ.setTo(this.getFrom());
2146 rIQ.setType('result');
2148 if (typeof payload == 'string')
2149 rIQ.getChild().appendChild(rIQ.getDoc().loadXML(payload));
2150 else if (payload.constructor == Array) {
2151 var node = rIQ.getChild();
2152 for (var i=0; i<payload.length; i++)
2153 if(typeof payload[i] == 'string')
2154 node.appendChild(rIQ.getDoc().loadXML(payload[i]));
2155 else if (typeof payload[i] == 'object')
2156 node.appendChild(payload[i]);
2158 else if (typeof payload == 'object')
2159 rIQ.getChild().appendChild(payload);
2165 * A jabber/XMPP message packet
2166 * @class Models the XMPP notion of an 'message' packet
2167 * @extends JSJaCPacket
2169 function JSJaCMessage() {
2173 this.base = JSJaCPacket;
2174 this.base('message');
2176 JSJaCMessage.prototype = new JSJaCPacket;
2179 * Sets the body of the message
2180 * @param {String} body Your message to be sent along
2181 * @return this message
2182 * @type JSJaCMessage
2184 JSJaCMessage.prototype.setBody = function(body) {
2185 this._setChildNode("body",body);
2189 * Sets the subject of the message
2190 * @param {String} subject Your subject to be sent along
2191 * @return this message
2192 * @type JSJaCMessage
2194 JSJaCMessage.prototype.setSubject = function(subject) {
2195 this._setChildNode("subject",subject);
2199 * Sets the 'tread' attribute for this message. This is used to identify
2200 * threads in chat conversations
2201 * @param {String} thread Usually a somewhat random hash.
2202 * @return this message
2203 * @type JSJaCMessage
2205 JSJaCMessage.prototype.setThread = function(thread) {
2206 this._setChildNode("thread", thread);
2210 * Gets the 'thread' identifier for this message
2211 * @return A thread identifier
2214 JSJaCMessage.prototype.getThread = function() {
2215 return this.getChildVal('thread');
2218 * Gets the body of this message
2219 * @return The body of this message
2222 JSJaCMessage.prototype.getBody = function() {
2223 return this.getChildVal('body');
2226 * Gets the subject of this message
2227 * @return The subject of this message
2230 JSJaCMessage.prototype.getSubject = function() {
2231 return this.getChildVal('subject')
2236 * Tries to transform a w3c DOM node to JSJaC's internal representation
2237 * (JSJaCPacket type, one of JSJaCPresence, JSJaCMessage, JSJaCIQ)
2239 * http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247}
2240 * node The node to be transformed
2241 * @return A JSJaCPacket representing the given node. If node's root
2242 * elemenent is not one of 'message', 'presence' or 'iq',
2243 * <code>null</code> is being returned.
2246 JSJaCPacket.wrapNode = function(node) {
2249 switch (node.nodeName.toLowerCase()) {
2251 oPacket = new JSJaCPresence();
2254 oPacket = new JSJaCMessage();
2257 oPacket = new JSJaCIQ();
2262 oPacket.getDoc().replaceChild(oPacket._importNode(node, true),
2272 * an error packet for internal use
2276 function JSJaCError(code,type,condition) {
2277 var xmldoc = XmlDocument.create("error","jsjac");
2279 xmldoc.documentElement.setAttribute('code',code);
2280 xmldoc.documentElement.setAttribute('type',type);
2282 xmldoc.documentElement.appendChild(xmldoc.createElement(condition)).
2283 setAttribute('xmlns','urn:ietf:params:xml:ns:xmpp-stanzas');
2284 return xmldoc.documentElement;
2290 * Creates a new set of hash keys
2291 * @class Reflects a set of sha1/md5 hash keys for securing sessions
2293 * @param {Function} func The hash function to be used for creating the keys
2294 * @param {Debugger} oDbg Reference to debugger implementation [optional]
2296 function JSJaCKeys(func,oDbg) {
2297 var seed = Math.random();
2302 this._k = new Array();
2303 this._k[0] = seed.toString();
2306 * Reference to Debugger
2312 this.oDbg.log = function() {};
2316 for (var i=1; i<JSJAC_NKEYS; i++) {
2317 this._k[i] = func(this._k[i-1]);
2318 oDbg.log(i+": "+this._k[i],4);
2325 this._indexAt = JSJAC_NKEYS-1;
2327 * Gets next key from stack
2328 * @return New hash key
2331 this.getKey = function() {
2332 return this._k[this._indexAt--];
2335 * Indicates whether there's only one key left
2336 * @return <code>true</code> if there's only one key left, false otherwise
2339 this.lastKey = function() { return (this._indexAt == 0); };
2341 * Returns number of overall/initial stack size
2342 * @return Number of keys created
2345 this.size = function() { return this._k.length; };
2350 this._getSuspendVars = function() {
2351 return ('_k,_indexAt').split(',');
2357 * @fileoverview Contains all things in common for all subtypes of connections
2359 * @author Stefan Strigler steve@zeank.in-berlin.de
2360 * @version $Revision$
2364 * Creates a new Jabber connection (a connection to a jabber server)
2365 * @class Somewhat abstract base class for jabber connections. Contains all
2366 * of the code in common for all jabber connections
2368 * @param {JSON http://www.json.org/index} oArg JSON with properties: <br>
2369 * * <code>httpbase</code> the http base address of the service to be used for
2370 * connecting to jabber<br>
2371 * * <code>oDbg</code> (optional) a reference to a debugger interface
2373 function JSJaCConnection(oArg) {
2375 if (oArg && oArg.oDbg && oArg.oDbg.log) {
2377 * Reference to debugger interface
2378 * (needs to implement method <code>log</code>)
2381 this.oDbg = oArg.oDbg;
2383 this.oDbg = new Object(); // always initialise a debugger
2384 this.oDbg.log = function() { };
2387 if (oArg && oArg.timerval)
2388 this.setPollInterval(oArg.timerval);
2390 this.setPollInterval(JSJAC_TIMERVAL);
2392 if (oArg && oArg.httpbase)
2396 this._httpbase = oArg.httpbase;
2398 if (oArg &&oArg.allow_plain)
2402 this.allow_plain = oArg.allow_plain;
2404 this.allow_plain = JSJAC_ALLOW_PLAIN;
2406 if (oArg && oArg.cookie_prefix)
2410 this._cookie_prefix = oArg.cookie_prefix;
2412 this._cookie_prefix = "";
2417 this._connected = false;
2421 this._events = new Array();
2433 this._inQ = new Array();
2437 this._pQueue = new Array();
2441 this._regIDs = new Array();
2445 this._req = new Array();
2449 this._status = 'intialized';
2457 this._inactivity = JSJAC_INACTIVITY;
2461 this._sendRawCallbacks = new Array();
2471 JSJaCConnection.prototype.connect = function(oArg) {
2472 this._setStatus('connecting');
2474 this.domain = oArg.domain || 'localhost';
2475 this.username = oArg.username;
2476 this.resource = oArg.resource;
2477 this.pass = oArg.pass;
2478 this.register = oArg.register;
2480 this.authhost = oArg.authhost || this.domain;
2481 this.authtype = oArg.authtype || 'sasl';
2483 if (oArg.xmllang && oArg.xmllang != '')
2484 this._xmllang = oArg.xmllang;
2486 this._xmllang = 'en';
2488 this.host = oArg.host || this.domain;
2489 this.port = oArg.port || 5222;
2491 this.secure = 'true';
2493 this.secure = 'false';
2496 this._wait = oArg.wait;
2498 this.jid = this.username + '@' + this.domain;
2499 this.fulljid = this.jid + '/' + this.resource;
2501 this._rid = Math.round( 100000.5 + ( ( (900000.49999) - (100000.5) ) * Math.random() ) );
2503 // setupRequest must be done after rid is created but before first use in reqstr
2504 var slot = this._getFreeSlot();
2505 this._req[slot] = this._setupRequest(true);
2507 var reqstr = this._getInitialRequestString();
2509 this.oDbg.log(reqstr,4);
2511 this._req[slot].r.onreadystatechange =
2512 JSJaC.bind(function() {
2513 var r = this._req[slot].r;
2514 if (r.readyState == 4) {
2515 this.oDbg.log("async recv: "+r.responseText,4);
2516 this._handleInitialResponse(r); // handle response
2520 if (typeof(this._req[slot].r.onerror) != 'undefined') {
2521 this._req[slot].r.onerror =
2522 JSJaC.bind(function(e) {
2523 this.oDbg.log('XmlHttpRequest error',1);
2528 this._req[slot].r.send(reqstr);
2532 * Tells whether this connection is connected
2533 * @return <code>true</code> if this connections is connected,
2534 * <code>false</code> otherwise
2537 JSJaCConnection.prototype.connected = function() { return this._connected; };
2540 * Disconnects from jabber server and terminates session (if applicable)
2542 JSJaCConnection.prototype.disconnect = function() {
2543 this._setStatus('disconnecting');
2545 if (!this.connected())
2547 this._connected = false;
2549 clearInterval(this._interval);
2550 clearInterval(this._inQto);
2553 clearTimeout(this._timeout); // remove timer
2555 var slot = this._getFreeSlot();
2556 // Intentionally synchronous
2557 this._req[slot] = this._setupRequest(false);
2559 request = this._getRequestString(false, true);
2561 this.oDbg.log("Disconnecting: " + request,4);
2562 this._req[slot].r.send(request);
2565 removeDB('jsjac', 'state');
2568 this.oDbg.log("Disconnected: "+this._req[slot].r.responseText,2);
2569 this._handleEvent('ondisconnect');
2573 * Gets current value of polling interval
2574 * @return Polling interval in milliseconds
2577 JSJaCConnection.prototype.getPollInterval = function() {
2578 return this._timerval;
2582 * Registers an event handler (callback) for this connection.
2584 * <p>Note: All of the packet handlers for specific packets (like
2585 * message_in, presence_in and iq_in) fire only if there's no
2586 * callback associated with the id.<br>
2589 * <code>con.registerHandler('iq', 'query', 'jabber:iq:version', handleIqVersion);</code>
2592 * @param {String} event One of
2595 * <li>onConnect - connection has been established and authenticated</li>
2596 * <li>onDisconnect - connection has been disconnected</li>
2597 * <li>onResume - connection has been resumed</li>
2599 * <li>onStatusChanged - connection status has changed, current
2600 * status as being passed argument to handler. See {@link #status}.</li>
2602 * <li>onError - an error has occured, error node is supplied as
2603 * argument, like this:<br><code><error code='404' type='cancel'><br>
2604 * <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/><br>
2605 * </error></code></li>
2607 * <li>packet_in - a packet has been received (argument: the
2610 * <li>packet_out - a packet is to be sent(argument: the
2613 * <li>message_in | message - a message has been received (argument:
2616 * <li>message_out - a message packet is to be sent (argument: the
2619 * <li>presence_in | presence - a presence has been received
2620 * (argument: the packet)</li>
2622 * <li>presence_out - a presence packet is to be sent (argument: the
2625 * <li>iq_in | iq - an iq has been received (argument: the packet)</li>
2626 * <li>iq_out - an iq is to be sent (argument: the packet)</li>
2629 * @param {String} childName A childnode's name that must occur within a
2630 * retrieved packet [optional]
2632 * @param {String} childNS A childnode's namespace that must occure within
2633 * a retrieved packet (works only if childName is given) [optional]
2635 * @param {String} type The type of the packet to handle (works only if childName and chidNS are given (both may be set to '*' in order to get skipped) [optional]
2637 * @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
2639 JSJaCConnection.prototype.registerHandler = function(event) {
2640 event = event.toLowerCase(); // don't be case-sensitive here
2641 var eArg = {handler: arguments[arguments.length-1],
2645 if (arguments.length > 2)
2646 eArg.childName = arguments[1];
2647 if (arguments.length > 3)
2648 eArg.childNS = arguments[2];
2649 if (arguments.length > 4)
2650 eArg.type = arguments[3];
2651 if (!this._events[event])
2652 this._events[event] = new Array(eArg);
2654 this._events[event] = this._events[event].concat(eArg);
2656 // sort events in order how specific they match criterias thus using
2657 // wildcard patterns puts them back in queue when it comes to
2658 // bubbling the event
2659 this._events[event] =
2660 this._events[event].sort(function(a,b) {
2668 if (childName == '*')
2676 if (childName == '*')
2685 this.oDbg.log("registered handler for event '"+event+"'",2);
2688 JSJaCConnection.prototype.unregisterHandler = function(event,handler) {
2689 event = event.toLowerCase(); // don't be case-sensitive here
2691 if (!this._events[event])
2694 var arr = this._events[event], res = new Array();
2695 for (var i=0; i<arr.length; i++)
2696 if (arr[i].handler != handler)
2699 if (arr.length != res.length) {
2700 this._events[event] = res;
2701 this.oDbg.log("unregistered handler for event '"+event+"'",2);
2706 * Register for iq packets of type 'get'.
2707 * @param {String} childName A childnode's name that must occur within a
2710 * @param {String} childNS A childnode's namespace that must occure within
2711 * a retrieved packet (works only if childName is given)
2713 * @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
2715 JSJaCConnection.prototype.registerIQGet = function(childName, childNS, handler) {
2716 this.registerHandler('iq', childName, childNS, 'get', handler);
2720 * Register for iq packets of type 'set'.
2721 * @param {String} childName A childnode's name that must occur within a
2724 * @param {String} childNS A childnode's namespace that must occure within
2725 * a retrieved packet (works only if childName is given)
2727 * @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
2729 JSJaCConnection.prototype.registerIQSet = function(childName, childNS, handler) {
2730 this.registerHandler('iq', childName, childNS, 'set', handler);
2734 * Resumes this connection from saved state (cookie)
2735 * @return Whether resume was successful
2738 JSJaCConnection.prototype.resume = function() {
2740 var json = getDB('jsjac', 'state');
2741 this.oDbg.log('read cookie: '+json,2);
2742 removeDB('jsjac', 'state');
2744 return this.resumeFromData(JSJaCJSON.parse(json));
2746 return false; // sth went wrong
2750 * Resumes BOSH connection from data
2751 * @param {Object} serialized jsjac state information
2752 * @return Whether resume was successful
2755 JSJaCConnection.prototype.resumeFromData = function(data) {
2757 this._setStatus('resuming');
2760 if (data.hasOwnProperty(i))
2763 // copy keys - not being very generic here :-/
2765 this._keys2 = new JSJaCKeys();
2766 var u = this._keys2._getSuspendVars();
2767 for (var i=0; i<u.length; i++)
2768 this._keys2[u[i]] = this._keys[u[i]];
2769 this._keys = this._keys2;
2772 if (this._connected) {
2773 // don't poll too fast!
2774 this._handleEvent('onresume');
2775 setTimeout(JSJaC.bind(this._resume, this),this.getPollInterval());
2776 this._interval = setInterval(JSJaC.bind(this._checkQueue, this),
2777 JSJAC_CHECKQUEUEINTERVAL);
2778 this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
2779 JSJAC_CHECKINQUEUEINTERVAL);
2782 return (this._connected === true);
2785 this.oDbg.log("Resume failed: "+e.message, 1);
2787 this.oDbg.log("Resume failed: "+e, 1);
2793 * Sends a JSJaCPacket
2794 * @param {JSJaCPacket} packet The packet to send
2795 * @param {Function} cb The callback to be called if there's a reply
2796 * to this packet (identified by id) [optional]
2797 * @param {Object} arg Arguments passed to the callback
2798 * (additionally to the packet received) [optional]
2799 * @return 'true' if sending was successfull, 'false' otherwise
2802 JSJaCConnection.prototype.send = function(packet,cb,arg) {
2803 if (!packet || !packet.pType) {
2804 this.oDbg.log("no packet: "+packet, 1);
2808 if (!this.connected())
2811 // generate an ID for the packet
2812 if (!packet.getID())
2813 packet.setID(genID());
2816 if (!packet.getXMLLang())
2817 packet.setXMLLang(XML_LANG);
2819 // remember id for response if callback present
2821 this._registerPID(packet.getID(),cb,arg);
2824 this._handleEvent(packet.pType()+'_out', packet);
2825 this._handleEvent("packet_out", packet);
2826 this._pQueue = this._pQueue.concat(packet.xml());
2828 this.oDbg.log(e.toString(),1);
2836 * Sends an IQ packet. Has default handlers for each reply type.
2837 * Those maybe overriden by passing an appropriate handler.
2838 * @param {JSJaCIQPacket} iq - the iq packet to send
2839 * @param {Object} handlers - object with properties 'error_handler',
2840 * 'result_handler' and 'default_handler'
2841 * with appropriate functions
2842 * @param {Object} arg - argument to handlers
2843 * @return 'true' if sending was successfull, 'false' otherwise
2846 JSJaCConnection.prototype.sendIQ = function(iq, handlers, arg) {
2847 if (!iq || iq.pType() != 'iq') {
2851 handlers = handlers || {};
2852 var error_handler = handlers.error_handler || JSJaC.bind(function(aIq) {
2853 this.oDbg.log(aIq.xml(), 1);
2856 var result_handler = handlers.result_handler || JSJaC.bind(function(aIq) {
2857 this.oDbg.log(aIq.xml(), 2);
2860 var iqHandler = function(aIq, arg) {
2861 switch (aIq.getType()) {
2866 result_handler(aIq, arg);
2870 return this.send(iq, iqHandler, arg);
2874 * Sets polling interval for this connection
2875 * @param {int} millisecs Milliseconds to set timer to
2876 * @return effective interval this connection has been set to
2879 JSJaCConnection.prototype.setPollInterval = function(timerval) {
2880 if (timerval && !isNaN(timerval))
2881 this._timerval = timerval;
2882 return this._timerval;
2886 * Returns current status of this connection
2887 * @return String to denote current state. One of
2889 * <li>'initializing' ... well
2890 * <li>'connecting' if connect() was called
2891 * <li>'resuming' if resume() was called
2892 * <li>'processing' if it's about to operate as normal
2893 * <li>'onerror_fallback' if there was an error with the request object
2894 * <li>'protoerror_fallback' if there was an error at the http binding protocol flow (most likely that's where you interested in)
2895 * <li>'internal_server_error' in case of an internal server error
2896 * <li>'suspending' if suspend() is being called
2897 * <li>'aborted' if abort() was called
2898 * <li>'disconnecting' if disconnect() has been called
2902 JSJaCConnection.prototype.status = function() { return this._status; };
2905 * Suspends this connection (saving state for later resume)
2906 * Saves state to cookie
2907 * @return Whether suspend (saving to cookie) was successful
2910 JSJaCConnection.prototype.suspend = function(has_pause) {
2911 var data = this.suspendToData(has_pause);
2914 var c = setDB('jsjac', 'state', JSJaCJSON.toString(data));
2917 this.oDbg.log("Failed creating cookie '"+this._cookie_prefix+
2918 "JSJaC_State': "+e.message,1);
2924 * Suspend connection and return serialized JSJaC connection state
2925 * @return JSJaC connection state object
2928 JSJaCConnection.prototype.suspendToData = function(has_pause) {
2932 clearTimeout(this._timeout);
2933 clearInterval(this._interval);
2934 clearInterval(this._inQto);
2939 var u = ('_connected,_keys,_ID,_inQ,_pQueue,_regIDs,_errcnt,_inactivity,domain,username,resource,jid,fulljid,_sid,_httpbase,_timerval,_is_polling').split(',');
2940 u = u.concat(this._getSuspendVars());
2941 var s = new Object();
2943 for (var i=0; i<u.length; i++) {
2944 if (!this[u[i]]) continue; // hu? skip these!
2945 if (this[u[i]]._getSuspendVars) {
2946 var uo = this[u[i]]._getSuspendVars();
2947 var o = new Object();
2948 for (var j=0; j<uo.length; j++)
2949 o[uo[j]] = this[u[i]][uo[j]];
2957 this._connected = false;
2958 this._setStatus('suspending');
2967 JSJaCConnection.prototype._abort = function() {
2968 clearTimeout(this._timeout); // remove timer
2970 clearInterval(this._inQto);
2971 clearInterval(this._interval);
2973 this._connected = false;
2975 this._setStatus('aborted');
2977 this.oDbg.log("Disconnected.",1);
2978 this._handleEvent('ondisconnect');
2979 this._handleEvent('onerror',
2980 JSJaCError('500','cancel','service-unavailable'));
2986 JSJaCConnection.prototype._checkInQ = function() {
2987 for (var i=0; i<this._inQ.length && i<10; i++) {
2988 var item = this._inQ[0];
2989 this._inQ = this._inQ.slice(1,this._inQ.length);
2990 var packet = JSJaCPacket.wrapNode(item);
2995 this._handleEvent("packet_in", packet);
2997 if (packet.pType && !this._handlePID(packet)) {
2998 this._handleEvent(packet.pType()+'_in',packet);
2999 this._handleEvent(packet.pType(),packet);
3007 JSJaCConnection.prototype._checkQueue = function() {
3008 if (this._pQueue.length != 0)
3016 JSJaCConnection.prototype._doAuth = function() {
3017 if (this.has_sasl && this.authtype == 'nonsasl')
3018 this.oDbg.log("Warning: SASL present but not used", 1);
3020 if (!this._doSASLAuth() &&
3021 !this._doLegacyAuth()) {
3022 this.oDbg.log("Auth failed for authtype "+this.authtype,1);
3032 JSJaCConnection.prototype._doInBandReg = function() {
3033 if (this.authtype == 'saslanon' || this.authtype == 'anonymous')
3034 return; // bullshit - no need to register if anonymous
3037 * In-Band Registration see JEP-0077
3040 var iq = new JSJaCIQ();
3043 iq.appendNode("query", {xmlns: "jabber:iq:register"},
3044 [["username", this.username],
3045 ["password", this.pass]]);
3047 this.send(iq,this._doInBandRegDone);
3053 JSJaCConnection.prototype._doInBandRegDone = function(iq) {
3054 if (iq && iq.getType() == 'error') { // we failed to register
3055 this.oDbg.log("registration failed for "+this.username,0);
3056 this._handleEvent('onerror',iq.getChild('error'));
3060 this.oDbg.log(this.username + " registered succesfully",0);
3068 JSJaCConnection.prototype._doLegacyAuth = function() {
3069 if (this.authtype != 'nonsasl' && this.authtype != 'anonymous')
3073 * Non-SASL Authentication as described in JEP-0078
3075 var iq = new JSJaCIQ();
3076 iq.setIQ(null,'get','auth1');
3077 iq.appendNode('query', {xmlns: 'jabber:iq:auth'},
3078 [['username', this.username]]);
3080 this.send(iq,this._doLegacyAuth2);
3087 JSJaCConnection.prototype._doLegacyAuth2 = function(iq) {
3088 if (!iq || iq.getType() != 'result') {
3089 if (iq && iq.getType() == 'error')
3090 this._handleEvent('onerror',iq.getChild('error'));
3095 var use_digest = (iq.getChild('digest') != null);
3098 * Send authentication
3100 var iq = new JSJaCIQ();
3101 iq.setIQ(null,'set','auth2');
3103 query = iq.appendNode('query', {xmlns: 'jabber:iq:auth'},
3104 [['username', this.username],
3105 ['resource', this.resource]]);
3107 if (use_digest) { // digest login
3108 query.appendChild(iq.buildNode('digest', {xmlns: 'jabber:iq:auth'},
3109 hex_sha1(this.streamid + this.pass)));
3110 } else if (this.allow_plain) { // use plaintext auth
3111 query.appendChild(iq.buildNode('password', {xmlns: 'jabber:iq:auth'},
3114 this.oDbg.log("no valid login mechanism found",1);
3119 this.send(iq,this._doLegacyAuthDone);
3125 JSJaCConnection.prototype._doLegacyAuthDone = function(iq) {
3126 if (iq.getType() != 'result') { // auth' failed
3127 if (iq.getType() == 'error')
3128 this._handleEvent('onerror',iq.getChild('error'));
3131 this._handleEvent('onconnect');
3137 JSJaCConnection.prototype._doSASLAuth = function() {
3138 if (this.authtype == 'nonsasl' || this.authtype == 'anonymous')
3141 if (this.authtype == 'saslanon') {
3142 if (this.mechs['ANONYMOUS']) {
3143 this.oDbg.log("SASL using mechanism 'ANONYMOUS'",2);
3144 return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>",
3145 this._doSASLAuthDone);
3147 this.oDbg.log("SASL ANONYMOUS requested but not supported",1);
3149 if (this.mechs['DIGEST-MD5']) {
3150 this.oDbg.log("SASL using mechanism 'DIGEST-MD5'",2);
3151 return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>",
3152 this._doSASLAuthDigestMd5S1);
3153 } else if (this.allow_plain && this.mechs['PLAIN']) {
3154 this.oDbg.log("SASL using mechanism 'PLAIN'",2);
3155 var authStr = this.username+'@'+
3156 this.domain+String.fromCharCode(0)+
3157 this.username+String.fromCharCode(0)+
3159 this.oDbg.log("authenticating with '"+authStr+"'",2);
3160 authStr = b64encode(authStr);
3161 return this._sendRaw("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"+authStr+"</auth>",
3162 this._doSASLAuthDone);
3164 this.oDbg.log("No SASL mechanism applied",1);
3165 this.authtype = 'nonsasl'; // fallback
3173 JSJaCConnection.prototype._doSASLAuthDigestMd5S1 = function(el) {
3174 if (el.nodeName != "challenge") {
3175 this.oDbg.log("challenge missing",1);
3176 this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
3179 var challenge = b64decode(el.firstChild.nodeValue);
3180 this.oDbg.log("got challenge: "+challenge,2);
3181 this._nonce = challenge.substring(challenge.indexOf("nonce=")+7);
3182 this._nonce = this._nonce.substring(0,this._nonce.indexOf("\""));
3183 this.oDbg.log("nonce: "+this._nonce,2);
3184 if (this._nonce == '' || this._nonce.indexOf('\"') != -1) {
3185 this.oDbg.log("nonce not valid, aborting",1);
3190 this._digest_uri = "xmpp/";
3191 // if (typeof(this.host) != 'undefined' && this.host != '') {
3192 // this._digest-uri += this.host;
3193 // if (typeof(this.port) != 'undefined' && this.port)
3194 // this._digest-uri += ":" + this.port;
3195 // this._digest-uri += '/';
3197 this._digest_uri += this.domain;
3199 this._cnonce = cnonce(14);
3201 this._nc = '00000001';
3203 var A1 = str_md5(this.username+':'+this.domain+':'+this.pass)+
3204 ':'+this._nonce+':'+this._cnonce;
3206 var A2 = 'AUTHENTICATE:'+this._digest_uri;
3208 var response = hex_md5(hex_md5(A1)+':'+this._nonce+':'+this._nc+':'+
3209 this._cnonce+':auth:'+hex_md5(A2));
3211 var rPlain = 'username="'+this.username+'",realm="'+this.domain+
3212 '",nonce="'+this._nonce+'",cnonce="'+this._cnonce+'",nc="'+this._nc+
3213 '",qop=auth,digest-uri="'+this._digest_uri+'",response="'+response+
3214 '",charset="utf-8"';
3216 this.oDbg.log("response: "+rPlain,2);
3218 this._sendRaw("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"+
3219 b64encode(rPlain)+"</response>",
3220 this._doSASLAuthDigestMd5S2);
3227 JSJaCConnection.prototype._doSASLAuthDigestMd5S2 = function(el) {
3228 if (el.nodeName == 'failure') {
3230 this.oDbg.log("auth error: "+el.xml,1);
3232 this.oDbg.log("auth error",1);
3233 this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
3238 var response = b64decode(el.firstChild.nodeValue);
3239 this.oDbg.log("response: "+response,2);
3241 var rspauth = response.substring(response.indexOf("rspauth=")+8);
3242 this.oDbg.log("rspauth: "+rspauth,2);
3244 var A1 = str_md5(this.username+':'+this.domain+':'+this.pass)+
3245 ':'+this._nonce+':'+this._cnonce;
3247 var A2 = ':'+this._digest_uri;
3249 var rsptest = hex_md5(hex_md5(A1)+':'+this._nonce+':'+this._nc+':'+
3250 this._cnonce+':auth:'+hex_md5(A2));
3251 this.oDbg.log("rsptest: "+rsptest,2);
3253 if (rsptest != rspauth) {
3254 this.oDbg.log("SASL Digest-MD5: server repsonse with wrong rspauth",1);
3259 if (el.nodeName == 'success') {
3260 this._reInitStream(JSJaC.bind(this._doStreamBind, this));
3261 } else { // some extra turn
3262 this._sendRaw("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>",
3263 this._doSASLAuthDone);
3270 JSJaCConnection.prototype._doSASLAuthDone = function (el) {
3271 if (el.nodeName != 'success') {
3272 this.oDbg.log("auth failed",1);
3273 this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
3276 this._reInitStream(JSJaC.bind(this._doStreamBind, this));
3283 JSJaCConnection.prototype._doStreamBind = function() {
3284 var iq = new JSJaCIQ();
3285 iq.setIQ(null,'set','bind_1');
3286 iq.appendNode("bind", {xmlns: "urn:ietf:params:xml:ns:xmpp-bind"},
3287 [["resource", this.resource]]);
3288 this.oDbg.log(iq.xml());
3289 this.send(iq,this._doXMPPSess);
3295 JSJaCConnection.prototype._doXMPPSess = function(iq) {
3296 if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
3298 if (iq.getType() == 'error')
3299 this._handleEvent('onerror',iq.getChild('error'));
3303 this.fulljid = iq.getChildVal("jid");
3304 this.jid = this.fulljid.substring(0,this.fulljid.lastIndexOf('/'));
3307 iq.setIQ(null,'set','sess_1');
3308 iq.appendNode("session", {xmlns: "urn:ietf:params:xml:ns:xmpp-session"},
3310 this.oDbg.log(iq.xml());
3311 this.send(iq,this._doXMPPSessDone);
3317 JSJaCConnection.prototype._doXMPPSessDone = function(iq) {
3318 if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
3320 if (iq.getType() == 'error')
3321 this._handleEvent('onerror',iq.getChild('error'));
3324 this._handleEvent('onconnect');
3330 JSJaCConnection.prototype._handleEvent = function(event,arg) {
3331 event = event.toLowerCase(); // don't be case-sensitive here
3332 this.oDbg.log("incoming event '"+event+"'",3);
3333 if (!this._events[event])
3335 this.oDbg.log("handling event '"+event+"'",2);
3336 for (var i=0;i<this._events[event].length; i++) {
3337 var aEvent = this._events[event][i];
3338 if (typeof aEvent.handler == 'function') {
3341 if (arg.pType) { // it's a packet
3342 if ((!arg.getNode().hasChildNodes() && aEvent.childName != '*') ||
3343 (arg.getNode().hasChildNodes() &&
3344 !arg.getChild(aEvent.childName, aEvent.childNS)))
3346 if (aEvent.type != '*' &&
3347 arg.getType() != aEvent.type)
3349 this.oDbg.log(aEvent.childName+"/"+aEvent.childNS+"/"+aEvent.type+" => match for handler "+aEvent.handler,3);
3351 if (aEvent.handler(arg)) {
3357 if (aEvent.handler()) {
3363 if (e.fileName&&e.lineNumber) {
3364 this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message+' in '+e.fileName+' line '+e.lineNumber,1);
3366 this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message,1);
3377 JSJaCConnection.prototype._handlePID = function(aJSJaCPacket) {
3378 if (!aJSJaCPacket.getID())
3380 for (var i in this._regIDs) {
3381 if (this._regIDs.hasOwnProperty(i) &&
3382 this._regIDs[i] && i == aJSJaCPacket.getID()) {
3383 var pID = aJSJaCPacket.getID();
3384 this.oDbg.log("handling "+pID,3);
3386 if (this._regIDs[i].cb.call(this, aJSJaCPacket, this._regIDs[i].arg) === false) {
3390 this._unregisterPID(pID);
3395 this.oDbg.log(e.name+": "+ e.message, 1);
3396 this._unregisterPID(pID);
3407 JSJaCConnection.prototype._handleResponse = function(req) {
3408 var rootEl = this._parseResponse(req);
3413 for (var i=0; i<rootEl.childNodes.length; i++) {
3414 if (this._sendRawCallbacks.length) {
3415 var cb = this._sendRawCallbacks[0];
3416 this._sendRawCallbacks = this._sendRawCallbacks.slice(1, this._sendRawCallbacks.length);
3417 cb.fn.call(this, rootEl.childNodes.item(i), cb.arg);
3420 this._inQ = this._inQ.concat(rootEl.childNodes.item(i));
3427 JSJaCConnection.prototype._parseStreamFeatures = function(doc) {
3429 this.oDbg.log("nothing to parse ... aborting",1);
3434 if (doc.getElementsByTagNameNS) {
3435 errorTag = doc.getElementsByTagNameNS("http://etherx.jabber.org/streams", "error").item(0);
3437 var errors = doc.getElementsByTagName("error");
3438 for (var i=0; i<errors.length; i++)
3439 if (errors.item(i).namespaceURI == "http://etherx.jabber.org/streams" ||
3440 errors.item(i).getAttribute('xmlns') == "http://etherx.jabber.org/streams") {
3441 errorTag = errors.item(i);
3447 this._setStatus("internal_server_error");
3448 clearTimeout(this._timeout); // remove timer
3449 clearInterval(this._interval);
3450 clearInterval(this._inQto);
3451 this._handleEvent('onerror',JSJaCError('503','cancel','session-terminate'));
3452 this._connected = false;
3453 this.oDbg.log("Disconnected.",1);
3454 this._handleEvent('ondisconnect');
3458 this.mechs = new Object();
3459 var lMec1 = doc.getElementsByTagName("mechanisms");
3460 this.has_sasl = false;
3461 for (var i=0; i<lMec1.length; i++)
3462 if (lMec1.item(i).getAttribute("xmlns") ==
3463 "urn:ietf:params:xml:ns:xmpp-sasl") {
3465 var lMec2 = lMec1.item(i).getElementsByTagName("mechanism");
3466 for (var j=0; j<lMec2.length; j++)
3467 this.mechs[lMec2.item(j).firstChild.nodeValue] = true;
3471 this.oDbg.log("SASL detected",2);
3473 this.oDbg.log("No support for SASL detected",2);
3477 // Get the server CAPS (if available)
3478 this.server_caps=null;
3479 var sCaps = doc.getElementsByTagName("c");
3480 for (var i=0; i<sCaps.length; i++) {
3481 var c_sCaps=sCaps.item(i);
3482 var x_sCaps=c_sCaps.getAttribute("xmlns");
3483 var v_sCaps=c_sCaps.getAttribute("ver");
3485 if ((x_sCaps == NS_CAPS) && v_sCaps) {
3486 this.server_caps=v_sCaps;
3497 JSJaCConnection.prototype._process = function(timerval) {
3498 if (!this.connected()) {
3499 this.oDbg.log("Connection lost ...",1);
3501 clearInterval(this._interval);
3505 this.setPollInterval(timerval);
3508 clearTimeout(this._timeout);
3510 var slot = this._getFreeSlot();
3515 if (typeof(this._req[slot]) != 'undefined' &&
3516 typeof(this._req[slot].r) != 'undefined' &&
3517 this._req[slot].r.readyState != 4) {
3518 this.oDbg.log("Slot "+slot+" is not ready");
3522 if (!this.isPolling() && this._pQueue.length == 0 &&
3523 this._req[(slot+1)%2] && this._req[(slot+1)%2].r.readyState != 4) {
3524 this.oDbg.log("all slots busy, standby ...", 2);
3528 if (!this.isPolling())
3529 this.oDbg.log("Found working slot at "+slot,2);
3531 this._req[slot] = this._setupRequest(true);
3533 /* setup onload handler for async send */
3534 this._req[slot].r.onreadystatechange =
3535 JSJaC.bind(function() {
3536 if (this._req[slot].r.readyState == 4) {
3537 this._setStatus('processing');
3538 this.oDbg.log("async recv: "+this._req[slot].r.responseText,4);
3539 this._handleResponse(this._req[slot]);
3541 if (!this.connected())
3544 // schedule next tick
3545 if (this._pQueue.length) {
3546 this._timeout = setTimeout(JSJaC.bind(this._process, this),100);
3548 this.oDbg.log("scheduling next poll in "+this.getPollInterval()+
3550 this._timeout = setTimeout(JSJaC.bind(this._process, this),this.getPollInterval());
3556 this._req[slot].r.onerror =
3557 JSJaC.bind(function() {
3558 if (!this.connected())
3561 this.oDbg.log('XmlHttpRequest error ('+this._errcnt+')',1);
3562 if (this._errcnt > JSJAC_ERR_COUNT) {
3568 this._setStatus('onerror_fallback');
3570 // schedule next tick
3571 setTimeout(JSJaC.bind(this._resume, this),this.getPollInterval());
3574 } catch(e) { } // well ... no onerror property available, maybe we
3575 // can catch the error somewhere else ...
3577 var reqstr = this._getRequestString();
3579 if (typeof(this._rid) != 'undefined') // remember request id if any
3580 this._req[slot].rid = this._rid;
3582 this.oDbg.log("sending: " + reqstr,4);
3583 this._req[slot].r.send(reqstr);
3589 JSJaCConnection.prototype._registerPID = function(pID,cb,arg) {
3592 this._regIDs[pID] = new Object();
3593 this._regIDs[pID].cb = cb;
3595 this._regIDs[pID].arg = arg;
3596 this.oDbg.log("registered "+pID,3);
3601 * partial function binding sendEmpty to callback
3604 JSJaCConnection.prototype._prepSendEmpty = function(cb, ctx) {
3606 ctx._sendEmpty(JSJaC.bind(cb, ctx));
3611 * send empty request
3612 * waiting for stream id to be able to proceed with authentication
3615 JSJaCConnection.prototype._sendEmpty = function(cb) {
3616 var slot = this._getFreeSlot();
3617 this._req[slot] = this._setupRequest(true);
3619 this._req[slot].r.onreadystatechange =
3620 JSJaC.bind(function() {
3621 if (this._req[slot].r.readyState == 4) {
3622 this.oDbg.log("async recv: "+this._req[slot].r.responseText,4);
3623 cb(this._req[slot].r); // handle response
3627 if (typeof(this._req[slot].r.onerror) != 'undefined') {
3628 this._req[slot].r.onerror =
3629 JSJaC.bind(function(e) {
3630 this.oDbg.log('XmlHttpRequest error',1);
3635 var reqstr = this._getRequestString();
3636 this.oDbg.log("sending: " + reqstr,4);
3637 this._req[slot].r.send(reqstr);
3643 JSJaCConnection.prototype._sendRaw = function(xml,cb,arg) {
3645 this._sendRawCallbacks.push({fn: cb, arg: arg});
3647 this._pQueue.push(xml);
3656 JSJaCConnection.prototype._setStatus = function(status) {
3657 if (!status || status == '')
3659 if (status != this._status) { // status changed!
3660 this._status = status;
3661 this._handleEvent('onstatuschanged', status);
3662 this._handleEvent('status_changed', status);
3669 JSJaCConnection.prototype._unregisterPID = function(pID) {
3670 if (!this._regIDs[pID])
3672 this._regIDs[pID] = null;
3673 this.oDbg.log("unregistered "+pID,3);
3679 * @fileoverview All stuff related to HTTP Binding
3680 * @author Stefan Strigler steve@zeank.in-berlin.de
3681 * @version $Revision$
3685 * Instantiates an HTTP Binding session
3686 * @class Implementation of {@link
3687 * http://www.xmpp.org/extensions/xep-0206.html XMPP Over BOSH}
3688 * formerly known as HTTP Binding.
3689 * @extends JSJaCConnection
3692 function JSJaCHttpBindingConnection(oArg) {
3696 this.base = JSJaCConnection;
3703 this._hold = JSJACHBC_MAX_HOLD;
3707 this._inactivity = 0;
3711 this._last_requests = new Object(); // 'hash' storing hold+1 last requests
3715 this._last_rid = 0; // I know what you did last summer
3719 this._min_polling = 0;
3728 this._wait = JSJACHBC_MAX_WAIT;
3730 JSJaCHttpBindingConnection.prototype = new JSJaCConnection();
3733 * Inherit an instantiated HTTP Binding session
3735 JSJaCHttpBindingConnection.prototype.inherit = function(oArg) {
3737 var oJid = new JSJaCJID(oArg.jid);
3738 this.domain = oJid.getDomain();
3739 this.username = oJid.getNode();
3740 this.resource = oJid.getResource();
3742 this.domain = oArg.domain || 'localhost';
3743 this.username = oArg.username;
3744 this.resource = oArg.resource;
3746 this._sid = oArg.sid;
3747 this._rid = oArg.rid;
3748 this._min_polling = oArg.polling;
3749 this._inactivity = oArg.inactivity;
3750 this._setHold(oArg.requests-1);
3751 this.setPollInterval(this._timerval);
3753 this._wait = oArg.wait; // for whatever reason
3755 this._connected = true;
3757 this._handleEvent('onconnect');
3759 this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
3760 JSJAC_CHECKQUEUEINTERVAL);
3761 this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
3762 JSJAC_CHECKINQUEUEINTERVAL);
3763 this._timeout = setTimeout(JSJaC.bind(this._process, this),
3764 this.getPollInterval());
3768 * Sets poll interval
3769 * @param {int} timerval the interval in seconds
3771 JSJaCHttpBindingConnection.prototype.setPollInterval = function(timerval) {
3772 if (timerval && !isNaN(timerval)) {
3773 if (!this.isPolling())
3774 this._timerval = 100;
3775 else if (this._min_polling && timerval < this._min_polling*1000)
3776 this._timerval = this._min_polling*1000;
3777 else if (this._inactivity && timerval > this._inactivity*1000)
3778 this._timerval = this._inactivity*1000;
3780 this._timerval = timerval;
3782 return this._timerval;
3786 * whether this session is in polling mode
3789 JSJaCHttpBindingConnection.prototype.isPolling = function() { return (this._hold == 0) };
3794 JSJaCHttpBindingConnection.prototype._getFreeSlot = function() {
3795 for (var i=0; i<this._hold+1; i++)
3796 if (typeof(this._req[i]) == 'undefined' || typeof(this._req[i].r) == 'undefined' || this._req[i].r.readyState == 4)
3798 return -1; // nothing found
3804 JSJaCHttpBindingConnection.prototype._getHold = function() { return this._hold; };
3809 JSJaCHttpBindingConnection.prototype._getRequestString = function(raw, last) {
3813 // check if we're repeating a request
3815 if (this._rid <= this._last_rid && typeof(this._last_requests[this._rid]) != 'undefined') // repeat!
3816 reqstr = this._last_requests[this._rid].xml;
3817 else { // grab from queue
3819 while (this._pQueue.length) {
3820 var curNode = this._pQueue[0];
3822 this._pQueue = this._pQueue.slice(1,this._pQueue.length);
3825 reqstr = "<body xml:lang='"+XML_LANG+"' rid='"+this._rid+"' sid='"+this._sid+"' xmlns='http://jabber.org/protocol/httpbind' ";
3826 if (JSJAC_HAVEKEYS) {
3827 reqstr += "key='"+this._keys.getKey()+"' ";
3828 if (this._keys.lastKey()) {
3829 this._keys = new JSJaCKeys(hex_sha1,this.oDbg);
3830 reqstr += "newkey='"+this._keys.getKey()+"' ";
3834 reqstr += "type='terminate'";
3835 else if (this._reinit) {
3836 if (JSJACHBC_USE_BOSH_VER)
3837 reqstr += "xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh' to='"+this.domain+"'";
3838 this._reinit = false;
3841 if (xml != '' || raw != '') {
3842 reqstr += ">" + raw + xml + "</body>";
3847 this._last_requests[this._rid] = new Object();
3848 this._last_requests[this._rid].xml = reqstr;
3849 this._last_rid = this._rid;
3851 for (var i in this._last_requests)
3852 if (this._last_requests.hasOwnProperty(i) &&
3853 i < this._rid-this._hold)
3854 delete(this._last_requests[i]); // truncate
3863 JSJaCHttpBindingConnection.prototype._getInitialRequestString = function() {
3864 var reqstr = "<body xml:lang='"+XML_LANG+"' content='text/xml; charset=utf-8' hold='"+this._hold+"' xmlns='http://jabber.org/protocol/httpbind' to='"+this.authhost+"' wait='"+this._wait+"' rid='"+this._rid+"'";
3866 reqstr += " secure='"+this.secure+"'";
3867 if (JSJAC_HAVEKEYS) {
3868 this._keys = new JSJaCKeys(hex_sha1,this.oDbg); // generate first set of keys
3869 key = this._keys.getKey();
3870 reqstr += " newkey='"+key+"'";
3873 if (JSJACHBC_USE_BOSH_VER) {
3874 reqstr += " ver='" + JSJACHBC_BOSH_VERSION + "'";
3875 reqstr += " xmlns:xmpp='urn:xmpp:xbosh'";
3876 if (this.authtype == 'sasl' || this.authtype == 'saslanon')
3877 reqstr += " xmpp:version='1.0'";
3886 JSJaCHttpBindingConnection.prototype._getStreamID = function(req) {
3888 this.oDbg.log(req.responseText,4);
3890 if (!req.responseXML || !req.responseXML.documentElement) {
3891 this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3894 var body = req.responseXML.documentElement;
3896 // any session error?
3897 if(body.getAttribute('type') == 'terminate') {
3898 this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3902 // extract stream id used for non-SASL authentication
3903 if (body.getAttribute('authid')) {
3904 this.streamid = body.getAttribute('authid');
3905 this.oDbg.log("got streamid: "+this.streamid,2);
3908 if (!this._parseStreamFeatures(body)) {
3909 this._sendEmpty(JSJaC.bind(this._getStreamID, this));
3913 this._timeout = setTimeout(JSJaC.bind(this._process, this),
3914 this.getPollInterval());
3917 this._doInBandReg();
3925 JSJaCHttpBindingConnection.prototype._getSuspendVars = function() {
3926 return ('host,port,secure,_rid,_last_rid,_wait,_min_polling,_inactivity,_hold,_last_requests,_pause').split(',');
3932 JSJaCHttpBindingConnection.prototype._handleInitialResponse = function(req) {
3934 // This will throw an error on Mozilla when the connection was refused
3935 this.oDbg.log(req.getAllResponseHeaders(),4);
3936 this.oDbg.log(req.responseText,4);
3938 this.oDbg.log("No response",4);
3941 if (req.status != 200 || !req.responseXML) {
3942 this.oDbg.log("initial response broken (status: "+req.status+")",1);
3943 this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3946 var body = req.responseXML.documentElement;
3948 if (!body || body.tagName != 'body' || body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
3949 this.oDbg.log("no body element or incorrect body in initial response",1);
3950 this._handleEvent("onerror",JSJaCError("500","wait","internal-service-error"));
3954 // Check for errors from the server
3955 if (body.getAttribute("type") == "terminate") {
3956 this.oDbg.log("invalid response:\n" + req.responseText,1);
3957 clearTimeout(this._timeout); // remove timer
3958 this._connected = false;
3959 this.oDbg.log("Disconnected.",1);
3960 this._handleEvent('ondisconnect');
3961 this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
3966 this._sid = body.getAttribute('sid');
3967 this.oDbg.log("got sid: "+this._sid,2);
3969 // get attributes from response body
3970 if (body.getAttribute('polling'))
3971 this._min_polling = body.getAttribute('polling');
3973 if (body.getAttribute('inactivity'))
3974 this._inactivity = body.getAttribute('inactivity');
3976 if (body.getAttribute('requests'))
3977 this._setHold(body.getAttribute('requests')-1);
3978 this.oDbg.log("set hold to " + this._getHold(),2);
3980 if (body.getAttribute('ver'))
3981 this._bosh_version = body.getAttribute('ver');
3983 if (body.getAttribute('maxpause'))
3984 this._pause = Number.min(body.getAttribute('maxpause'), JSJACHBC_MAXPAUSE);
3986 // must be done after response attributes have been collected
3987 this.setPollInterval(this._timerval);
3989 /* start sending from queue for not polling connections */
3990 this._connected = true;
3992 this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
3993 JSJAC_CHECKINQUEUEINTERVAL);
3994 this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
3995 JSJAC_CHECKQUEUEINTERVAL);
3997 /* wait for initial stream response to extract streamid needed
4000 this._getStreamID(req);
4006 JSJaCHttpBindingConnection.prototype._parseResponse = function(req) {
4007 if (!this.connected() || !req)
4010 var r = req.r; // the XmlHttpRequest
4013 if (r.status == 404 || r.status == 403) {
4014 // connection manager killed session
4019 if (r.status != 200 || !r.responseXML) {
4021 var errmsg = "invalid response ("+r.status+"):\n" + r.getAllResponseHeaders()+"\n"+r.responseText;
4023 errmsg += "\nResponse failed to parse!";
4024 this.oDbg.log(errmsg,1);
4025 if (this._errcnt > JSJAC_ERR_COUNT) {
4031 if (this.connected()) {
4032 this.oDbg.log("repeating ("+this._errcnt+")",1);
4033 this._setStatus('proto_error_fallback');
4035 // schedule next tick
4036 setTimeout(JSJaC.bind(this._resume, this),
4037 this.getPollInterval());
4043 this.oDbg.log("XMLHttpRequest error: status not available", 1);
4045 if (this._errcnt > JSJAC_ERR_COUNT) {
4049 if (this.connected()) {
4050 this.oDbg.log("repeating ("+this._errcnt+")",1);
4052 this._setStatus('proto_error_fallback');
4054 // schedule next tick
4055 setTimeout(JSJaC.bind(this._resume, this),
4056 this.getPollInterval());
4062 var body = r.responseXML.documentElement;
4063 if (!body || body.tagName != 'body' ||
4064 body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
4065 this.oDbg.log("invalid response:\n" + r.responseText,1);
4067 clearTimeout(this._timeout); // remove timer
4068 clearInterval(this._interval);
4069 clearInterval(this._inQto);
4071 this._connected = false;
4072 this.oDbg.log("Disconnected.",1);
4073 this._handleEvent('ondisconnect');
4075 this._setStatus('internal_server_error');
4076 this._handleEvent('onerror',
4077 JSJaCError('500','wait','internal-server-error'));
4082 if (typeof(req.rid) != 'undefined' && this._last_requests[req.rid]) {
4083 if (this._last_requests[req.rid].handled) {
4084 this.oDbg.log("already handled "+req.rid,2);
4087 this._last_requests[req.rid].handled = true;
4091 // Check for errors from the server
4092 if (body.getAttribute("type") == "terminate") {
4094 var condition = body.getAttribute('condition');
4096 if (condition != "item-not-found") {
4097 this.oDbg.log("session terminated:\n" + r.responseText,1);
4099 clearTimeout(this._timeout); // remove timer
4100 clearInterval(this._interval);
4101 clearInterval(this._inQto);
4104 removeDB('jsjac', 'state');
4107 this._connected = false;
4109 if (condition == "remote-stream-error")
4110 if (body.getElementsByTagName("conflict").length > 0)
4111 this._setStatus("session-terminate-conflict");
4112 if (condition == null)
4113 condition = 'session-terminate';
4114 this._handleEvent('onerror',JSJaCError('503','cancel',condition));
4116 this.oDbg.log("Aborting remaining connections",4);
4118 for (var i=0; i<this._hold+1; i++) {
4120 this._req[i].r.abort();
4121 } catch(e) { this.oDbg.log(e, 1); }
4124 this.oDbg.log("parseResponse done with terminating", 3);
4126 this.oDbg.log("Disconnected.",1);
4127 this._handleEvent('ondisconnect');
4130 if (this._errcnt > JSJAC_ERR_COUNT)
4138 return r.responseXML.documentElement;
4144 JSJaCHttpBindingConnection.prototype._reInitStream = function(cb) {
4145 // tell http binding to reinit stream with/before next request
4146 this._reinit = true;
4148 this._sendEmpty(this._prepReInitStreamWait(cb));
4152 JSJaCHttpBindingConnection.prototype._prepReInitStreamWait = function(cb) {
4153 return JSJaC.bind(function(req) {
4154 this._reInitStreamWait(req, cb);
4161 JSJaCHttpBindingConnection.prototype._reInitStreamWait = function(req, cb) {
4162 this.oDbg.log("checking for stream features");
4163 var doc = req.responseXML.documentElement;
4165 if (doc.getElementsByTagNameNS) {
4166 this.oDbg.log("checking with namespace");
4167 var features = doc.getElementsByTagNameNS('http://etherx.jabber.org/streams',
4168 'features').item(0);
4170 var bind = features.getElementsByTagNameNS('urn:ietf:params:xml:ns:xmpp-bind',
4174 var featuresNL = doc.getElementsByTagName('stream:features');
4175 for (var i=0, l=featuresNL.length; i<l; i++) {
4176 if (featuresNL.item(i).namespaceURI == 'http://etherx.jabber.org/streams' ||
4177 featuresNL.item(i).getAttribute('xmlns') ==
4178 'http://etherx.jabber.org/streams') {
4179 var features = featuresNL.item(i);
4184 var bind = features.getElementsByTagName('bind');
4185 for (var i=0, l=bind.length; i<l; i++) {
4186 if (bind.item(i).namespaceURI == 'urn:ietf:params:xml:ns:xmpp-bind' ||
4187 bind.item(i).getAttribute('xmlns') ==
4188 'urn:ietf:params:xml:ns:xmpp-bind') {
4189 bind = bind.item(i);
4195 this.oDbg.log(features);
4196 this.oDbg.log(bind);
4202 this.oDbg.log("no bind feature - giving up",1);
4203 this._handleEvent('onerror',JSJaCError('503','cancel',"service-unavailable"));
4204 this._connected = false;
4205 this.oDbg.log("Disconnected.",1);
4206 this._handleEvent('ondisconnect');
4210 this._sendEmpty(this._prepReInitStreamWait(cb));
4217 JSJaCHttpBindingConnection.prototype._resume = function() {
4218 /* make sure to repeat last request as we can be sure that
4219 * it had failed (only if we're not using the 'pause' attribute
4221 if (this._pause == 0 && this._rid >= this._last_rid)
4222 this._rid = this._last_rid-1;
4230 JSJaCHttpBindingConnection.prototype._setHold = function(hold) {
4231 if (!hold || isNaN(hold) || hold < 0)
4233 else if (hold > JSJACHBC_MAX_HOLD)
4234 hold = JSJACHBC_MAX_HOLD;
4242 JSJaCHttpBindingConnection.prototype._setupRequest = function(async) {
4243 var req = new Object();
4244 var r = XmlHttp.create();
4246 r.open("POST",this._httpbase,async);
4247 r.setRequestHeader('Content-Type','text/xml; charset=utf-8');
4248 } catch(e) { this.oDbg.log(e,1); }
4251 req.rid = this._rid;
4258 JSJaCHttpBindingConnection.prototype._suspend = function() {
4259 if (this._pause == 0)
4260 return; // got nothing to do
4262 var slot = this._getFreeSlot();
4263 // Intentionally synchronous
4264 this._req[slot] = this._setupRequest(false);
4266 var reqstr = "<body xml:lang='"+XML_LANG+"' pause='"+this._pause+"' xmlns='http://jabber.org/protocol/httpbind' sid='"+this._sid+"' rid='"+this._rid+"'";
4267 if (JSJAC_HAVEKEYS) {
4268 reqstr += " key='"+this._keys.getKey()+"'";
4269 if (this._keys.lastKey()) {
4270 this._keys = new JSJaCKeys(hex_sha1,this.oDbg);
4271 reqstr += " newkey='"+this._keys.getKey()+"'";
4277 while (this._pQueue.length) {
4278 var curNode = this._pQueue[0];
4280 this._pQueue = this._pQueue.slice(1,this._pQueue.length);
4283 //reqstr += "<presence type='unavailable' xmlns='jabber:client'/>";
4284 reqstr += "</body>";
4286 this.oDbg.log("Disconnecting: " + reqstr,4);
4287 this._req[slot].r.send(reqstr);